Django: Extract Django/Jinja2 commons

GitOrigin-RevId: 3f6bb7b7cd80cbf6007e1930d4d018e1b1cff44a
This commit is contained in:
Alexey Sedunov
2022-09-23 10:47:37 +02:00
committed by intellij-monorepo-bot
parent f148162c19
commit c443410d79
19 changed files with 30 additions and 22 deletions

View File

@@ -7,8 +7,12 @@
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="intellij.python.community" exported="" />
<orderEntry type="module" module-name="intellij.platform.analysis.impl" exported="" />
<orderEntry type="module" module-name="intellij.python.psi" />
<orderEntry type="module" module-name="intellij.platform.analysis.impl" />
<orderEntry type="module" module-name="intellij.platform.lang.impl" />
<orderEntry type="module" module-name="intellij.regexp" />
<orderEntry type="library" name="Guava" level="project" />
<orderEntry type="module" module-name="intellij.python.psi.impl" />
<orderEntry type="module" module-name="intellij.python.sdk" />
</component>
</module>

View File

@@ -13,18 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python;
package com.jetbrains.extensions
import com.intellij.psi.PsiElement;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.Nullable;
import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.ModuleRootManager
/**
* Reference that may be resolved differently (i.e use better {@link TypeEvalContext}) when resolved by user request
* @author Ilya.Kazakevich
*/
@FunctionalInterface
public interface PyUserInitiatedResolvableReference {
@Nullable
PsiElement userInitiatedResolve();
}
fun Module.getSdk(): Sdk? = ModuleRootManager.getInstance(this).sdk

View File

@@ -0,0 +1,182 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.extensions
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiManager
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.QualifiedName
import com.jetbrains.python.PyNames
import com.jetbrains.python.psi.PyClass
import com.jetbrains.python.psi.PyFile
import com.jetbrains.python.psi.resolve.*
import com.jetbrains.python.psi.stubs.PyModuleNameIndex
import com.jetbrains.python.psi.types.TypeEvalContext
import com.jetbrains.python.sdk.PySdkUtil
import java.util.*
interface ContextAnchor {
val sdk: Sdk?
val project: Project
val qualifiedNameResolveContext: PyQualifiedNameResolveContext?
val scope: GlobalSearchScope
fun getRoots(): Array<VirtualFile> {
return sdk?.rootProvider?.getFiles(OrderRootType.CLASSES) ?: emptyArray()
}
}
class ModuleBasedContextAnchor(val module: Module) : ContextAnchor {
override val sdk: Sdk? = module.getSdk()
override val project: Project = module.project
override val qualifiedNameResolveContext: PyQualifiedNameResolveContext = fromModule(module)
override val scope: GlobalSearchScope = module.moduleContentScope
override fun getRoots(): Array<VirtualFile> {
val manager = ModuleRootManager.getInstance(module)
return super.getRoots() + manager.contentRoots + manager.sourceRoots
}
}
class ProjectSdkContextAnchor(override val project: Project, override val sdk: Sdk?) : ContextAnchor {
override val qualifiedNameResolveContext: PyQualifiedNameResolveContext? = sdk?.let { fromSdk(project, it) }
override val scope: GlobalSearchScope = GlobalSearchScope.projectScope(project) //TODO: Check if project scope includes SDK
override fun getRoots(): Array<VirtualFile> {
val manager = ProjectRootManager.getInstance(project)
return super.getRoots() + manager.contentRoots + manager.contentSourceRoots
}
}
data class QNameResolveContext(
val contextAnchor: ContextAnchor,
/**
* Used for language level etc
*/
val sdk: Sdk? = contextAnchor.sdk,
val evalContext: TypeEvalContext,
/**
* If not provided resolves against roots only. Resolved also against this folder otherwise
*/
val folderToStart: VirtualFile? = null,
/**
* Use index, plain dirs with Py2 and so on. May resolve names unresolvable in other cases, but may return false results.
*/
val allowInaccurateResult: Boolean = false
)
/**
* @return qname part relative to root
*/
fun QualifiedName.getRelativeNameTo(root: QualifiedName): QualifiedName? {
if (Collections.indexOfSubList(components, root.components) == -1) {
return null
}
return subQualifiedName(root.componentCount, componentCount)
}
/**
* Resolves qname of any symbol to appropriate PSI element.
* Shortcut for [getElementAndResolvableName]
* @see [getElementAndResolvableName]
*/
fun QualifiedName.resolveToElement(context: QNameResolveContext, stopOnFirstFail: Boolean = false): PsiElement? {
return getElementAndResolvableName(context, stopOnFirstFail)?.element
}
data class NameAndElement(val name: QualifiedName, val element: PsiElement)
/**
* Resolves qname of any symbol to PSI element popping tail until element becomes resolved or only one time if stopOnFirstFail
* @return element and longest name that was resolved successfully.
* @see [resolveToElement]
*/
fun QualifiedName.getElementAndResolvableName(context: QNameResolveContext, stopOnFirstFail: Boolean = false): NameAndElement? {
var currentName = QualifiedName.fromComponents(this.components)
var element: PsiElement? = null
var lastElement: String? = null
var psiDirectory: PsiDirectory? = null
var resolveContext = context.contextAnchor.qualifiedNameResolveContext?.copyWithMembers() ?: return null
if (PySdkUtil.getLanguageLevelForSdk(context.sdk).isPy3K || context.allowInaccurateResult) {
resolveContext = resolveContext.copyWithPlainDirectories()
}
if (context.folderToStart != null) {
psiDirectory = PsiManager.getInstance(context.contextAnchor.project).findDirectory(context.folderToStart)
}
// Drill as deep, as we can
while (currentName.componentCount > 0 && element == null) {
if (psiDirectory != null) { // Resolve against folder
// There could be folder and module on the same level. Empty folder should be ignored in this case.
element = resolveModuleAt(currentName, psiDirectory, resolveContext).filterNot {
it is PsiDirectory && it.children.filterIsInstance<PyFile>().isEmpty()
}.firstOrNull()
}
if (element == null) { // Resolve against roots
element = resolveQualifiedName(currentName, resolveContext).firstOrNull()
}
if (element != null || stopOnFirstFail) {
break
}
lastElement = currentName.lastComponent!!
currentName = currentName.removeLastComponent()
}
if (lastElement != null && element is PyClass) {
// Drill in class
//TODO: Support nested classes
val method = element.findMethodByName(lastElement, true, context.evalContext)
if (method != null) {
return NameAndElement(currentName.append(lastElement), method)
}
}
if (element == null && this.firstComponent != null && context.allowInaccurateResult) {
// If name starts with file which is not in root nor in folders -- use index.
val nameToFind = this.firstComponent!!
val pyFile = PyModuleNameIndex.find(nameToFind, context.contextAnchor.project, false).firstOrNull() ?: return element
val folder =
if (pyFile.name == PyNames.INIT_DOT_PY) { // We are in folder
pyFile.virtualFile.parent.parent
}
else {
pyFile.virtualFile.parent
}
return getElementAndResolvableName(context.copy(folderToStart = folder))
}
return if (element != null) NameAndElement(currentName, element) else null
}

