[java, completion] generate a provider method while using jigsaw services IDEA-347698

GitOrigin-RevId: 146fa445dcb5426eba5d8fa1d4e1a61d892cee18
This commit is contained in:
Aleksey Dobrynin
2024-02-28 12:19:51 +01:00
committed by intellij-monorepo-bot
parent 9760eeb0e6
commit 3d708aa8d7
65 changed files with 881 additions and 0 deletions

View File

@@ -1383,6 +1383,7 @@
implementationClass="com.intellij.codeInsight.completion.JShellCompletionContributor"/>
<completion.contributor language="JAVA" id="jvmLogger" implementationClass="com.intellij.codeInsight.completion.JvmLoggerCompletionContributor"/>
<completion.contributor language="JAVA" id="jigsaw" implementationClass="com.intellij.codeInsight.completion.JigsawCompletionContributor"/>
<weigher implementationClass="com.intellij.codeInsight.completion.LoggerWeigher" key="completion" id="logger"/>
<completion.confidence language="JAVA" implementationClass="com.intellij.psi.impl.source.resolve.reference.impl.JavaReflectionCompletionConfidence" id="javaReflection" />

View File

@@ -0,0 +1,46 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.completion;
import com.intellij.codeInsight.template.JavaCodeContextType;
import com.intellij.codeInsight.template.TemplateActionContext;
import com.intellij.codeInsight.template.impl.TemplateContextTypes;
import com.intellij.codeInspection.jigsaw.JigsawUtil;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.patterns.PsiJavaPatterns;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiIdentifier;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;
public class JigsawCompletionContributor extends CompletionContributor {
public JigsawCompletionContributor() {
extend(CompletionType.BASIC, PsiJavaPatterns.psiElement(PsiIdentifier.class)
.inside(PsiClass.class)
.withLanguage(JavaLanguage.INSTANCE), new CompletionProvider<>() {
@Override
public void addCompletions(@NotNull CompletionParameters parameters,
@NotNull ProcessingContext context,
@NotNull CompletionResultSet resultSet) {
TemplateActionContext templateActionContext = TemplateActionContext.create(parameters.getOriginalFile(),
parameters.getEditor(),
parameters.getOffset() - 1,
parameters.getOffset() - 1, false);
JavaCodeContextType declaration = TemplateContextTypes.getByClass(JavaCodeContextType.Declaration.class);
if (!declaration.isInContext(templateActionContext)) return;
PsiElement element = parameters.getPosition();
PsiClass targetClass = PsiTreeUtil.getParentOfType(element, PsiClass.class);
if (!JigsawUtil.checkProviderMethodAccessible(targetClass)) return;
String className = targetClass.getName();
if (className == null) return;
resultSet.addElement(new JigsawProviderLookupElement(targetClass));
}
}
);
}
}

View File

@@ -0,0 +1,34 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.completion;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementPresentation;
import com.intellij.codeInspection.jigsaw.JigsawApiConstants;
import com.intellij.codeInspection.jigsaw.JigsawUtil;
import com.intellij.psi.PsiClass;
import org.jetbrains.annotations.NotNull;
public class JigsawProviderLookupElement extends LookupElement {
private final PsiClass myPsiClass;
public JigsawProviderLookupElement(PsiClass aClass) { myPsiClass = aClass; }
@Override
public @NotNull String getLookupString() {
return JigsawApiConstants.PROVIDER;
}
@Override
public void renderElement(@NotNull LookupElementPresentation presentation) {
super.renderElement(presentation);
presentation.setTypeText("provider() method declaration");
}
@Override
public void handleInsert(@NotNull InsertionContext context) {
int selectionLength = context.getSelectionEndOffset() - context.getStartOffset();
JigsawUtil.addProviderMethod(myPsiClass, context.getEditor(), context.getStartOffset(),
(offset, content) -> context.getDocument().replaceString(offset, offset + selectionLength,
content));
}
}

View File

