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

Camelcade / Perl5-IDEA / #525521553

27 May 2025 10:24AM UTC coverage: 82.286% (-0.03%) from 82.32%
#525521553

push

github

hurricup
Removed redundant implementations, moved up to the abstract class

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

128 existing lines in 30 files now uncovered.

30886 of 37535 relevant lines covered (82.29%)

0.82 hits per line

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

79.49
/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.TextRange;
30
import com.intellij.openapi.util.text.StringUtil;
31
import com.intellij.psi.PsiDocumentManager;
32
import com.intellij.psi.PsiElement;
33
import com.intellij.psi.PsiFile;
34
import com.intellij.psi.util.PsiTreeUtil;
35
import com.intellij.ui.SpeedSearchComparator;
36
import com.perl5.PerlIcons;
37
import com.perl5.lang.perl.extensions.PerlCodeGenerator;
38
import com.perl5.lang.perl.idea.codeInsight.PerlMethodMember;
39
import com.perl5.lang.perl.idea.codeInsight.typeInference.value.PerlValue;
40
import com.perl5.lang.perl.parser.PerlParserUtil;
41
import com.perl5.lang.perl.psi.PerlMethodDefinition;
42
import com.perl5.lang.perl.psi.PerlNamespaceDefinitionElement;
43
import com.perl5.lang.perl.psi.PerlSubDefinitionElement;
44
import com.perl5.lang.perl.psi.PerlSubElement;
45
import com.perl5.lang.perl.psi.utils.PerlElementFactory;
46
import com.perl5.lang.perl.psi.utils.PerlSubAnnotations;
47
import com.perl5.lang.perl.psi.utils.PerlSubArgument;
48
import com.perl5.lang.perl.util.PerlPackageUtil;
49
import org.jetbrains.annotations.NotNull;
50
import org.jetbrains.annotations.Nullable;
51
import org.jetbrains.annotations.TestOnly;
52

53
import java.util.*;
54
import java.util.function.UnaryOperator;
55

56

57
public class PerlCodeGeneratorImpl implements PerlCodeGenerator {
1✔
58
  private static UnaryOperator<List<PerlMethodMember>> ourTestChooser = null;
1✔
59
  public static final PerlCodeGenerator INSTANCE = new PerlCodeGeneratorImpl();
1✔
60

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

UNCOV
216
    chooser.setTitle("Override/Implement Method");
×
217
    chooser.setCopyJavadocVisible(false);
×
UNCOV
218
    chooser.show();
×
UNCOV
219
    if (chooser.getExitCode() != DialogWrapper.OK_EXIT_CODE) {
×
UNCOV
220
      return null;
×
221
    }
222

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

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

230
    for (String name : askFieldsNames(anchor.getProject(), "Type comma-separated setters names:", "Generating Setters")) {
1✔
231
      code.append(getSetterCode(name));
1✔
232
    }
1✔
233

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

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

243
    for (String name : askFieldsNames(anchor.getProject(), "Type comma-separated getters names:", "Generating Getters")) {
1✔
244
      code.append(getGetterCode(name));
1✔
245
    }
1✔
246

247
    if (!code.isEmpty()) {
1✔
248
      insertCodeAfterElement(anchor, code.toString(), editor);
1✔
249
    }
250
  }
1✔
251

252
  @Override
253
  public void generateGettersAndSetters(PsiElement anchor, Editor editor) {
254
    StringBuilder code = new StringBuilder();
1✔
255

256
    for (String name : askFieldsNames(anchor.getProject(), "Type comma-separated accessors names:", "Generating Getters and Setters")) {
1✔
257
      code.append(getGetterCode(name));
1✔
258
      code.append(getSetterCode(name));
1✔
259
    }
1✔
260

261
    if (!code.isEmpty()) {
1✔
262
      insertCodeAfterElement(anchor, code.toString(), editor);
1✔
263
    }
264
  }
1✔
265

266
  @Override
267
  public void generateConstructor(PsiElement anchor, Editor editor) {
268
    insertCodeAfterElement(anchor, getConstructorCode(), editor);
1✔
269
  }
1✔
270

271
  protected List<String> askFieldsNames(
272
    Project project,
273
    String promptText,
274
    String promptTitle
275
  ) {
276
    Set<String> result = new HashSet<>();
1✔
277
    String name = Messages.showInputDialog(project, promptText, promptTitle, Messages.getQuestionIcon(), "", null);
1✔
278

279
    if (!StringUtil.isEmpty(name)) {
1✔
280

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

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

298
                                                         if (!code.isEmpty() && document != null) {
1✔
299
                                                           manager.doPostponedOperationsAndUnblockDocument(document);
1✔
300

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

306
                                                           if (newFile.getFirstChild() != null && newFile.getLastChild() != null) {
1✔
307
                                                             container
1✔
308
                                                               .addRangeAfter(newFile.getFirstChild(), newFile.getLastChild(), anchor);
1✔
309
                                                           }
310

311
                                                           manager.commitDocument(document);
1✔
312
                                                           editor.getCaretModel().moveToOffset(newOffset);
1✔
313
                                                           editor.getScrollingModel().scrollToCaret(ScrollType.CENTER);
1✔
314
                                                         }
315
                                                       });
1✔
316
  }
1✔
317

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

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

334
  public static String getConstructorCode() {
335
    return """
1✔
336

337
      sub new
338
      {
339
        my ($proto) = @_;
340
        my $self = bless {}, $proto;
341
        return $self;
342
      }
343

344
      """;
345
  }
346

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