View File

@@ -0,0 +1,7 @@
// Copyright 2000-2017 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.jetbrains.extensions
import org.intellij.lang.regexp.psi.RegExpPattern
import java.util.regex.Pattern
fun RegExpPattern.asPattern(): Pattern = Pattern.compile(text)!!

View File

@@ -0,0 +1,21 @@
// Copyright 2000-2019 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.jetbrains.extensions.python
import com.jetbrains.python.nameResolver.FQNamesProvider
import com.jetbrains.python.nameResolver.NameResolverTools
import com.jetbrains.python.psi.PyAssignmentStatement
import com.jetbrains.python.psi.PyCallExpression
import com.jetbrains.python.psi.PyPossibleClassMember
/**
* Checks if ``foo = SomeExpr()`` where foo is class attribute
*/
val PyCallExpression.isClassAttribute: Boolean
get() =
(parent as? PyAssignmentStatement)?.targets?.filterIsInstance<PyPossibleClassMember>()?.any { it.containingClass != null } == true
/**
* Checks if callee has certain name. Only name is checked, so import aliases aren't supported, but it works pretty fast
*/
fun PyCallExpression.isCalleeName(vararg names: FQNamesProvider): Boolean = NameResolverTools.isCalleeShortCut(this, *names)

View File

@@ -1,69 +0,0 @@
// 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.
package com.jetbrains.python;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.PsiReference;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Alexei Orischenko
*/
public abstract class BaseReference implements PsiReference, PyUserInitiatedResolvableReference {
protected final PsiElement myElement;
protected BaseReference(@NotNull PsiElement element) {
myElement = element;
}
@Override
@NotNull
public PsiElement getElement() {
return myElement;
}
@Override
@NotNull
public TextRange getRangeInElement() {
return new TextRange(0, myElement.getTextLength());
}
@Override
@NotNull
public String getCanonicalText() {
return myElement.getText();
}
@Override
public PsiElement handleElementRename(@NotNull String newElementName) throws IncorrectOperationException {
if (myElement instanceof PsiNamedElement) {
return ((PsiNamedElement)myElement).setName(newElementName);
}
throw new IncorrectOperationException();
}
@Override
public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
return null;
}
@Override
public boolean isReferenceTo(@NotNull PsiElement element) {
return resolve() == element;
}
@Override
public boolean isSoft() {
return false;
}
@Nullable
@Override
public PsiElement userInitiatedResolve() {
// Override this method if your reference may benefit from this knowledge
return resolve();
}
}

