[api] add ability to provide external symbol references to elements which support them

GitOrigin-RevId: 8d4484c67589795e23444f85b9d85a6f64903be5
This commit is contained in:
Daniil Ovchinnikov
2020-01-13 12:39:24 +00:00
committed by intellij-monorepo-bot
parent eb6e65d4b3
commit 529ea32cf8
7 changed files with 223 additions and 1 deletions
@@ -0,0 +1,20 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.model.psi;
import com.intellij.psi.PsiElement;
/**
* Elements which support external references should implement this interface.
* <p>
* There are two kinds of element references: own references and external references.<br/>
* Own references are references the element knows about, they are usually used by language support.
* Element doesn't know about external references since, for example, they might be contributed by plugins.
* External references are used for navigation/Find Usages/etc as well as own references.
* <p>
* The element must implement this interface to support hosting external references,
* so this mechanism is effectively opt-in.
*
* @see PsiElement#getOwnReferences
*/
public interface PsiExternalReferenceHost extends PsiElement {
}
@@ -0,0 +1,52 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.model.psi;
import com.intellij.model.Symbol;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface PsiSymbolReferenceHints {
/**
* Provider may return only references which could be resolved to symbols of this type if the type is not {@code null}.
*
* @return type of expected target symbol
*/
@Nullable
default Class<? extends Symbol> getTargetClass() {
Symbol target = getTarget();
return target != null ? target.getClass() : null;
}
/**
* Provider may return only references which could be resolved to specified symbol if the symbol is not {@code null}.
*
* @return expected target symbol
*/
@Nullable
default Symbol getTarget() {
return null;
}
/**
* Provider may return references which contain specified offset if the offset is greater than or equal to 0;
* in this case the offset is guaranteed to be within {@code [0, element.getTextLength())}.
*
* @return offset in the element for which references are queried, or {@code null} if the offset doesn't matter
*/
@Nullable
default Integer getOffsetInElement() {
return null;
}
@NotNull
static PsiSymbolReferenceHints offsetHint(int offsetInElement) {
assert offsetInElement >= 0;
return new PsiSymbolReferenceHints() {
@Override
public Integer getOffsetInElement() {
return offsetInElement;
}
};
}
}
@@ -0,0 +1,12 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.model.psi;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
public interface PsiSymbolReferenceProvider {
@NotNull
Collection<? extends PsiSymbolReference> getReferences(@NotNull PsiExternalReferenceHost element, @NotNull PsiSymbolReferenceHints hints);
}
@@ -0,0 +1,78 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.model.psi;
import com.intellij.diagnostic.PluginException;
import com.intellij.lang.Language;
import com.intellij.model.Symbol;
import com.intellij.openapi.extensions.CustomLoadingExtensionPointBean;
import com.intellij.openapi.extensions.PluginDescriptor;
import com.intellij.openapi.extensions.RequiredElement;
import com.intellij.util.xmlb.annotations.Attribute;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class PsiSymbolReferenceProviderBean extends CustomLoadingExtensionPointBean<PsiSymbolReferenceProvider> {
/**
* {@link Language#getID() id} of the language for which references are provided.<br/>
* The references will be provided for the specified language and its {@linkplain Language#getBaseLanguage() base languages}.
*/
@Attribute
@RequiredElement
public String hostLanguage;
/**
* Fully qualified name of the class of the PsiElement for which references are provided.<br/>
* The references will be provided for the specified class and its superclasses.
*/
@Attribute
@RequiredElement
public String hostElementClass;
/**
* Fully qualified name of the common supertype of all symbols that this provider's references could resolve to.
*/
@Attribute
@RequiredElement
public String targetClass;
@Attribute
@RequiredElement
public String implementationClass;
@Nullable
@Override
protected String getImplementationClassName() {
return implementationClass;
}
@NotNull
public Language getHostLanguage() {
Language language = Language.findLanguageByID(hostLanguage);
if (language == null) {
throw new PluginException("Cannot find language '" + hostLanguage + "'", getPluginDescriptor().getPluginId());
}
return language;
}
@NotNull
public Class<? extends PsiExternalReferenceHost> getHostElementClass() {
return loadClass(hostElementClass);
}
@NotNull
public Class<? extends Symbol> getResolveTargetClass() {
return loadClass(targetClass);
}
@SuppressWarnings("unchecked")
private <T> Class<T> loadClass(@NotNull String fqn) {
PluginDescriptor pluginDescriptor = getPluginDescriptor();
try {
return (Class<T>)Class.forName(fqn, true, pluginDescriptor.getPluginClassLoader());
}
catch (ClassNotFoundException e) {
throw new PluginException(e, pluginDescriptor.getPluginId());
}
}
}
@@ -0,0 +1,29 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.model.psi;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
/**
* Entry point for obtaining {@link PsiSymbolReference}s from {@link PsiElement}.
*/
public interface PsiSymbolReferenceService {
@NotNull
static PsiSymbolReferenceService getService() {
return ServiceManager.getService(PsiSymbolReferenceService.class);
}
/**
* @return all (own and external) references from this element
*/
@NotNull
Iterable<? extends PsiSymbolReference> getReferences(@NotNull PsiElement element);
/**
* @return all (own and external) references from this element, which match {@code hints}
*/
@NotNull
Iterable<? extends PsiSymbolReference> getReferences(@NotNull PsiElement element, @NotNull PsiSymbolReferenceHints hints);
}
@@ -1,8 +1,9 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.psi;
import com.intellij.lang.ASTNode;
import com.intellij.lang.Language;
import com.intellij.model.psi.PsiSymbolReference;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Iconable;
import com.intellij.openapi.util.Key;
@@ -13,11 +14,14 @@ import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.SearchScope;
import com.intellij.util.ArrayFactory;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.ApiStatus.Experimental;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
/**
* The common base interface for all elements of the PSI tree.
* <p/>
@@ -429,6 +433,26 @@ public interface PsiElement extends UserDataHolder, Iconable {
@Contract(pure=true)
boolean isWritable();
/**
* The returned references are expected to be used by language support,
* for example in Java `foo` element in `foo = 42` expression has a reference,
* which is used by Java language support to compute expected type of the assignment.
* <p>
* On the other hand {@code "bar"} literal in {@code new File("bar")} is a string literal,
* and from Java language perspective it has no references,
* but the framework support "knows" that this literal contains the reference to a file.
* These are external references.
*
* @return collection of references from this element, or empty collection if there are no such references
* @see com.intellij.model.psi.PsiExternalReferenceHost
* @see com.intellij.model.psi.PsiSymbolReferenceService#getReferences(PsiElement)
*/
@Experimental
@NotNull
default Iterable<? extends PsiSymbolReference> getOwnReferences() {
return Arrays.asList(getReferences());
}
/**
* Returns the reference from this PSI element to another PSI element (or elements), if one exists.
* If the element has multiple associated references (see {@link #getReferences()}
@@ -11,6 +11,13 @@
<extensionPoint name="psi.referenceContributor" beanClass="com.intellij.psi.impl.source.resolve.reference.PsiReferenceContributorEP" dynamic="true">
<with attribute="implementation" implements="com.intellij.psi.PsiReferenceContributor"/>
</extensionPoint>
<extensionPoint name="psi.symbolReferenceProvider"
dynamic="true"
beanClass="com.intellij.model.psi.PsiSymbolReferenceProviderBean">
<with attribute="hostElementClass" implements="com.intellij.model.psi.PsiExternalReferenceHost"/>
<with attribute="targetClass" implements="com.intellij.model.Symbol"/>
<with attribute="implementationClass" implements="com.intellij.model.psi.PsiSymbolReferenceProvider"/>
</extensionPoint>
<extensionPoint name="lang.ast.factory" beanClass="com.intellij.lang.LanguageExtensionPoint" dynamic="true">
<with attribute="implementationClass" implements="com.intellij.lang.ASTFactory"/>
</extensionPoint>