@@ -0,0 +1,56 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.generation;
import com.intellij.codeInsight.CodeInsightActionHandler;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInspection.jigsaw.JigsawUtil;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiJavaFile;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class GenerateProviderMethodHandler implements CodeInsightActionHandler {
@Override
public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
if (!(file instanceof PsiJavaFile)) return;
int offset = editor.getCaretModel().getOffset();
PsiElement context = getContext(file.findElementAt(offset));
if (context == null) return;
PsiClass targetClass = PsiTreeUtil.getParentOfType(context, PsiClass.class, false);
if (!JigsawUtil.checkProviderMethodAccessible(targetClass)) return;
if (!FileModificationService.getInstance().preparePsiElementsForWrite(targetClass)) return;
WriteCommandAction.writeCommandAction(project, file)
.run(() -> JigsawUtil.addProviderMethod(targetClass, editor, getOffset(context, offset),
(currentOffset, content) -> editor.getDocument().insertString(currentOffset, content)));
}
/**
* Retrieves the context of a given PsiElement.
* The context refers to the nearest parent that is a PsiClass.
*
* @param context the initial PsiElement
* @return the context PsiElement, or null if no context is found
*/
@Nullable
private static PsiElement getContext(@Nullable PsiElement context) {
while (context != null && !(context.getParent() instanceof PsiClass)) {
context = context.getParent();
}
return context;
}
private static int getOffset(@NotNull PsiElement context, int defaultOffset) {
PsiElement child = context.getFirstChild();
return child != null ? child.getTextOffset() : defaultOffset;
}
}

View File

@@ -0,0 +1,20 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.generation.actions;
import com.intellij.codeInsight.generation.GenerateProviderMethodHandler;
import com.intellij.codeInspection.jigsaw.JigsawUtil;
import com.intellij.openapi.project.DumbAware;
import com.intellij.psi.PsiClass;
public class GenerateProviderMethodAction extends BaseGenerateAction implements DumbAware {
public GenerateProviderMethodAction() {
super(new GenerateProviderMethodHandler());
}
@Override
protected boolean isValidForClass(PsiClass targetClass) {
if (!super.isValidForClass(targetClass)) return false;
if (!targetClass.isWritable()) return false;
return JigsawUtil.checkProviderMethodAccessible(targetClass);
}
}

View File

@@ -0,0 +1,6 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection.jigsaw;
public interface JigsawApiConstants {
String PROVIDER = "provider";
}

View File