View File

@@ -0,0 +1,68 @@
// 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.jetbrains.python;
import com.google.common.collect.Lists;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.psi.PsiElement;
import com.intellij.util.ProcessingContext;
import com.jetbrains.python.codeInsight.PyCustomMember;
import com.jetbrains.python.psi.AccessDirection;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.impl.ResolveResultList;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.RatedResolveResult;
import com.jetbrains.python.psi.types.PyType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public class PySyntheticType implements PyType {
private final List<PyCustomMember> myMembers;
private final String myName;
private final PsiElement myContext;
public PySyntheticType(String name, PsiElement context, PyCustomMember... members) {
myName = name;
myContext = context;
myMembers = Lists.newArrayList(members);
}
@Override
public List<? extends RatedResolveResult> resolveMember(@NotNull String name,
@Nullable PyExpression location,
@NotNull AccessDirection direction,
@NotNull PyResolveContext resolveContext) {
final ResolveResultList result = new ResolveResultList();
for (PyCustomMember member : myMembers) {
if (name.equals(member.getName())) {
result.poke(member.resolve(myContext, resolveContext), RatedResolveResult.RATE_NORMAL);
}
}
return result;
}
@Override
public Object[] getCompletionVariants(String completionPrefix, PsiElement location, ProcessingContext context) {
final List<Object> result = new ArrayList<>();
for (PyCustomMember member : myMembers) {
result.add(LookupElementBuilder.create(member.getName()));
}
return result.toArray();
}
@Override
public String getName() {
return myName;
}
@Override
public boolean isBuiltin() {
return false;
}
@Override
public void assertValid(String message) {
}
}

View File

@@ -0,0 +1,166 @@
// 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.jetbrains.python;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.util.QualifiedName;
import com.intellij.util.PathUtil;
import com.jetbrains.python.psi.PyStringLiteralCoreUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Alexei Orischenko
* @author vlan
*/
public final class PythonStringUtil {
private PythonStringUtil() {
}
@NotNull
public static String removeFirstPrefix(@Nullable String s, String separator) {
if (s != null) {
int pos = s.indexOf(separator);
if (pos != -1) {
return s.substring(pos + separator.length());
}
}
return "";
}
@NotNull
public static String removeLastSuffix(@Nullable String s, String separator) {
if (s != null) {
int pos = s.lastIndexOf(separator);
if (pos != -1) {
return s.substring(0, pos);
}
}
return "";
}
public static boolean isPath(@Nullable String s) {
if (!StringUtil.isEmpty(s)) {
s = Objects.requireNonNull(s);
s = FileUtil.toSystemIndependentName(s);
final List<String> components = StringUtil.split(s, "/");
for (String name : components) {
if (name == components.get(0) && SystemInfo.isWindows && name.endsWith(":")) {
continue;
}
if (!PathUtil.isValidFileName(name)) {
return false;
}
}
return true;
}
return false;
}
public static boolean isEmail(String s) {
if (!StringUtil.isEmpty(s)) {
Pattern p = Pattern.compile("^[\\w\\.-]+@([\\w\\-]+\\.)+[a-z]{2,4}$");
Matcher m = p.matcher(s);
return m.matches();
}
return false;
}
@NotNull
public static String getFirstPrefix(String s, String separator) {
if (s != null) {
int pos = s.indexOf(separator);
if (pos != -1) {
return s.substring(0, pos);
}
}
return s != null ? s : "";
}
public static String getLastSuffix(String s, String separator) {
if (s != null) {
int pos = s.lastIndexOf(separator);
if (pos != -1) {
return s.substring(pos + 1);
}
}
return "";
}
/**
* Replaces word after last dot from string s to newElementName.
* For example 'yy.xxx.foo' can be renamed to 'yy.xxx.bar'
*
*/
public static String replaceLastSuffix(String s, String separator, String newElementName) {
Pair<String, String> quotes = null;
if (PyStringLiteralCoreUtil.isQuoted(s)) {
quotes = PyStringLiteralCoreUtil.getQuotes(s);
s = PyStringLiteralCoreUtil.stripQuotesAroundValue(s);
}
s = removeLastSuffix(s, separator);
if (s.length() > 0) {
s += separator;
}
s += newElementName;
if (quotes != null) {
s = quotes.first + s + quotes.second;
}
return s;
}
public static TextRange lastSuffixTextRange(@NotNull String text, String separator) {
int offset = text.lastIndexOf(separator) + 1;
int length = text.length() - offset;
return TextRange.from(offset + 1, length);
}
@Nullable
public static String intersect(String fullName, String elementStringValue) {
QualifiedName fullQName = QualifiedName.fromDottedString(fullName);
QualifiedName stringQName = QualifiedName.fromDottedString(elementStringValue);
String[] s1 = stringQName.getComponents().toArray(new String[stringQName.getComponentCount()]);
String[] s2 = fullQName.getComponents().toArray(new String[fullQName.getComponentCount()]);
for (int i = s1.length - 1; i >= 0; i--) {
boolean flag = true;
if (i > s2.length - 1) {
continue;
}
for (int j = 0; j <= i; j++) {
if (!s1[i - j].equals(s2[s2.length - j - 1])) {
flag = false;
break;
}
}
if (flag) {
StringBuilder res = new StringBuilder();
for (int j = 0; j <= i; j++) {
if (j > 0) {
res.append(".");
}
res.append(s1[j]);
}
return res.toString();
}
}
return null;
}
}

