mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 13:02:30 +07:00
[javadoc] IDEA-285556 Support language injection into snippet
Implement updating the content of a snippet tag with the help from a method implementer. A snippet might invoke overriding a method though a completion and when a parent method is inserted it might contain javadoc. In this case it's impossible to recreate a snippet tag because the new text for the tag contains "/**" and "*/" that come from the parent method's javadoc. The implemented method implementer helps to strip a method's javadoc if it's present. Signed-off-by: Nikita Eshkeev <nikita.eshkeev@jetbrains.com> GitOrigin-RevId: 42b315ed2670701e50574a383f0442df69accd35
This commit is contained in:
committed by
intellij-monorepo-bot
parent
715e914e30
commit
436eee6cf4
@@ -163,7 +163,7 @@ public final class OverrideImplementUtil extends OverrideImplementExploreUtil {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PsiMethod decorateMethod(@NotNull PsiClass aClass,
|
||||
public static PsiMethod decorateMethod(@NotNull PsiClass aClass,
|
||||
@NotNull PsiMethod method,
|
||||
boolean toCopyJavaDoc,
|
||||
boolean insertOverrideIfPossible,
|
||||
@@ -292,7 +292,7 @@ public final class OverrideImplementUtil extends OverrideImplementExploreUtil {
|
||||
for (MethodImplementor implementor : getImplementors()) {
|
||||
final GenerationInfo info = implementor.createGenerationInfo(s, mergeIfExists);
|
||||
if (info instanceof PsiGenerationInfo) {
|
||||
@SuppressWarnings({"unchecked"}) final PsiGenerationInfo<PsiMethod> psiGenerationInfo = (PsiGenerationInfo<PsiMethod>)info;
|
||||
@SuppressWarnings("unchecked") final PsiGenerationInfo<PsiMethod> psiGenerationInfo = (PsiGenerationInfo<PsiMethod>)info;
|
||||
return psiGenerationInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.javadoc;
|
||||
|
||||
import com.intellij.codeInsight.MethodImplementor;
|
||||
import com.intellij.codeInsight.generation.GenerateMembersUtil;
|
||||
import com.intellij.codeInsight.generation.GenerationInfo;
|
||||
import com.intellij.codeInsight.generation.OverrideImplementUtil;
|
||||
import com.intellij.codeInsight.generation.PsiGenerationInfo;
|
||||
import com.intellij.lang.injection.InjectedLanguageManager;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiLanguageInjectionHost;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiSubstitutor;
|
||||
import com.intellij.psi.javadoc.PsiSnippetDocTag;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.Consumer;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class SnippetDocTagMethodImplementor implements MethodImplementor {
|
||||
@Override
|
||||
public PsiMethod @NotNull [] getMethodsToImplement(PsiClass aClass) {
|
||||
return PsiMethod.EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiMethod @NotNull [] createImplementationPrototypes(PsiClass inClass,
|
||||
PsiMethod method) throws IncorrectOperationException {
|
||||
final PsiLanguageInjectionHost injectionHost = InjectedLanguageManager.getInstance(inClass.getProject()).getInjectionHost(inClass);
|
||||
if (!(injectionHost instanceof PsiSnippetDocTag)) {
|
||||
return PsiMethod.EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
final PsiClass containingClass = method.getContainingClass();
|
||||
if (containingClass == null) {
|
||||
return PsiMethod.EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
final PsiSubstitutor substitutor = inClass.isInheritor(containingClass, true)
|
||||
? TypeConversionUtil.getSuperClassSubstitutor(containingClass, inClass, PsiSubstitutor.EMPTY)
|
||||
: PsiSubstitutor.EMPTY;
|
||||
final PsiMethod result = GenerateMembersUtil.substituteGenericMethod(method, substitutor, inClass);
|
||||
|
||||
return new PsiMethod[]{result};
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable GenerationInfo createGenerationInfo(PsiMethod method,
|
||||
boolean mergeIfExists) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Consumer<PsiMethod> createDecorator(PsiClass targetClass,
|
||||
final PsiMethod baseMethod,
|
||||
boolean toCopyJavaDoc,
|
||||
boolean insertOverrideIfPossible) {
|
||||
return result -> OverrideImplementUtil.decorateMethod(targetClass, baseMethod, false, insertOverrideIfPossible, result);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,8 @@ import java.util.List;
|
||||
public class JavadocInjector implements MultiHostInjector {
|
||||
|
||||
private static final String LANG_ATTR_KEY = "lang";
|
||||
private static final String SNIPPET_INJECTION_JAVA_HEADER = "class ___JavadocSnippetPlaceholder {\n" +
|
||||
" void ___JavadocSnippetPlaceholderMethod() throws Throwable {\n";
|
||||
|
||||
@Override
|
||||
public void getLanguagesToInject(@NotNull MultiHostRegistrar registrar,
|
||||
@@ -32,8 +34,8 @@ public class JavadocInjector implements MultiHostInjector {
|
||||
|
||||
final Language language = getLanguage(snippet);
|
||||
|
||||
final String prefix = language == JavaLanguage.INSTANCE ? "class ___JavadocSnippetPlaceholder { " : null;
|
||||
final String suffix = language == JavaLanguage.INSTANCE ? " }" : null;
|
||||
final String prefix = language == JavaLanguage.INSTANCE ? SNIPPET_INJECTION_JAVA_HEADER : null;
|
||||
final String suffix = language == JavaLanguage.INSTANCE ? " }}" : null;
|
||||
|
||||
registrar.startInjecting(language)
|
||||
.addPlace(prefix, suffix, snippet, innerRangeStrippingQuotes(snippet))
|
||||
|
||||
@@ -121,6 +121,7 @@
|
||||
implementationClass="com.intellij.psi.impl.source.resolve.reference.impl.manipulators.StringLiteralManipulator"/>
|
||||
<lang.elementManipulator forClass="com.intellij.psi.impl.source.javadoc.PsiSnippetDocTagImpl"
|
||||
implementationClass="com.intellij.psi.impl.source.javadoc.SnippetDocTagManipulator"/>
|
||||
<methodImplementor implementation="com.intellij.codeInsight.javadoc.SnippetDocTagMethodImplementor"/>
|
||||
<superMethodsSearch implementation="com.intellij.psi.impl.search.MethodSuperSearcher"/>
|
||||
<lang.psiAugmentProvider implementation="com.intellij.psi.impl.source.JShellPsiAugmentProvider"/>
|
||||
<codeInsight.containerProvider implementation="com.intellij.codeInsight.JavaContainerProvider" id="JAVA"/>
|
||||
|
||||
@@ -7,9 +7,15 @@ import com.intellij.psi.AbstractElementManipulator;
|
||||
import com.intellij.psi.JavaDocTokenType;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.impl.source.tree.TreeUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.JavaFileCodeStyleFacade;
|
||||
import com.intellij.psi.javadoc.PsiDocComment;
|
||||
import com.intellij.psi.javadoc.PsiSnippetDocTag;
|
||||
import com.intellij.psi.javadoc.PsiSnippetDocTagBody;
|
||||
import com.intellij.psi.javadoc.PsiSnippetDocTagValue;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class SnippetDocTagManipulator extends AbstractElementManipulator<PsiSnippetDocTagImpl> {
|
||||
@@ -18,7 +24,41 @@ public final class SnippetDocTagManipulator extends AbstractElementManipulator<P
|
||||
public PsiSnippetDocTagImpl handleContentChange(@NotNull PsiSnippetDocTagImpl element,
|
||||
@NotNull TextRange range,
|
||||
String newContent) throws IncorrectOperationException {
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(element.getProject());
|
||||
|
||||
final JavaFileCodeStyleFacade codeStyleFacade = JavaFileCodeStyleFacade.forContext(element.getContainingFile());
|
||||
final String newSnippetTagContent = codeStyleFacade.isJavaDocLeadingAsterisksEnabled()
|
||||
? prependAbsentAsterisks(newContent)
|
||||
: newContent;
|
||||
|
||||
final PsiDocComment text = factory.createDocCommentFromText("/**\n" + newSnippetTagContent + "\n*/");
|
||||
final PsiSnippetDocTag snippet = PsiTreeUtil.findChildOfType(text, PsiSnippetDocTag.class);
|
||||
if (snippet == null) {
|
||||
return element;
|
||||
}
|
||||
return (PsiSnippetDocTagImpl)element.replace(snippet);
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
private static @NotNull String prependAbsentAsterisks(@NotNull String input) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
boolean afterNewLine = false;
|
||||
for (char c : input.toCharArray()) {
|
||||
if (c == '\n') {
|
||||
afterNewLine = true;
|
||||
}
|
||||
else if (afterNewLine) {
|
||||
if (c == '*') {
|
||||
afterNewLine = false;
|
||||
}
|
||||
else if (!Character.isWhitespace(c)) {
|
||||
builder.append("* ");
|
||||
afterNewLine = false;
|
||||
}
|
||||
}
|
||||
builder.append(c);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/** {<warning descr="'@snippet' tag is not available at this language level">@snippet</warning> :
|
||||
* Body<EOLError descr="Identifier expected"></EOLError>
|
||||
* Body
|
||||
* }
|
||||
*/
|
||||
class A {
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
/** {<warning descr="'@snippet' tag is not available at this language level">@snippet</warning> :
|
||||
* {
|
||||
* Optional<Integer> v = null;
|
||||
* if (v.isPresent()) {
|
||||
* System.out.println("v: " + v.get());
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
class A {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/** {<warning descr="'@snippet' tag is not available at this language level">@snippet</warning> :
|
||||
* FileInputStream is = new FileInputStream("hello.world");
|
||||
* int r = is.read();
|
||||
* }
|
||||
*/
|
||||
class A {
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/** {<warning descr="'@snippet' tag is not available at this language level">@snippet</warning> :
|
||||
* void f() {}
|
||||
* void f<error descr="';' expected">(</error><error descr="Expression expected">)</error> {}
|
||||
* }
|
||||
*/
|
||||
class A {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
class Main {
|
||||
/**
|
||||
* A simple program.
|
||||
* {@snippet:
|
||||
* class HelloWorld {
|
||||
* @Override
|
||||
* public boolean equals(Object obj) {
|
||||
* return super.equals(obj);
|
||||
* }void f() {}
|
||||
* }
|
||||
*}
|
||||
*/
|
||||
void f() {}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
class Main {
|
||||
/**
|
||||
* A simple program.
|
||||
* {@snippet :
|
||||
* class HelloWorld {
|
||||
* equal<caret>
|
||||
* void f() {}
|
||||
* }
|
||||
*}
|
||||
*/
|
||||
void f() {}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ class Main {
|
||||
* A simple program.
|
||||
* {@snippet :
|
||||
* class HelloWorld {
|
||||
* <caret>
|
||||
* <caret>
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
|
||||
@@ -131,7 +131,7 @@ public class JavadocHighlightingTest extends LightDaemonAnalyzerTestCase {
|
||||
public void testSnippetInstructions() { doTest(); }
|
||||
public void testEmptySnippet() { doTest(); }
|
||||
public void testOnlyEmptyLinesInSnippet() { doTest(); }
|
||||
|
||||
public void testSnippetInstructionsWithUnhandledThrowable() { doTest(); }
|
||||
|
||||
public void testIssueLinksInJavaDoc() {
|
||||
IssueNavigationConfiguration navigationConfiguration = IssueNavigationConfiguration.getInstance(getProject());
|
||||
@@ -182,4 +182,4 @@ public class JavadocHighlightingTest extends LightDaemonAnalyzerTestCase {
|
||||
private void doTest(String testFileName) {
|
||||
doTest(testFileName, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.java.codeInsight.javadoc;
|
||||
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import com.intellij.testFramework.PlatformTestUtil;
|
||||
import com.intellij.testFramework.fixtures.BasePlatformTestCase;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase.JAVA_17;
|
||||
|
||||
public class JavadocCompletionInSnippetTest extends BasePlatformTestCase {
|
||||
|
||||
public void testOverrideMethod() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
private void doTest() {
|
||||
myFixture.configureByFile("before" + getTestName(false) + ".java");
|
||||
myFixture.completeBasic();
|
||||
myFixture.checkResultByFile("after" + getTestName(false) + ".java");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTestDataPath() {
|
||||
return PlatformTestUtil.getCommunityPath().replace(File.separatorChar, '/') + "/java/java-tests/testData/codeInsight/javadoc/snippet/completion/";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected LightProjectDescriptor getProjectDescriptor() {
|
||||
return JAVA_17;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCa
|
||||
import com.intellij.lang.Language;
|
||||
import com.intellij.lang.injection.InjectedLanguageManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.impl.source.tree.injected.MyTestInjector;
|
||||
import com.intellij.psi.javadoc.PsiSnippetDocTag;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
|
||||
@@ -142,11 +142,16 @@ class SmartPsiElementPointerImpl<E extends PsiElement> implements SmartPointerEx
|
||||
boolean forInjected) {
|
||||
SmartPointerElementInfo elementInfo = doCreateElementInfo(manager.getProject(), element, containingFile, forInjected);
|
||||
if (ApplicationManager.getApplication().isUnitTestMode() && !ApplicationManagerEx.isInStressTest()) {
|
||||
PsiElement restored = elementInfo.restoreElement(manager);
|
||||
if (!element.equals(restored)) {
|
||||
PsiElement restoredElement = elementInfo.restoreElement(manager);
|
||||
if (restoredElement == null) {
|
||||
// The problem might be with injection. It's a questionable solution, requires more discussion.
|
||||
elementInfo = doCreateElementInfo(manager.getProject(), element, containingFile, !forInjected);
|
||||
restoredElement = elementInfo.restoreElement(manager);
|
||||
}
|
||||
if (!element.equals(restoredElement)) {
|
||||
// likely cause: PSI having isPhysical==true, but which can't be restored by containing file and range. To fix, make isPhysical return false
|
||||
LOG.error("Cannot restore " + element + " of " + element.getClass() + " from " + elementInfo +
|
||||
"; restored=" + restored + (restored == null ? "" : " of "+restored.getClass())+" in " + element.getProject());
|
||||
"; restored=" + restoredElement + (restoredElement == null ? "" : " of " + restoredElement.getClass()) + " in " + element.getProject());
|
||||
}
|
||||
}
|
||||
return elementInfo;
|
||||
|
||||
Reference in New Issue
Block a user