PY-2821: Support "reverse" and Co reference injection

* Drop Django 1.5 style {% url %} tag support
* Provide view name references for reverse etc
* Improve  DjangoFQNamesProvider API to support "package/name only" check
This commit is contained in:
Ilya.Kazakevich
2017-12-18 23:15:26 +03:00
parent 98d32d1ea1
commit e401531a4a
5 changed files with 83 additions and 5 deletions
@@ -15,10 +15,20 @@
*/
package com.jetbrains.python.nameResolver;
import com.intellij.navigation.NavigationItem;
import com.intellij.psi.util.QualifiedName;
import com.intellij.util.ArrayUtil;
import com.jetbrains.python.psi.PyQualifiedNameOwner;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Some enum value that represents one or more fully qualified names for some function
*
* @author Ilya.Kazakevich
*/
public interface FQNamesProvider {
@@ -28,8 +38,73 @@ public interface FQNamesProvider {
@NotNull
String[] getNames();
@NotNull
default QualifiedName[] getQualifiedNames() {
return Arrays.stream(getNames()).map(QualifiedName::fromDottedString).toArray(QualifiedName[]::new);
}
@NotNull
default String getFirstName() {
return getNames()[0];
}
default boolean isShortNameMatches(@NotNull final NavigationItem item) {
final String name = item.getName();
if (name == null) {
return false;
}
return getShortNames().contains(name);
}
/**
* @return all names in unqualified ("after last dot") format
*/
@NotNull
default List<String> getShortNames() {
return Arrays.stream(getQualifiedNames()).map(QualifiedName::getLastComponent).filter(o -> o != null).collect(Collectors.toList());
}
/**
* @return is name of class (true) or function (false)
*/
boolean isClass();
/**
* @return if element should be checked by full name conformity by {@link #isNameMatches(PyQualifiedNameOwner)}
* or only name and package should be checked
* @see #isNameMatches(PyQualifiedNameOwner)
*/
default boolean alwaysCheckQualifiedName() {
return true;
}
/**
* Checks if element name matches. {@link #alwaysCheckQualifiedName()} controls if full name should be checked, or only last and first
* parts (name and package) are enough. It may be used for cases when physical FQN is not documented.
*/
default boolean isNameMatches(@NotNull final PyQualifiedNameOwner qualifiedNameOwner) {
final String qualifiedName = qualifiedNameOwner.getQualifiedName();
if (qualifiedName == null) {
return false;
}
// Only check qualified name
if (alwaysCheckQualifiedName()) {
return ArrayUtil.contains(qualifiedName, getNames());
}
// Relaxed check: package and name
final QualifiedName elementQualifiedName = QualifiedName.fromDottedString(qualifiedName);
final Stream<QualifiedName> nameStream = Arrays.stream(getQualifiedNames());
return nameStream.anyMatch((name) -> {
final String first = name.getFirstComponent();
final String last = name.getLastComponent();
return first != null
&& last != null
&& first.equals(elementQualifiedName.getFirstComponent())
&& last.equals(elementQualifiedName.getLastComponent());
}
);
}
}
@@ -50,7 +50,7 @@ public abstract class PyElementGenerator {
public abstract PyStringLiteralExpression createStringLiteralFromString(@NotNull String unescaped);
public abstract PyStringLiteralExpression createStringLiteral(@NotNull PyStringLiteralExpression oldElement, @NotNull String unescaped);
public abstract PyStringLiteralExpression createStringLiteral(@NotNull StringLiteralExpression oldElement, @NotNull String unescaped);
public abstract PyListLiteralExpression createListLiteral();
@@ -18,7 +18,6 @@ package com.jetbrains.python;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.jetbrains.python.psi.PyElementGenerator;
import com.jetbrains.python.psi.PyStringLiteralUtil;
import com.jetbrains.python.psi.StringLiteralExpression;
import org.jetbrains.annotations.NotNull;
@@ -39,12 +38,12 @@ public abstract class PyStringLiteralReference extends BaseReference {
@SuppressWarnings("RefusedBequest") // 1 instead of 1 in range and "-1" at the end because we do not need quotes
@Override
public final TextRange getRangeInElement() {
return PyStringLiteralUtil.getTextRange(myElement);
return myStringLiteral.getStringValueTextRange();
}
@Override
public PsiElement handleElementRename(@NotNull final String newElementName) {
final PsiElement newString = PyElementGenerator.getInstance(myElement.getProject()).createStringLiteralFromString(newElementName);
final PsiElement newString = PyElementGenerator.getInstance(myElement.getProject()).createStringLiteral(myStringLiteral, newElementName);
myStringLiteral.replace(newString);
return newString;
}
@@ -1708,7 +1708,10 @@ public class PyUtil {
* @param expectedPackage package like "django"
* @param expectedName expected name (i.e. AppConfig)
* @return true if element in package
* @deprecated use {@link com.jetbrains.python.nameResolver.FQNamesProvider#isNameMatches(PyQualifiedNameOwner)}
* Remove in 2018
*/
@Deprecated
public static boolean isSymbolInPackage(@NotNull final PyQualifiedNameOwner symbol,
@NotNull final String expectedPackage,
@NotNull final String expectedName) {
@@ -108,7 +108,8 @@ public class PyElementGeneratorImpl extends PyElementGenerator {
return createStringLiteralFromString(null, unescaped, true);
}
public PyStringLiteralExpression createStringLiteral(@NotNull PyStringLiteralExpression oldElement, @NotNull String unescaped) {
@Override
public PyStringLiteralExpression createStringLiteral(@NotNull StringLiteralExpression oldElement, @NotNull String unescaped) {
Pair<String, String> quotes = PyStringLiteralUtil.getQuotes(oldElement.getText());
if (quotes != null) {
return createStringLiteralAlreadyEscaped(quotes.first + unescaped + quotes.second);