From 5cb1d206fea7b4cd9b1afaf237419c1f0f58d8c2 Mon Sep 17 00:00:00 2001 From: Mikhail Pyltsin Date: Thu, 14 Dec 2023 11:18:41 +0000 Subject: [PATCH] [java-execution] IDEA-339923 runnable and gutters for implicit classes GitOrigin-RevId: 73795451d90cd498aea44d3db7420fdbfc809264 --- .../ApplicationRunLineMarkerProvider.java | 27 +- .../runners/ProcessProxyFactoryImpl.java | 6 + .../com/intellij/psi/util/PsiMethodUtil.java | 35 ++- .../rt/execution/application/AppMainV2.java | 54 +++- .../navigation/RunLineMarkerJava21Test.java | 48 +++- .../navigation/RunLineMarkerJava22Test.java | 236 ++++++++++++++++++ 6 files changed, 374 insertions(+), 32 deletions(-) create mode 100644 java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/RunLineMarkerJava22Test.java diff --git a/java/execution/impl/src/com/intellij/execution/application/ApplicationRunLineMarkerProvider.java b/java/execution/impl/src/com/intellij/execution/application/ApplicationRunLineMarkerProvider.java index 96240972f18e..0a8fc7fd9885 100644 --- a/java/execution/impl/src/com/intellij/execution/application/ApplicationRunLineMarkerProvider.java +++ b/java/execution/impl/src/com/intellij/execution/application/ApplicationRunLineMarkerProvider.java @@ -9,10 +9,12 @@ import com.intellij.icons.AllIcons; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.util.registry.Registry; +import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; import com.intellij.psi.util.PsiMethodUtil; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -24,18 +26,21 @@ import java.util.function.Function; import java.util.stream.Collectors; public class ApplicationRunLineMarkerProvider extends RunLineMarkerContributor { private final static Comparator mainCandidateComparator = (o1, o2) -> { + boolean isO1Static = o1.hasModifierProperty(PsiModifier.STATIC); boolean isO2Static = o2.hasModifierProperty(PsiModifier.STATIC); + int o1Parameters = o1.getParameterList().getParametersCount(); + int o2Parameters = o2.getParameterList().getParametersCount(); + boolean is22PreviewOrLater = PsiUtil.getLanguageLevel(o1).isAtLeast(LanguageLevel.JDK_22_PREVIEW); + + if (is22PreviewOrLater) { + return Integer.compare(o2Parameters, o1Parameters); + } + + //only for java 21 preview if(isO1Static == isO2Static) { - int o1Parameters = o1.getParameterList().getParametersCount(); - int o2Parameters = o2.getParameterList().getParametersCount(); - - if (o1Parameters == o2Parameters) { - return 0; - } else { - return Integer.compare(o2Parameters, o1Parameters); - } + return Integer.compare(o2Parameters, o1Parameters); } else if(isO1Static) { return -1; } else { @@ -60,7 +65,11 @@ public class ApplicationRunLineMarkerProvider extends RunLineMarkerContributor { PsiClass containingClass = method.getContainingClass(); if (!(containingClass instanceof PsiImplicitClass) && PsiTreeUtil.getParentOfType(containingClass, PsiImplicitClass.class) != null) return null; if (containingClass == null || PsiUtil.isLocalOrAnonymousClass(containingClass)) return null; - if (containingClass.isInterface() && !method.hasModifierProperty(PsiModifier.STATIC)) return null; + PsiMethod[] constructors = containingClass.getConstructors(); + if (!method.hasModifierProperty(PsiModifier.STATIC) && constructors.length != 0 && !ContainerUtil.exists(constructors, method1 -> method1.getParameterList().isEmpty())) { + return null; + } + if (containingClass.hasModifierProperty(PsiModifier.ABSTRACT) && !method.hasModifierProperty(PsiModifier.STATIC)) return null; Optional mainMethod = Arrays.stream(containingClass.getMethods()) .filter(m -> "main".equals(m.getName()) && PsiMethodUtil.isMainMethod(m)) diff --git a/java/execution/impl/src/com/intellij/execution/runners/ProcessProxyFactoryImpl.java b/java/execution/impl/src/com/intellij/execution/runners/ProcessProxyFactoryImpl.java index 2e24ab48229f..9234e8506441 100644 --- a/java/execution/impl/src/com/intellij/execution/runners/ProcessProxyFactoryImpl.java +++ b/java/execution/impl/src/com/intellij/execution/runners/ProcessProxyFactoryImpl.java @@ -51,6 +51,12 @@ public final class ProcessProxyFactoryImpl extends ProcessProxyFactory { vmParametersList.defineProperty(AppMainV2.LAUNCHER_PORT_NUMBER, port); vmParametersList.defineProperty(AppMainV2.LAUNCHER_BIN_PATH, binPath); + boolean isJava21preview = JavaSdkVersion.JDK_21.equals(jdkVersion) && + javaParameters.getVMParametersList().getParameters().contains(JavaParameters.JAVA_ENABLE_PREVIEW_PROPERTY); + if (isJava21preview) { + vmParametersList.defineProperty(AppMainV2.LAUNCHER_USE_JDK_21_PREVIEW, Boolean.toString(isJava21preview)); + } + javaParameters.getProgramParametersList().prepend(mainClass); javaParameters.setMainClass(AppMainV2.class.getName()); } diff --git a/java/java-psi-api/src/com/intellij/psi/util/PsiMethodUtil.java b/java/java-psi-api/src/com/intellij/psi/util/PsiMethodUtil.java index eb1e797cd0b3..8a0e24a68725 100644 --- a/java/java-psi-api/src/com/intellij/psi/util/PsiMethodUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/util/PsiMethodUtil.java @@ -5,7 +5,7 @@ import com.intellij.codeInsight.runner.JavaMainMethodProvider; import com.intellij.openapi.util.Condition; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; -import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public final class PsiMethodUtil { @@ -33,9 +33,18 @@ public final class PsiMethodUtil { @Nullable private static PsiMethod findMainMethod(final PsiMethod[] mainMethods, PsiClass aClass) { for (final PsiMethod mainMethod : mainMethods) { + if (mainMethod.hasModifierProperty(PsiModifier.ABSTRACT)) { + continue; + } + if (aClass.hasModifierProperty(PsiModifier.ABSTRACT) && !mainMethod.hasModifierProperty(PsiModifier.STATIC)) { + continue; + } PsiClass containingClass = mainMethod.getContainingClass(); if (containingClass != null && containingClass != aClass) { - if (containingClass.isInterface() && PsiUtil.getLanguageLevel(containingClass).isLessThan(LanguageLevel.JDK_21_PREVIEW)) { + if (containingClass.isInterface() && !instanceMainMethodsEnabled(containingClass)) { + continue; + } + if (mainMethod.hasModifierProperty(PsiModifier.STATIC) && !inheritedStaticMainEnabled(containingClass)) { continue; } } @@ -44,6 +53,18 @@ public final class PsiMethodUtil { return null; } + private static boolean instanceMainMethodsEnabled(@NotNull PsiElement psiElement) { + LanguageLevel languageLevel = PsiUtil.getLanguageLevel(psiElement); + boolean is21Preview = languageLevel.equals(LanguageLevel.JDK_21_PREVIEW); + boolean is22PreviewOrOlder = languageLevel.isAtLeast(LanguageLevel.JDK_22_PREVIEW); + return is21Preview || is22PreviewOrOlder; + } + + private static boolean inheritedStaticMainEnabled(@NotNull PsiElement psiElement) { + LanguageLevel languageLevel = PsiUtil.getLanguageLevel(psiElement); + return languageLevel.isAtLeast(LanguageLevel.JDK_22_PREVIEW); + } + /** * ATTENTION: does not check the method name equals "main" * @@ -54,14 +75,10 @@ public final class PsiMethodUtil { if (method == null || method.getContainingClass() == null) return false; if (!PsiTypes.voidType().equals(method.getReturnType())) return false; final PsiParameter[] parameters = method.getParameterList().getParameters(); - if (PsiUtil.getLanguageLevel(method).isAtLeast(LanguageLevel.JDK_21_PREVIEW)) { + if (instanceMainMethodsEnabled(method)) { if (!method.hasModifierProperty(PsiModifier.PUBLIC) && !method.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) && !method.hasModifierProperty(PsiModifier.PROTECTED)) return false; - PsiMethod[] constructors = method.getContainingClass().getConstructors(); - if (constructors.length != 0 && !ContainerUtil.exists(constructors, method1 -> method1.getParameterList().isEmpty())) { - return false; - } if (parameters.length == 1) { return isJavaLangStringArray(parameters[0]); } @@ -93,8 +110,6 @@ public final class PsiMethodUtil { @Nullable public static PsiMethod findMainInClass(final PsiClass aClass) { if (!MAIN_CLASS.value(aClass)) return null; - PsiMethod method = findMainMethod(aClass); - if (method != null && !method.hasModifierProperty(PsiModifier.STATIC) && aClass.isInterface()) return null; - return method; + return findMainMethod(aClass); } } diff --git a/java/java-runtime/src/com/intellij/rt/execution/application/AppMainV2.java b/java/java-runtime/src/com/intellij/rt/execution/application/AppMainV2.java index b1c085b70596..62b6453c7209 100644 --- a/java/java-runtime/src/com/intellij/rt/execution/application/AppMainV2.java +++ b/java/java-runtime/src/com/intellij/rt/execution/application/AppMainV2.java @@ -18,6 +18,7 @@ import java.util.*; public final class AppMainV2 { public static final String LAUNCHER_PORT_NUMBER = "idea.launcher.port"; public static final String LAUNCHER_BIN_PATH = "idea.launcher.bin.path"; + public static final String LAUNCHER_USE_JDK_21_PREVIEW = "idea.launcher.use.21.preview"; private static native void triggerControlBreak(); @@ -90,7 +91,7 @@ public final class AppMainV2 { Method m = findMethodToRun(appClass); if (m == null) { try { - // left for compatibility reasons - before Java 21 it was possible to call the static main method placed in the superclass + // left for compatibility reasons and as a fallback m = appClass.getMethod("main", String[].class); } catch (NoSuchMethodException e) { if (!startJavaFXApplication(params, appClass)) { @@ -131,13 +132,24 @@ public final class AppMainV2 { } } + private enum MainMethodSearchMode{ + ALL_METHOD, STATIC_METHOD, NON_STATIC_METHOD + } /** - * @param staticMode searches for static only if true and for instance only if false + * Retrieves the status of the main method. + * + * @param method method to check + * @param mode indicates which methods are considered + * @return the status of the main method. Can be one of the following: + * - MainMethodStatus.NotMain: the method is not the main method + * - MainMethodStatus.WithArgs: the method is the main method and accepts an array of strings as parameter + * - MainMethodStatus.WithoutArgs: the method is the main method and does not accept any parameters */ - private static MainMethodStatus getMainMethodStatus(Method method, boolean staticMode) { + private static MainMethodStatus getMainMethodStatus(Method method, MainMethodSearchMode mode) { if ("main".equals(method.getName()) ) { if (!Modifier.isPrivate(method.getModifiers())) { - if (staticMode == Modifier.isStatic(method.getModifiers())) { + if (mode == MainMethodSearchMode.ALL_METHOD || + (mode == MainMethodSearchMode.STATIC_METHOD && Modifier.isStatic(method.getModifiers()))) { Class[] parameterTypes = method.getParameterTypes(); if (parameterTypes.length == 1 && parameterTypes[0] == String[].class) { return MainMethodStatus.WithArgs; @@ -151,15 +163,32 @@ public final class AppMainV2 { return MainMethodStatus.NotMain; } + /** + * @return true if the Java version is "21", false otherwise, because + * logic java 21 preview and other versions are not compatible + */ + private static boolean isJava21Preview() { + try { + return Boolean.parseBoolean(System.getProperty(LAUNCHER_USE_JDK_21_PREVIEW)); + } + catch (Throwable e) { + return false; + } + } + private static Method findMethodToRun(Class aClass) { + boolean java21Preview = isJava21Preview(); Method methodWithoutArgsCandidate = null; // static main methods may be only in this class - for (Method declaredMethod : aClass.getDeclaredMethods()) { - MainMethodStatus status = getMainMethodStatus(declaredMethod, true); - if (status == MainMethodStatus.WithArgs) { - return declaredMethod; - } else if (status == MainMethodStatus.WithoutArgs) { - methodWithoutArgsCandidate = declaredMethod; + if (java21Preview) { + for (Method declaredMethod : aClass.getDeclaredMethods()) { + MainMethodStatus status = getMainMethodStatus(declaredMethod, MainMethodSearchMode.STATIC_METHOD); + if (status == MainMethodStatus.WithArgs) { + return declaredMethod; + } + else if (status == MainMethodStatus.WithoutArgs && methodWithoutArgsCandidate == null) { + methodWithoutArgsCandidate = declaredMethod; + } } } @@ -174,10 +203,11 @@ public final class AppMainV2 { Class last = classesToVisit.removeLast(); Method[] declaredMethods = last.getDeclaredMethods(); for (Method method : declaredMethods) { - MainMethodStatus status = getMainMethodStatus(method, false); + MainMethodStatus status = getMainMethodStatus(method, java21Preview ? MainMethodSearchMode.NON_STATIC_METHOD : MainMethodSearchMode.ALL_METHOD); if (status == MainMethodStatus.WithArgs) { return method; - } else if (status == MainMethodStatus.WithoutArgs) { + } + else if (status == MainMethodStatus.WithoutArgs && methodWithoutArgsCandidate == null) { methodWithoutArgsCandidate = method; } } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/RunLineMarkerJava21Test.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/RunLineMarkerJava21Test.java index c0a5ea77c3f1..523a66f53ab2 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/RunLineMarkerJava21Test.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/RunLineMarkerJava21Test.java @@ -122,7 +122,7 @@ public class RunLineMarkerJava21Test extends LightJavaCodeInsightFixtureTestCase } public void testInstanceMainMethodInSuperInterface() { - myFixture.addClass("public interface B { void main() {} }"); + myFixture.addClass("public interface B { default void main() {} }"); IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_21_PREVIEW, () -> { myFixture.configureByText("MainTest.java", """ class A implements B {} @@ -132,6 +132,52 @@ public class RunLineMarkerJava21Test extends LightJavaCodeInsightFixtureTestCase }); } + public void testStaticMainMethodInSuperInterface() { + myFixture.addClass("public interface B { static void main() {} }"); + IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_21_PREVIEW, () -> { + myFixture.configureByText("MainTest.java", """ + class A implements B {} + """); + List marks = myFixture.findAllGutters(); + assertEquals(0, marks.size()); + }); + } + + public void testAbstractInstanceMainMethodInSuperInterface() { + myFixture.addClass("public interface B { void main(); }"); + IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_22_PREVIEW, () -> { + myFixture.configureByText("MainTest.java", """ + abstract class A implements B {} + """); + List marks = myFixture.findAllGutters(); + assertEquals(0, marks.size()); + }); + } + + public void testAbstractClass() { + IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_22_PREVIEW, () -> { + myFixture.configureByText("MainTest.java", """ + abstract class A { + void main(){}; + } + """); + List marks = myFixture.findAllGutters(); + assertEquals(0, marks.size()); + }); + } + + public void testInterface() { + IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_22_PREVIEW, () -> { + myFixture.configureByText("MainTest.java", """ + interface A { + default void main(){}; + } + """); + List marks = myFixture.findAllGutters(); + assertEquals(0, marks.size()); + }); + } + public void testInstanceMainMethodInInterface() { IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_21_PREVIEW, () -> { myFixture.configureByText("Run.java", """ diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/RunLineMarkerJava22Test.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/RunLineMarkerJava22Test.java new file mode 100644 index 000000000000..f28985e0e579 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/RunLineMarkerJava22Test.java @@ -0,0 +1,236 @@ +// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.java.codeInsight.navigation; + +import com.intellij.codeInsight.daemon.GutterMark; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.testFramework.IdeaTestUtil; +import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase; + +import java.util.List; + +public class RunLineMarkerJava22Test extends LightJavaCodeInsightFixtureTestCase { + + public void testImplicitAllowsNonStatic() { + IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_22_PREVIEW, () -> { + myFixture.configureByText("MainTest.java", """ + void main() { + } + """); + List marks = myFixture.findGuttersAtCaret(); + assertEquals(1, marks.size()); + }); + } + + public void testImplicitNotAllowsNonStatic() { + IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_22, () -> { + myFixture.configureByText("MainTest.java", """ + void main() { + } + """); + List marks = myFixture.findGuttersAtCaret(); + assertEquals(0, marks.size()); + }); + } + + public void testClassWithConstructorWithoutParamsAndInstanceMainIsAllowed() { + IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_22_PREVIEW, () -> { + myFixture.configureByText("MainTest.java", """ + public class A { + A() {} + public void main() {} + } + """); + List marks = myFixture.findGuttersAtCaret(); + assertEquals(1, marks.size()); + }); + } + + public void testClassWithConstructorWithoutParamsAndInstanceMainIsNotAllowed() { + IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_22, () -> { + myFixture.configureByText("MainTest.java", """ + public class A { + A() {} + public void main() {} + } + """); + List marks = myFixture.findGuttersAtCaret(); + assertEquals(0, marks.size()); + }); + } + + public void testClassWithDefaultConstructorParamsAndInstanceMainIsAllowed() { + IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_22_PREVIEW, () -> { + myFixture.configureByText("MainTest.java", """ + public class A { + public void main() {} + } + """); + List marks = myFixture.findGuttersAtCaret(); + assertEquals(1, marks.size()); + }); + } + + public void testClassWithConstructorWithParametersAndInstanceMethodIsNotEntryPoint() { + IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_22_PREVIEW, () -> { + myFixture.configureByText("MainTest.java", """ + public class A { + A(String s) {} + public void main() {} + } + """); + List marks = myFixture.findGuttersAtCaret(); + assertEquals(0, marks.size()); + }); + } + + public void testMainInsideInnerClassInImplicitClassHasNoGutter() { + IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_22_PREVIEW, () -> { + myFixture.configureByText("MainTest.java", """ + void foo() { + } + + public class A { + public void main() {} + } + """); + List marks = myFixture.findGuttersAtCaret(); + assertEquals(0, marks.size()); + }); + } + + public void testStaticWithParameterHasHigherPriority() { + IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_22_PREVIEW, () -> { + myFixture.configureByText("MainTest.java", """ + static void main() {} + static void main(String[] args) {} + """); + List marks = myFixture.findAllGutters(); + assertEquals(1, marks.size()); + assertEmpty(myFixture.findGuttersAtCaret()); + }); + } + + public void testStaticWithNoParametersHasHigherPriorityThanInstance() { + IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_22_PREVIEW, () -> { + myFixture.configureByText("MainTest.java", """ + static void main() {} + void main(String[] args) {} + """); + List marks = myFixture.findAllGutters(); + assertEquals(1, marks.size()); + assertNotEmpty(myFixture.findGuttersAtCaret()); + }); + } + + public void testInstanceWithParameterHasHigherPriority() { + IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_22_PREVIEW, () -> { + myFixture.configureByText("MainTest.java", """ + void main() {} + void main(String[] args) {} + """); + List marks = myFixture.findAllGutters(); + assertEquals(1, marks.size()); + assertEmpty(myFixture.findGuttersAtCaret()); + }); + } + + public void testInstanceMainMethodInSuperClass() { + myFixture.addClass("public class B { void main() {} }"); + IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_22_PREVIEW, () -> { + myFixture.configureByText("MainTest.java", """ + class A extends B {} + """); + List marks = myFixture.findAllGutters(); + assertEquals(1, marks.size()); + }); + } + + public void testInstanceMainMethodInSuperInterface() { + myFixture.addClass("public interface B { default void main() {} }"); + IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_22_PREVIEW, () -> { + myFixture.configureByText("MainTest.java", """ + class A implements B {} + """); + List marks = myFixture.findAllGutters(); + assertEquals(1, marks.size()); + }); + } + + public void testStaticMainMethodInSuperInterface() { + myFixture.addClass("public interface B { static void main() {} }"); + IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_22_PREVIEW, () -> { + myFixture.configureByText("MainTest.java", """ + class A implements B {} + """); + List marks = myFixture.findAllGutters(); + assertEquals(1, marks.size()); + }); + } + + public void testAbstractInstanceMainMethodInSuperInterface() { + myFixture.addClass("public interface B { void main(); }"); + IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_22_PREVIEW, () -> { + myFixture.configureByText("MainTest.java", """ + abstract class A implements B {} + """); + List marks = myFixture.findAllGutters(); + assertEquals(0, marks.size()); + }); + } + + public void testAbstractClass() { + IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_22_PREVIEW, () -> { + myFixture.configureByText("MainTest.java", """ + abstract class A { + void main(){}; + } + """); + List marks = myFixture.findAllGutters(); + assertEquals(0, marks.size()); + }); + } + + public void testInterface() { + IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_22_PREVIEW, () -> { + myFixture.configureByText("MainTest.java", """ + interface A { + default void main(){}; + } + """); + List marks = myFixture.findAllGutters(); + assertEquals(0, marks.size()); + }); + } + + public void testInstanceMainMethodInInterface() { + IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_22_PREVIEW, () -> { + myFixture.configureByText("Run.java", """ + interface Run { + public default void main(String[] args) { + System.out.println("Hello from default!"); + } + } + """); + List marks = myFixture.findAllGutters(); + assertEmpty(marks); + }); + } + + public void testTwoStaticMainMethods() { + IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_22_PREVIEW, () -> { + myFixture.configureByText("Run.java", """ + class Main { + public static void main(String[] args) { + System.out.println("main with parameters"); + } + + static void main() { + System.out.println("main without parameters"); + } + } + """); + List marks = myFixture.findAllGutters(); + assertEquals(2, marks.size()); // class and one method + }); + } +}