View File

@@ -0,0 +1,31 @@
// Copyright 2000-2021 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.jetbrains.python.codeInsight;
import com.intellij.ide.util.PsiElementListCellRenderer;
import com.intellij.navigation.ItemPresentation;
import com.intellij.navigation.NavigationItem;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
public class PyElementListCellRenderer extends PsiElementListCellRenderer {
@Override
public String getElementText(final PsiElement element) {
if (element instanceof PsiNamedElement) {
final String name = ((PsiNamedElement)element).getName();
return name == null ? "" : name;
}
return element.getText();
}
@Override
protected String getContainerText(final PsiElement element, final String name) {
if (element instanceof NavigationItem) {
final ItemPresentation presentation = ((NavigationItem)element).getPresentation();
if (presentation != null) {
return presentation.getLocationString();
}
}
return null;
}
}

View File

@@ -1,103 +0,0 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.psi;
import com.google.common.collect.ImmutableList;
import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class PyStringLiteralCoreUtil {
/**
* Valid string prefix characters (lowercased) as defined in Python lexer.
*/
public static final String PREFIX_CHARACTERS = "ubcrf";
/**
* Maximum length of a string prefix as defined in Python lexer.
*/
public static final int MAX_PREFIX_LENGTH = 3;
private static final ImmutableList<String> QUOTES = ImmutableList.of("'''", "\"\"\"", "'", "\"");
protected PyStringLiteralCoreUtil() {
}
/**
* Returns a pair where the first element is the prefix combined with the opening quote and the second is the closing quote.
* <p>
* If the given string literal is not properly quoted, e.g. the closing quote has fewer quotes as opposed to the
* opening one, or it's missing altogether this method returns null.
* <p>
* Examples:
* <pre>
* ur"foo" -> ("ur, ")
* ur'bar -> null
* """baz""" -> (""", """)
* '''quux' -> null
* </pre>
*/
@Nullable
public static Pair<String, String> getQuotes(@NotNull String text) {
final String prefix = getPrefix(text);
final String mainText = text.substring(prefix.length());
for (String quote : QUOTES) {
final Pair<String, String> quotes = getQuotes(mainText, prefix, quote);
if (quotes != null) {
return quotes;
}
}
return null;
}
/**
* Finds the end offset of the string prefix starting from {@code startOffset} in the given char sequence.
* String prefix may contain only up to {@link #MAX_PREFIX_LENGTH} characters from {@link #PREFIX_CHARACTERS}
* (case insensitively).
*
* @return end offset of found string prefix
*/
public static int getPrefixEndOffset(@NotNull CharSequence text, int startOffset) {
int offset;
for (offset = startOffset; offset < Math.min(startOffset + MAX_PREFIX_LENGTH, text.length()); offset++) {
if (PREFIX_CHARACTERS.indexOf(Character.toLowerCase(text.charAt(offset))) < 0) {
break;
}
}
return offset;
}
@NotNull
public static String getPrefix(@NotNull CharSequence text) {
return getPrefix(text, 0);
}
/**
* Extracts string prefix from the given char sequence using {@link #getPrefixEndOffset(CharSequence, int)}.
*
* @return extracted string prefix
* @see #getPrefixEndOffset(CharSequence, int)
*/
@NotNull
public static String getPrefix(@NotNull CharSequence text, int startOffset) {
return text.subSequence(startOffset, getPrefixEndOffset(text, startOffset)).toString();
}
@Nullable
private static Pair<String, String> getQuotes(@NotNull String text, @NotNull String prefix, @NotNull String quote) {
final int length = text.length();
final int n = quote.length();
if (length >= 2 * n && text.startsWith(quote) && text.endsWith(quote)) {
return Pair.create(prefix + text.substring(0, n), text.substring(length - n));
}
return null;
}
public static String stripQuotesAroundValue(String text) {
Pair<String, String> quotes = getQuotes(text);
if (quotes == null) {
return text;
}
return text.substring(quotes.first.length(), text.length() - quotes.second.length());
}
}