PY-48012 Resolve and complete attributes in PEP 634 class patterns

Both are implemented other the type of the corresponding class.
References resolve to any readable attribute of a class, however
some obviously wrong variants such as special "dunder" attributes
and methods are intentionally excluded from completion suggestions.

GitOrigin-RevId: 5edac14f47cba39840b15b0dd7f21e2e46077261
This commit is contained in:
Mikhail Golubev
2021-07-09 20:29:00 +03:00
committed by intellij-monorepo-bot
parent d15bf8a552
commit 1421975c96
49 changed files with 698 additions and 8 deletions

View File

@@ -1,5 +1,10 @@
// 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.psi;
import org.jetbrains.annotations.NotNull;
public interface PyClassPattern extends PyPattern {
@NotNull PyReferenceExpression getClassNameReference();
@NotNull PyPatternArgumentList getArgumentList();
}

View File

@@ -47,6 +47,9 @@ public abstract class PyElementGenerator {
@NotNull
public abstract PyExpression createExpressionFromText(@NotNull LanguageLevel languageLevel, @NotNull String text) throws IncorrectOperationException;
@NotNull
public abstract PyPattern createPatternFromText(@NotNull LanguageLevel languageLevel, @NotNull String text) throws IncorrectOperationException;
/**
* Adds elements to list inserting required commas.

View File

@@ -1,5 +1,14 @@
// 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.psi;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface PyKeywordPattern extends PyPattern {
@NotNull String getKeyword();
@NotNull PsiElement getKeywordElement();
@Nullable PyPattern getValuePattern();
}

View File

@@ -47,6 +47,8 @@
implementationClass="com.jetbrains.python.psi.impl.PyStringLiteralExpressionManipulator"/>
<lang.elementManipulator forClass="com.jetbrains.python.psi.PyKeywordArgument"
implementationClass="com.jetbrains.python.psi.impl.PyKeywordArgumentManipulator"/>
<lang.elementManipulator forClass="com.jetbrains.python.psi.PyKeywordPattern"
implementationClass="com.jetbrains.python.psi.impl.references.PyKeywordPatternManipulator"/>
<lang.parserDefinition language="Python" implementationClass="com.jetbrains.python.PythonParserDefinition"/>
<!-- PyFunctionTypeAnnotation -->

View File

@@ -6,21 +6,28 @@ import com.intellij.codeInsight.completion.InsertionContext;
import com.intellij.codeInsight.lookup.Lookup;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.openapi.editor.Document;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
public class OverwriteEqualsInsertHandler implements InsertHandler<LookupElement> {
public static OverwriteEqualsInsertHandler INSTANCE = new OverwriteEqualsInsertHandler();
public final static OverwriteEqualsInsertHandler INSTANCE = new OverwriteEqualsInsertHandler();
private OverwriteEqualsInsertHandler() { }
@Override
public void handleInsert(@NotNull InsertionContext context, @NotNull LookupElement item) {
if (context.getCompletionChar() != Lookup.REPLACE_SELECT_CHAR) {
return;
}
String lookupString = item.getLookupString();
Document doc = context.getDocument();
int tailOffset = context.getTailOffset();
if (tailOffset < doc.getCharsSequence().length() && doc.getCharsSequence().charAt(tailOffset) == '=') {
doc.deleteString(tailOffset, tailOffset+1);
if (lookupString.endsWith("=") && CharArrayUtil.regionMatches(doc.getCharsSequence(), tailOffset, "=")) {
doc.deleteString(tailOffset, tailOffset + 1);
}
else if (lookupString.endsWith(" = ") && CharArrayUtil.regionMatches(doc.getCharsSequence(), tailOffset, " = ")) {
doc.deleteString(tailOffset, tailOffset + 3);
}
}
}

View File

@@ -261,7 +261,7 @@ public abstract class PyUnresolvedReferencesVisitor extends PyInspectionVisitor
final String text = element.getText();
TextRange rangeInElement = reference.getRangeInElement();
String refText = text; // text of the part we're working with
if (rangeInElement.getStartOffset() > 0 && rangeInElement.getEndOffset() > 0) {
if (rangeInElement.getStartOffset() >= 0 && rangeInElement.getEndOffset() > 0) {
refText = rangeInElement.substring(text);
}

View File

@@ -3,6 +3,11 @@ package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.jetbrains.python.psi.PyClassPattern;
import com.jetbrains.python.psi.PyElementVisitor;
import com.jetbrains.python.psi.PyPatternArgumentList;
import com.jetbrains.python.psi.PyReferenceExpression;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
public class PyClassPatternImpl extends PyElementImpl implements PyClassPattern {
public PyClassPatternImpl(ASTNode astNode) {
@@ -13,4 +18,14 @@ public class PyClassPatternImpl extends PyElementImpl implements PyClassPattern
protected void acceptPyVisitor(PyElementVisitor pyVisitor) {
pyVisitor.visitPyClassPattern(this);
}
@Override
public @NotNull PyReferenceExpression getClassNameReference() {
return Objects.requireNonNull(findChildByClass(PyReferenceExpression.class));
}
@Override
public @NotNull PyPatternArgumentList getArgumentList() {
return Objects.requireNonNull(findChildByClass(PyPatternArgumentList.class));
}
}

View File

@@ -274,6 +274,16 @@ public class PyElementGeneratorImpl extends PyElementGenerator {
throw new IncorrectOperationException("could not parse text as expression: " + text);
}
@Override
public @NotNull PyPattern createPatternFromText(@NotNull LanguageLevel languageLevel, @NotNull String text)
throws IncorrectOperationException {
String matchStatement = "match x:\n" +
" case C(" + text + "):\n" +
" pass ";
int[] pathToAttrPattern = {0, 5, 2, 1, 1};
return createFromText(languageLevel, PyPattern.class, matchStatement, pathToAttrPattern);
}
@Override
@NotNull
public PyCallExpression createCallExpression(final LanguageLevel langLevel, String functionName) {

View File

@@ -1,8 +1,14 @@
package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.jetbrains.python.psi.PyElementVisitor;
import com.jetbrains.python.psi.PyKeywordPattern;
import com.jetbrains.python.psi.PyPattern;
import com.jetbrains.python.psi.impl.references.PyKeywordPatternReference;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class PyKeywordPatternImpl extends PyElementImpl implements PyKeywordPattern {
public PyKeywordPatternImpl(ASTNode astNode) {
@@ -13,4 +19,24 @@ public class PyKeywordPatternImpl extends PyElementImpl implements PyKeywordPatt
protected void acceptPyVisitor(PyElementVisitor pyVisitor) {
pyVisitor.visitPyKeywordPattern(this);
}
@Override
public @NotNull String getKeyword() {
return getKeywordElement().getText();
}
@Override
public @NotNull PsiElement getKeywordElement() {
return getFirstChild();
}
@Override
public @Nullable PyPattern getValuePattern() {
return findChildByClass(PyPattern.class);
}
@Override
public PsiReference getReference() {
return new PyKeywordPatternReference(this);
}
}

View File

@@ -0,0 +1,26 @@
package com.jetbrains.python.psi.impl.references;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.AbstractElementManipulator;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyElementGenerator;
import com.jetbrains.python.psi.PyKeywordPattern;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public final class PyKeywordPatternManipulator extends AbstractElementManipulator<PyKeywordPattern> {
@Override
public @Nullable PyKeywordPattern handleContentChange(@NotNull PyKeywordPattern element,
@NotNull TextRange range,
String newContent) throws IncorrectOperationException {
if (element.getKeywordElement().getTextRangeInParent().equals(range)) {
PyElementGenerator generator = PyElementGenerator.getInstance(element.getProject());
String newPatternText = newContent + "=None";
PyKeywordPattern newPattern = (PyKeywordPattern)generator.createPatternFromText(LanguageLevel.forElement(element), newPatternText);
element.getKeywordElement().replace(newPattern.getKeywordElement());
return element;
}
return null;
}
}

View File

@@ -0,0 +1,133 @@
package com.jetbrains.python.psi.impl.references;
import com.intellij.codeInsight.completion.InsertionContext;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementDecorator;
import com.intellij.codeInsight.lookup.LookupElementPresentation;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReferenceBase;
import com.intellij.psi.ResolveResult;
import com.intellij.util.ProcessingContext;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PythonCodeStyleService;
import com.jetbrains.python.codeInsight.completion.OverwriteEqualsInsertHandler;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.types.PyInstantiableType;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.psi.types.PyTypeUtil;
import com.jetbrains.python.psi.types.TypeEvalContext;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import static com.jetbrains.python.psi.PyUtil.as;
public final class PyKeywordPatternReference extends PsiReferenceBase.Poly<PyKeywordPattern> {
public PyKeywordPatternReference(@NotNull PyKeywordPattern keywordPattern) {
super(keywordPattern, keywordPattern.getKeywordElement().getTextRangeInParent(), false);
}
@Override
public ResolveResult @NotNull [] multiResolve(boolean incompleteCode) {
PyClassPattern classPattern = getContainingClassPattern();
if (classPattern == null) {
return ResolveResult.EMPTY_ARRAY;
}
PyKeywordPattern keywordPattern = getElement();
TypeEvalContext typeContext = TypeEvalContext.codeInsightFallback(keywordPattern.getProject());
PyResolveContext resolveContext = PyResolveContext.defaultContext(typeContext);
return StreamEx.of(resolveToClassTypes(classPattern, resolveContext))
.flatMap(t -> StreamEx.of(ContainerUtil.notNullize(t.resolveMember(keywordPattern.getKeyword(),
null, AccessDirection.READ, resolveContext))))
.toArray(ResolveResult.EMPTY_ARRAY);
}
@Override
public Object @NotNull [] getVariants() {
PyClassPattern classPattern = getContainingClassPattern();
if (classPattern == null) {
return LookupElement.EMPTY_ARRAY;
}
PyKeywordPattern keywordPattern = getElement();
TypeEvalContext typeContext = TypeEvalContext.codeCompletion(keywordPattern.getProject(), keywordPattern.getContainingFile());
return collectClassAttributeVariants(getElement(), classPattern, typeContext);
}
@Nullable
private PyClassPattern getContainingClassPattern() {
return as(getElement().getParent().getParent(), PyClassPattern.class);
}
static LookupElement @NotNull [] collectClassAttributeVariants(@NotNull PsiElement location,
@NotNull PyClassPattern classPattern,
@NotNull TypeEvalContext typeContext) {
PyResolveContext resolveContext = PyResolveContext.defaultContext(typeContext);
return StreamEx.of(resolveToClassTypes(classPattern, resolveContext))
.flatMap(t -> StreamEx.of(t.getCompletionVariants("", location, new ProcessingContext())))
.select(LookupElement.class)
.filter(e -> isMeaningfulClassPatternAttribute(e))
.map(e -> new KeywordAttributeLookupDecorator(e, location))
.toArray(LookupElement.EMPTY_ARRAY);
}
private static boolean isMeaningfulClassPatternAttribute(@NotNull LookupElement lookupElement) {
String lookupString = lookupElement.getLookupString();
if (lookupString.contains(".") || PyUtil.isSpecialName(lookupString)) {
return false;
}
PsiElement elem = lookupElement.getPsiElement();
if (elem instanceof PyClass) {
return false;
}
if (elem instanceof PyFunction && ((PyFunction)elem).getProperty() == null) {
return false;
}
return true;
}
@Nullable
private static PyType resolveToClassTypes(@NotNull PyClassPattern classPattern, @NotNull PyResolveContext resolveContext) {
List<PsiElement> elements = PyUtil.multiResolveTopPriority(classPattern.getClassNameReference(), resolveContext);
return StreamEx.of(elements)
.select(PyClass.class)
.map(e -> e.getType(resolveContext.getTypeEvalContext()))
.nonNull()
.map(PyInstantiableType::toInstance)
.collect(PyTypeUtil.toUnion());
}
private static class KeywordAttributeLookupDecorator extends LookupElementDecorator<LookupElement> {
private final boolean myAddSpacesAroundEq;
private KeywordAttributeLookupDecorator(@NotNull LookupElement e, @NotNull PsiElement settingsAnchor) {
super(e);
myAddSpacesAroundEq = PythonCodeStyleService.getInstance().isSpaceAroundEqInKeywordArgument(settingsAnchor.getContainingFile());
}
@Override
public @NotNull String getLookupString() {
return super.getLookupString() + (myAddSpacesAroundEq ? " = " : "=");
}
@Override
public Set<String> getAllLookupStrings() {
return Collections.singleton(getLookupString());
}
@Override
public void renderElement(LookupElementPresentation presentation) {
super.renderElement(presentation);
presentation.setItemText(getLookupString());
}
@Override
public void handleInsert(@NotNull InsertionContext context) {
OverwriteEqualsInsertHandler.INSTANCE.handleInsert(context, this);
}
}
}

View File

@@ -7,12 +7,13 @@ import com.intellij.psi.PsiElementResolveResult;
import com.intellij.psi.ResolveResult;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtilRt;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.PyImportElement;
import com.jetbrains.python.psi.PyQualifiedExpression;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.jetbrains.python.psi.PyUtil.as;
public class PyTargetReference extends PyReferenceImpl {
@@ -49,6 +50,20 @@ public class PyTargetReference extends PyReferenceImpl {
if (importElement != null && myElement == importElement.getAsNameElement()) {
return ArrayUtilRt.EMPTY_OBJECT_ARRAY;
}
PyClassPattern classPattern = getContainingClassPatternIfInAttributeNamePosition();
if (classPattern != null) {
TypeEvalContext context = TypeEvalContext.codeCompletion(myElement.getProject(), myElement.getContainingFile());
return PyKeywordPatternReference.collectClassAttributeVariants(myElement, classPattern, context);
}
return super.getVariants();
}
@Nullable
private PyClassPattern getContainingClassPatternIfInAttributeNamePosition() {
PsiElement grandParent = myElement.getParent().getParent();
if (myElement.getParent() instanceof PyCapturePattern && grandParent instanceof PyPatternArgumentList) {
return as(grandParent.getParent(), PyClassPattern.class);
}
return null;
}
}

View File

@@ -0,0 +1,12 @@
import dataclasses
@dataclasses.dataclass
class C:
count: int
name: str
match C(count=42, name='foo'):
case C(<caret>):
pass

View File

@@ -0,0 +1,11 @@
import typing
class C(typing.NamedTuple):
count: int
name: str
match C(count=42, name='foo'):
case C(<caret>):
pass

View File

@@ -0,0 +1,6 @@
class C:
class_attr = 42
match C():
case C(class_attr=<caret>):
pass

View File

@@ -0,0 +1,6 @@
class C:
class_attr = 42
match C():
case C(cla<caret>):
pass

View File

@@ -0,0 +1,8 @@
class C:
def __init__(self):
self.__attr = 42
match C():
case C(__<caret>):
pass

View File

@@ -0,0 +1,8 @@
class C:
def __init__(self):
self.__attr = 42
match C():
case C(__<caret>):
pass

View File

@@ -0,0 +1,7 @@
class C:
attr = 42
match C():
case C(<caret>):
pass

View File

@@ -0,0 +1,7 @@
class C:
def __init__(self):
self.instance_attr = 42
match C():
case C(instance_attr=<caret>=True):
pass

View File

@@ -0,0 +1,7 @@
class C:
def __init__(self):
self.instance_attr = 42
match C():
case C(instance<caret>=True):
pass

View File

@@ -0,0 +1,12 @@
class Base:
def __init__(self):
self.inherited_attr = 42
class C(Base):
pass
match C():
case C(inherited_attr=<caret>):
pass

View File

@@ -0,0 +1,12 @@
class Base:
def __init__(self):
self.inherited_attr = 42
class C(Base):
pass
match C():
case C(inh<caret>):
pass

View File

@@ -0,0 +1,8 @@
class C:
class InnerClass:
pass
match C():
case C(Inner<caret>):
pass

View File

@@ -0,0 +1,7 @@
class C:
def __init__(self):
self.instance_attr = 42
match C():
case C(instance_attr=<caret>):
pass

View File

@@ -0,0 +1,7 @@
class C:
def __init__(self):
self.instance_attr = 42
match C():
case C(ins<caret>):
pass

View File

@@ -0,0 +1,8 @@
class C:
def method(self):
pass
match C():
case C(met<caret>):
pass

View File

@@ -0,0 +1,9 @@
class C:
@property
def prop(self):
pass
match C():
case C(prop=<caret>):
pass

View File

@@ -0,0 +1,9 @@
class C:
@property
def prop(self):
pass
match C():
case C(pro<caret>):
pass

View File

@@ -0,0 +1,8 @@
class C:
def __init__(self):
self.attr = 42
match C():
case C(attr = <caret>):
pass

View File

@@ -0,0 +1,8 @@
class C:
def __init__(self):
self.attr = 42
match C():
case C(attr<caret>):
pass

View File

@@ -0,0 +1,6 @@
class C:
pass
match C():
case C(__<caret>):
pass

View File

@@ -0,0 +1,7 @@
class C:
attr1 = 42
match C():
case C(attr1=42):
pass

View File

@@ -0,0 +1,7 @@
class C:
attr1 = 42
match C():
case C(att<caret>r2=42):
pass

View File

@@ -0,0 +1,7 @@
class C:
attr1 = 42
match C():
case C(attr1 = <caret>42):
pass

View File

@@ -0,0 +1,7 @@
class C:
attr1 = 42
match C():
case C(att<caret>r2 = 42):
pass

View File

@@ -0,0 +1,9 @@
class Class:
def __init__(self, foo):
self.foo = foo
match Class(1, 2):
case Class(<error descr="Unresolved reference 'baz'">baz</error>=42):
pass

View File

@@ -0,0 +1,9 @@
class Class:
def __init__(self, foo):
self.foo = foo
match Class(1, 2):
case Class(f<caret>oo=42):
pass

View File

@@ -0,0 +1,9 @@
class Class:
def __init__(self, foo):
self.bar = foo
match Class(1, 2):
case Class(bar=42):
pass

View File

@@ -0,0 +1,9 @@
class Class:
foo = 42
match Class():
case Class(foo=42):
# <ref>
pass

View File

@@ -0,0 +1,13 @@
class Base:
foo = 42
class Class(Base):
pass
match Class():
case Class(foo=42):
# <ref>
pass

View File

@@ -0,0 +1,14 @@
class Base:
def __init__(self, foo):
self.foo = foo
class Class(Base):
pass
match Class(1):
case Class(foo=42):
# <ref>
pass

View File

@@ -0,0 +1,15 @@
class Base:
@property
def foo(self):
return 42
class Class(Base):
pass
match Class():
case Class(foo=42):
# <ref>
pass

View File

@@ -0,0 +1,10 @@
class Class:
def __init__(self, foo):
self.foo = foo
match Class(1):
case Class(foo=42):
# <ref>
pass

View File

@@ -0,0 +1,11 @@
class Class:
@property
def foo(self):
return 42
match Class():
case Class(foo=42):
# <ref>
pass

View File

@@ -2,6 +2,7 @@
package com.jetbrains.python;
import com.intellij.codeInsight.completion.impl.CamelHumpMatcher;
import com.intellij.codeInsight.lookup.Lookup;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.openapi.module.Module;
import com.intellij.testFramework.PsiTestUtil;
@@ -10,6 +11,7 @@ import com.intellij.testFramework.fixtures.TestLookupElementPresentation;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.codeInsight.completion.PyModuleNameCompletionContributor;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.formatter.PyCodeStyleSettings;
import com.jetbrains.python.inspections.PyMethodParametersInspection;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.types.PyNamedTupleType;
@@ -62,6 +64,13 @@ public class Py3CompletionTest extends PyTestCase {
myFixture.checkResultByFile(testName + ".after.py");
}
private void doTabTest() {
myFixture.configureByFile(getTestName(true) + ".py");
myFixture.completeBasic();
myFixture.finishLookup(Lookup.REPLACE_SELECT_CHAR);
myFixture.checkResultByFile(getTestName(true) + ".after.py");
}
public void doNegativeTest() {
final String testName = getTestName(true);
myFixture.configureByFile(testName + ".py");
@@ -545,6 +554,102 @@ public class Py3CompletionTest extends PyTestCase {
assertTrue(ContainerUtil.exists(variants, v -> v.getLookupString().equals("join")));
}
// PY-48012
public void testInstanceAttributesSuggestedForKeywordPatterns() {
doTest();
}
// PY-48012
public void testClassAttributesSuggestedForKeywordPatterns() {
doTest();
}
// PY-48012
public void testInheritedAttributesSuggestedForKeywordPatterns() {
doTest();
}
// PY-48012
public void testPropertiesSuggestedForKeywordPatterns() {
doTest();
}
// PY-48012
public void testExistingKeywordPattern() {
doTest();
}
// PY-48012
public void testEqualSignRenderedInKeywordPatternVariant() {
myFixture.configureByFile(getTestName(true) + ".py");
LookupElement[] variants = myFixture.completeBasic();
assertNotNull(variants);
TestLookupElementPresentation presentation = new TestLookupElementPresentation();
LookupElement attrVariant = ContainerUtil.find(variants, v -> v.getLookupString().equals("attr="));
assertNotNull(attrVariant);
attrVariant.renderElement(presentation);
assertTrue(presentation.getItemText().endsWith("="));
}
// PY-48012
public void testTabCompletionOverridesEqualSignInKeywordPattern() {
doTabTest();
}
// PY-48012
public void testInnerClassesNotSuggestedForKeywordPatterns() {
doNegativeTest();
}
// PY-48012
public void testMethodsNotSuggestedForKeywordPatterns() {
doNegativeTest();
}
// PY-48012
public void testSpecialAttributesNotSuggestedForKeywordPatterns() {
doNegativeTest();
}
// PY-48012
public void testClassPrivateAttributesNotSuggestedForKeywordPatterns() {
doNegativeTest();
}
// PY-48012
public void testAttributesOfDataclassSuggestedForKeywordPatterns() {
final String testName = getTestName(true);
myFixture.configureByFile(testName + ".py");
myFixture.completeBasic();
List<String> variants = myFixture.getLookupElementStrings();
assertContainsElements(variants, "count=", "name=");
}
// PY-48012
public void testAttributesOfNamedTupleSuggestedForKeywordPatterns() {
final String testName = getTestName(true);
myFixture.configureByFile(testName + ".py");
myFixture.completeBasic();
List<String> variants = myFixture.getLookupElementStrings();
assertContainsElements(variants, "count=", "name=");
}
// PY-48012
public void testSpacesAroundEqualSignForKeywordPatternsIfEnabled() {
getPythonCodeStyleSettings().SPACE_AROUND_EQ_IN_KEYWORD_ARGUMENT = true;
doTest();
}
// PY-48012
public void testTabCompletionOverridesSpacesAroundEqualSignInKeywordPatternIfEnabled() {
getPythonCodeStyleSettings().SPACE_AROUND_EQ_IN_KEYWORD_ARGUMENT = true;
doTabTest();
}
private @NotNull PyCodeStyleSettings getPythonCodeStyleSettings() {
return getCodeStyleSettings().getCustomSettings(PyCodeStyleSettings.class);
}
@Override
protected String getTestDataPath() {
return super.getTestDataPath() + "/completion";

View File

@@ -826,4 +826,35 @@ public class Py3ResolveTest extends PyResolveTestCase {
final PyFunction function = assertInstanceOf(ScopeUtil.getScopeOwner(target), PyFunction.class);
assertEquals("g", function.getName());
}
// PY-48012
public void testKeywordPatternResolvesToInstanceAttribute() {
assertResolvesTo(PyTargetExpression.class, "foo");
}
// PY-48012
public void testKeywordPatternResolvesToClassAttribute() {
assertResolvesTo(PyTargetExpression.class, "foo");
}
// PY-48012
public void testKeywordPatternResolvesToProperty() {
assertResolvesTo(PyFunction.class, "foo");
}
// PY-48012
public void testKeywordPatternResolvesToInheritedInstanceAttribute() {
assertResolvesTo(PyTargetExpression.class, "foo");
}
// PY-48012
public void testKeywordPatternResolvesToInheritedClassAttribute() {
assertResolvesTo(PyTargetExpression.class, "foo");
}
// PY-48012
public void testKeywordPatternResolvesToInheritedProperty() {
assertResolvesTo(PyFunction.class, "foo");
}
}

View File

@@ -886,6 +886,11 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase {
doMultiFileTest();
}
// PY-48012
public void testUnresolvedKeywordPattern() {
doTest();
}
@NotNull
@Override
protected Class<? extends PyInspection> getInspectionClass() {

View File

@@ -363,6 +363,11 @@ public class PyRenameTest extends PyTestCase {
doMultiFileTest("bar.pyi");
}
// PY-48012
public void testRenameKeywordParameter() {
doTest("bar");
}
private void renameWithDocStringFormat(DocStringFormat format, final String newName) {
runWithDocStringFormat(format, () -> doTest(newName));
}