@@ -0,0 +1,69 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection.jigsaw;
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil;
import com.intellij.lang.jvm.JvmMethod;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.impl.light.LightJavaModule;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.BiConsumer;
public final class JigsawUtil {
private JigsawUtil() { }
public static void addProviderMethod(@NotNull PsiClass targetClass,
@NotNull Editor editor,
int startOffset,
@NotNull BiConsumer<Integer, String> setMethod) {
String className = targetClass.getName();
if (className == null) return;
String methodStringBeforeCursor = "public static " + className + " " + JigsawApiConstants.PROVIDER + "() {" +
"return new " + className + "(";
String methodStringAfterCursor = ");}";
Document document = editor.getDocument();
PsiDocumentManager documentManager = PsiDocumentManager.getInstance(targetClass.getProject());
setMethod.accept(startOffset, methodStringBeforeCursor + methodStringAfterCursor);
editor.getCaretModel().moveToOffset(startOffset + methodStringBeforeCursor.length());
documentManager.commitDocument(document);
documentManager.doPostponedOperationsAndUnblockDocument(document);
PsiFile psiFile = documentManager.getPsiFile(document);
if (psiFile != null) {
CodeStyleManager.getInstance(targetClass.getProject())
.reformatText(psiFile, startOffset, startOffset + methodStringBeforeCursor.length() + methodStringAfterCursor.length() + 1);
}
}
@Contract("null -> false")
public static boolean checkProviderMethodAccessible(@Nullable PsiClass targetClass) {
if (targetClass == null || targetClass.getName() == null) return false;
JvmMethod[] methods = targetClass.findMethodsByName(JigsawApiConstants.PROVIDER);
for (JvmMethod method : methods) {
if (!method.hasParameters()) return false;
}
PsiJavaModule descriptor = JavaModuleGraphUtil.findDescriptorByElement(targetClass.getContainingFile().getOriginalFile());
if (descriptor == null || descriptor instanceof LightJavaModule) return false;
Iterable<PsiProvidesStatement> providers = descriptor.getProvides();
for (PsiProvidesStatement provider : providers) {
PsiReferenceList implementations = provider.getImplementationList();
if (implementations == null) continue;
for (PsiClassType type : implementations.getReferencedTypes()) {
if (targetClass.isEquivalentTo(type.resolve())) {
return true;
}
}
}
return false;
}
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/providers.iml" filepath="$PROJECT_DIR$/providers.iml" />
<module fileurl="file://$PROJECT_DIR$/sub/sub.iml" filepath="$PROJECT_DIR$/sub/sub.iml" />
</modules>
</component>
</project>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="sub" />
</component>
</module>

View File

@@ -0,0 +1,18 @@
import org.jetbrains.providers.MyProviderImpl;
import org.jetbrains.providers.MySuperClass.MySubProviderImpl;
import org.jetbrains.providers.MyRecord;
import org.jetbrains.providers.WithProvider;
import org.jetbrains.providers.WrongPlace;
import org.jetbrains.providers.WrongPlace2;
import org.jetbraons.api.MyProviderInterface;
module my.providers {
uses MyProviderInterface;
requires sub.module;
provides MyProviderInterface with MyProviderImpl,
MySubProviderImpl,
MyRecord,
WithProvider,
WrongPlace,
WrongPlace2;
}

View File

@@ -0,0 +1,9 @@
package org.jetbrains.providers;
import org.jetbraons.api.MyProviderInterface;
public class MyProviderImpl implements MyProviderInterface {
public static MyProviderImpl provider() {
return new MyProviderImpl(<caret>);
}
}

View File

@@ -0,0 +1,9 @@
package org.jetbrains.providers;
import org.jetbraons.api.MyProviderInterface;
public record MyRecord(String a) implements MyProviderInterface {
public static MyRecord provider() {
return new MyRecord(<caret>);
}
}

View File

@@ -0,0 +1,15 @@
package org.jetbrains.providers;
public class MySuperClass {
public static class MySubProviderImpl extends MyProviderImpl {
public MySubProviderImpl() {
}
public static MySubProviderImpl provider() {
return new MySubProviderImpl(<caret>);
}
public static void method() {
}
}
}

View File

@@ -0,0 +1,7 @@
package org.jetbrains.providers;
import org.jetbraons.api.MyProviderInterface;
public class SimpleClass implements MyProviderInterface {
pr<caret>
}

View File

@@ -0,0 +1,10 @@
package org.jetbrains.providers;
import org.jetbraons.api.MyProviderInterface;
public class WithProvider implements MyProviderInterface {
pr<caret>
public static WithProvider provider() {
return new WithProvider();
}
}

View File

@@ -0,0 +1,17 @@
package org.jetbrains.providers;
import org.jetbraons.api.MyProviderInterface;
public class WrongPlace implements MyProviderInterface {
public static void method() {
pr<caret>
}
private static void program() {
}
private static void process() {
}
}

View File

@@ -0,0 +1,9 @@
package org.jetbrains.providers;
import org.jetbraons.api.MyProviderInterface;
public class WrongPlace2 implements MyProviderInterface {
public static void pr<caret> method() {
}
}

View File

@@ -0,0 +1,3 @@
module sub.module {
exports org.jetbraons.api;
}

View File

@@ -0,0 +1,4 @@
package org.jetbraons.api;
public interface MyProviderInterface {
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/providers.iml" filepath="$PROJECT_DIR$/providers.iml" />
<module fileurl="file://$PROJECT_DIR$/sub/sub.iml" filepath="$PROJECT_DIR$/sub/sub.iml" />
</modules>
</component>
</project>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="sub" />
</component>
</module>

View File

@@ -0,0 +1,18 @@
import org.jetbrains.providers.MyProviderImpl;
import org.jetbrains.providers.MySuperClass.MySubProviderImpl;
import org.jetbrains.providers.MyRecord;
import org.jetbrains.providers.WithProvider;
import org.jetbrains.providers.WrongPlace;
import org.jetbrains.providers.WrongPlace2;
import org.jetbraons.api.MyProviderInterface;
module my.providers {
uses MyProviderInterface;
requires sub.module;
provides MyProviderInterface with MyProviderImpl,
MySubProviderImpl,
MyRecord,
WithProvider,
WrongPlace,
WrongPlace2;
}

View File

@@ -0,0 +1,7 @@
package org.jetbrains.providers;
import org.jetbraons.api.MyProviderInterface;
public class MyProviderImpl implements MyProviderInterface {
pr<caret>
}

View File

@@ -0,0 +1,7 @@
package org.jetbrains.providers;
import org.jetbraons.api.MyProviderInterface;
public record MyRecord(String a) implements MyProviderInterface {
pr<caret>
}

View File

@@ -0,0 +1,11 @@
package org.jetbrains.providers;
public class MySuperClass {
public static class MySubProviderImpl extends MyProviderImpl {
public MySubProviderImpl() {
}
pr<caret>public static void method() {
}
}
}

View File

@@ -0,0 +1,7 @@
package org.jetbrains.providers;
import org.jetbraons.api.MyProviderInterface;
public class SimpleClass implements MyProviderInterface {
pr<caret>
}

View File

@@ -0,0 +1,10 @@
package org.jetbrains.providers;
import org.jetbraons.api.MyProviderInterface;
public class WithProvider implements MyProviderInterface {
pr<caret>
public static WithProvider provider() {
return new WithProvider();
}
}

View File

@@ -0,0 +1,17 @@
package org.jetbrains.providers;
import org.jetbraons.api.MyProviderInterface;
public class WrongPlace implements MyProviderInterface {
public static void method() {
pr<caret>
}
private static void program() {
}
private static void process() {
}
}

View File

@@ -0,0 +1,9 @@
package org.jetbrains.providers;
import org.jetbraons.api.MyProviderInterface;
public class WrongPlace2 implements MyProviderInterface {
public static void pr<caret> method() {
}
}

View File

@@ -0,0 +1,3 @@
module sub.module {
exports org.jetbraons.api;
}

View File

@@ -0,0 +1,4 @@
package org.jetbraons.api;
public interface MyProviderInterface {
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/providers.iml" filepath="$PROJECT_DIR$/providers.iml" />
<module fileurl="file://$PROJECT_DIR$/sub/sub.iml" filepath="$PROJECT_DIR$/sub/sub.iml" />
</modules>
</component>
</project>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="sub" />
</component>
</module>

View File

@@ -0,0 +1,11 @@
import org.jetbrains.providers.MyProviderImpl;
import org.jetbrains.providers.MySuperClass.MySubProviderImpl;
import org.jetbrains.providers.MyRecord;
import org.jetbrains.providers.WithProvider;
import org.jetbraons.api.MyProviderInterface;
module my.providers {
uses MyProviderInterface;
requires sub.module;
provides MyProviderInterface with MyProviderImpl, MySubProviderImpl, MyRecord, WithProvider;
}

View File

@@ -0,0 +1,9 @@
package org.jetbrains.providers;
import org.jetbraons.api.MyProviderInterface;
public class MyProviderImpl implements MyProviderInterface {
public static MyProviderImpl provider() {
return new MyProviderImpl(<caret>);
}
}

View File

@@ -0,0 +1,9 @@
package org.jetbrains.providers;
import org.jetbraons.api.MyProviderInterface;
public record MyRecord(String a) implements MyProviderInterface {
public static MyRecord provider() {
return new MyRecord(<caret>);
}
}

View File

@@ -0,0 +1,9 @@
package org.jetbrains.providers;
public class MySuperClass {
public static class MySubProviderImpl extends MyProviderImpl {
public static MySubProviderImpl provider() {
return new MySubProviderImpl(<caret>);
}
}
}

View File

@@ -0,0 +1,7 @@
package org.jetbrains.providers;
import org.jetbraons.api.MyProviderInterface;
public class SimpleClass implements MyProviderInterface {
<caret>
}

View File

@@ -0,0 +1,10 @@
package org.jetbrains.providers;
import org.jetbraons.api.MyProviderInterface;
public class WithProvider implements MyProviderInterface {
<caret>
public static WithProvider provider() {
return new WithProvider();
}
}

View File

@@ -0,0 +1,13 @@
package org.jetbrains.providers;
import org.jetbraons.api.MyProviderInterface;
public class WrongPlace implements MyProviderInterface {
private static int a = 0;
public static WrongPlace provider() {
return new WrongPlace(<caret>);
}
public WrongPlace() {}
}

View File

@@ -0,0 +1,3 @@
module sub.module {
exports org.jetbraons.api;
}

View File

@@ -0,0 +1,4 @@
package org.jetbraons.api;
public interface MyProviderInterface {
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/providers.iml" filepath="$PROJECT_DIR$/providers.iml" />
<module fileurl="file://$PROJECT_DIR$/sub/sub.iml" filepath="$PROJECT_DIR$/sub/sub.iml" />
</modules>
</component>
</project>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="sub" />
</component>
</module>

View File

@@ -0,0 +1,12 @@
import org.jetbrains.providers.MyProviderImpl;
import org.jetbrains.providers.MySuperClass.MySubProviderImpl;
import org.jetbrains.providers.MyRecord;
import org.jetbrains.providers.WithProvider;
import org.jetbrains.providers.WrongPlace;
import org.jetbraons.api.MyProviderInterface;
module my.providers {
uses MyProviderInterface;
requires sub.module;
provides MyProviderInterface with MyProviderImpl, MySubProviderImpl, MyRecord, WithProvider, WrongPlace;
}

View File

@@ -0,0 +1,7 @@
package org.jetbrains.providers;
import org.jetbraons.api.MyProviderInterface;
public class MyProviderImpl implements MyProviderInterface {
<caret>
}

View File

@@ -0,0 +1,7 @@
package org.jetbrains.providers;
import org.jetbraons.api.MyProviderInterface;
public record MyRecord(String a) implements MyProviderInterface {
<caret>
}

View File

@@ -0,0 +1,7 @@
package org.jetbrains.providers;
public class MySuperClass {
public static class MySubProviderImpl extends MyProviderImpl {
<caret>
}
}

View File

@@ -0,0 +1,7 @@
package org.jetbrains.providers;
import org.jetbraons.api.MyProviderInterface;
public class SimpleClass implements MyProviderInterface {
<caret>
}

View File

@@ -0,0 +1,10 @@
package org.jetbrains.providers;
import org.jetbraons.api.MyProviderInterface;
public class WithProvider implements MyProviderInterface {
<caret>
public static WithProvider provider() {
return new WithProvider();
}
}

View File

@@ -0,0 +1,9 @@
package org.jetbrains.providers;
import org.jetbraons.api.MyProviderInterface;
public class WrongPlace implements MyProviderInterface {
private static int a = 0;
public WrongPlace() {<caret>}
}

View File

@@ -0,0 +1,3 @@
module sub.module {
exports org.jetbraons.api;
}

View File

@@ -0,0 +1,4 @@
package org.jetbraons.api;
public interface MyProviderInterface {
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,66 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.codeInsight;
import com.intellij.JavaTestUtil;
import com.intellij.codeInsight.completion.LightFixtureCompletionTestCase;
import com.intellij.codeInsight.generation.GenerateProviderMethodHandler;
import com.intellij.java.testFramework.fixtures.MultiModuleProjectDescriptor;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiManager;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.NeedsIndex;
import org.jetbrains.annotations.NotNull;
import java.nio.file.Paths;
@NeedsIndex.SmartMode(reason = "Provider method completion is not supported in the dumb mode")
public class GenerateProviderMethodTest extends LightFixtureCompletionTestCase {
private MultiModuleProjectDescriptor myDescriptor;
@Override
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
return myDescriptor == null
? myDescriptor =
new MultiModuleProjectDescriptor(Paths.get(getTestDataPath()), "providers", null)
: myDescriptor;
}
@Override
protected String getTestDataPath() {
return JavaTestUtil.getJavaTestDataPath() + "/codeInsight/generateProviderMethod";
}
public void testNonModuleClass() {
doTest("src/org/jetbrains/providers/SimpleClass.java");
}
public void testSimpleProvider() {
doTest("src/org/jetbrains/providers/MyProviderImpl.java");
}
public void testWithRecord() {
doTest("src/org/jetbrains/providers/MyRecord.java");
}
public void testWithProvider() {
doTest("src/org/jetbrains/providers/WithProvider.java");
}
public void testSubClass() {
doTest("src/org/jetbrains/providers/MySuperClass.java");
}
public void testWrongPlace() {
doTest("src/org/jetbrains/providers/WrongPlace.java");
}
private void doTest(@NotNull String path) {
VirtualFile file = getModule().getModuleFile().getParent().findFileByRelativePath(path);
myFixture.configureFromExistingVirtualFile(file);
new GenerateProviderMethodHandler().invoke(getProject(), getEditor(), PsiManager.getInstance(getProject()).findFile(file));
checkResultByFile("after/" + path);
}
}

View File

@@ -0,0 +1,93 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.codeInsight.completion;
import com.intellij.JavaTestUtil;
import com.intellij.codeInsight.completion.LightFixtureCompletionTestCase;
import com.intellij.codeInsight.lookup.Lookup;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.impl.LookupImpl;
import com.intellij.java.testFramework.fixtures.MultiModuleProjectDescriptor;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.NeedsIndex;
import org.jetbrains.annotations.NotNull;
import java.nio.file.Paths;
import java.util.Optional;
@NeedsIndex.SmartMode(reason = "Provider method completion is not supported in the dumb mode")
public class ProviderMethodCompletionTest extends LightFixtureCompletionTestCase {
private MultiModuleProjectDescriptor myDescriptor;
@Override
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
return myDescriptor == null
? myDescriptor =
new MultiModuleProjectDescriptor(Paths.get(getTestDataPath()), "providers", null)
: myDescriptor;
}
@Override
protected String getTestDataPath() {
return JavaTestUtil.getJavaTestDataPath() + "/codeInsight/completion/providerMethod";
}
public void testNonModuleClass() {
doTest("src/org/jetbrains/providers/SimpleClass.java", "private",
"protected");
}
public void testSimpleProvider() {
doTest("src/org/jetbrains/providers/MyProviderImpl.java", "provider",
"private",
"protected");
}
public void testWithRecord() {
doTest("src/org/jetbrains/providers/MyRecord.java", "provider",
"private",
"protected");
}
public void testWithProvider() {
doTest("src/org/jetbrains/providers/WithProvider.java", "private",
"protected");
}
public void testSubClass() {
doTest("src/org/jetbrains/providers/MySuperClass.java", "provider",
"private",
"protected");
}
public void testWrongPlace() {
doTest("src/org/jetbrains/providers/WrongPlace.java", "process", "program");
}
public void testWrongPlace2() {
doTest("src/org/jetbrains/providers/WrongPlace2.java");
}
private void doTest(String path, String... names) {
VirtualFile file = getModule().getModuleFile().getParent().findFileByRelativePath(path);
myFixture.configureFromExistingVirtualFile(file);
myFixture.completeBasic();
if (names.length != 0) {
assertStringItems(names);
Optional<LookupElement> item = getLookup().getItems().stream().filter(le -> "provider".equals(le.getLookupString())).findFirst();
if (item.isPresent()) {
myFixture.getLookup().setCurrentItem(item.get());
myFixture.finishLookup(Lookup.NORMAL_SELECT_CHAR);
}
}
else {
LookupImpl lookup = getLookup();
if (lookup != null) {
assertStringItems(names);
}
}
checkResultByFile("after/" + path);
}
}

View File

@@ -237,6 +237,8 @@ action.GenerateGetter.text=Getter
action.GenerateGetter.description=Generate getter
action.GenerateSetter.text=Setter
action.GenerateSetter.description=Generate setter
action.GenerateProviderMethod.text=provider()
action.GenerateProviderMethod.description=Generate provider method
action.GenerateGetterAndSetter.text=Getter and Setter
action.GenerateGetterAndSetter.description=Generate getter and setter
action.GenerateEquals.text=equals() and hashCode()

View File

@@ -288,6 +288,7 @@
<action id="GenerateEquals" class="com.intellij.codeInsight.generation.actions.GenerateEqualsAction"/>
<action id="Actions.ActionsPlugin.GenerateToString" class="org.jetbrains.java.generate.GenerateToStringAction"/>
<action id="GenerateCreateUI" class="com.intellij.codeInsight.generation.actions.GenerateCreateUIAction"/>
<action id="GenerateProviderMethod" class="com.intellij.codeInsight.generation.actions.GenerateProviderMethodAction"/>
<add-to-group group-id="GenerateGroup" anchor="first"/>
</group>