mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-20 13:31:28 +07:00
PY-49283 Add quick fix for from future import annotations
(cherry picked from commit 7d26e211c67f2574078e1f0e8951f744889d82d6) IJ-CR-10316 GitOrigin-RevId: 34d4bc72b11613c0603587c1c0a9e640b0add023
This commit is contained in:
committed by
intellij-monorepo-bot
parent
c42fb45d25
commit
1fe7e5443c
@@ -1087,6 +1087,7 @@ QFIX.remove.type.comment=Remove the type comment
|
||||
QFIX.remove.annotation=Remove the annotation
|
||||
QFIX.replace.with.type.name=Replace with the type name
|
||||
QFIX.replace.with.old.union.style=Replace with an old-style Union
|
||||
QFIX.add.from.future.import.annotations=Add 'from __future__ import annotations'
|
||||
|
||||
# PyInspectionsSuppressor
|
||||
INSP.python.suppressor.suppress.for.function=Suppress for a function
|
||||
|
||||
@@ -136,19 +136,14 @@ public class PyCompatibilityInspection extends PyInspection {
|
||||
protected void registerProblem(@NotNull PsiElement element,
|
||||
@NotNull TextRange range,
|
||||
@NotNull @InspectionMessage String message,
|
||||
@Nullable LocalQuickFix quickFix,
|
||||
boolean asError) {
|
||||
boolean asError,
|
||||
LocalQuickFix @NotNull ... fixes) {
|
||||
if (element.getTextLength() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
range = range.shiftRight(-element.getTextRange().getStartOffset());
|
||||
if (quickFix != null) {
|
||||
myHolder.registerProblem(element, range, message, quickFix);
|
||||
}
|
||||
else {
|
||||
myHolder.registerProblem(element, range, message);
|
||||
}
|
||||
myHolder.registerProblem(element, range, message, fixes);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -244,8 +244,8 @@ public abstract class CompatibilityVisitor extends PyAnnotator {
|
||||
PyPsiBundle.message("INSP.compatibility.feature.support.string.literal.prefix", prefix),
|
||||
node,
|
||||
TextRange.create(elementStart, elementStart + element.getPrefixLength()),
|
||||
new RemovePrefixQuickFix(prefix),
|
||||
true);
|
||||
true,
|
||||
new RemovePrefixQuickFix(prefix));
|
||||
}
|
||||
|
||||
if (seenBytes && seenNonBytes) {
|
||||
@@ -452,7 +452,6 @@ public abstract class CompatibilityVisitor extends PyAnnotator {
|
||||
PyPsiBundle.message("INSP.compatibility.feature.support.this.syntax"),
|
||||
node,
|
||||
asyncNode.getTextRange(),
|
||||
null,
|
||||
true);
|
||||
}
|
||||
}
|
||||
@@ -489,17 +488,13 @@ public abstract class CompatibilityVisitor extends PyAnnotator {
|
||||
protected abstract void registerProblem(@NotNull PsiElement node,
|
||||
@NotNull TextRange range,
|
||||
@NotNull @InspectionMessage String message,
|
||||
@Nullable LocalQuickFix localQuickFix,
|
||||
boolean asError);
|
||||
boolean asError,
|
||||
LocalQuickFix @NotNull ... fixes);
|
||||
|
||||
protected void registerProblem(@NotNull PsiElement node,
|
||||
@NotNull @InspectionMessage String message,
|
||||
@Nullable LocalQuickFix localQuickFix) {
|
||||
registerProblem(node, node.getTextRange(), message, localQuickFix, true);
|
||||
}
|
||||
|
||||
protected void registerProblem(@NotNull PsiElement node, @NotNull @InspectionMessage String message) {
|
||||
registerProblem(node, message, null);
|
||||
LocalQuickFix @NotNull ... fixes) {
|
||||
registerProblem(node, node.getTextRange(), message, true, fixes);
|
||||
}
|
||||
|
||||
protected void setVersionsToProcess(@NotNull List<LanguageLevel> versionsToProcess) {
|
||||
@@ -509,8 +504,8 @@ public abstract class CompatibilityVisitor extends PyAnnotator {
|
||||
protected void registerForAllMatchingVersions(@NotNull Predicate<LanguageLevel> levelPredicate,
|
||||
@NotNull @Nls String suffix,
|
||||
@NotNull Iterable<Pair<? extends PsiElement, TextRange>> nodesWithRanges,
|
||||
@Nullable LocalQuickFix localQuickFix,
|
||||
boolean asError) {
|
||||
boolean asError,
|
||||
LocalQuickFix... fixes) {
|
||||
final List<String> levels = myVersionsToProcess
|
||||
.stream()
|
||||
.filter(levelPredicate)
|
||||
@@ -522,7 +517,7 @@ public abstract class CompatibilityVisitor extends PyAnnotator {
|
||||
@InspectionMessage String message = PyPsiBundle.message("INSP.compatibility.inspection.unsupported.feature.prefix",
|
||||
levels.size(), versions, suffix);
|
||||
for (Pair<? extends PsiElement, TextRange> nodeWithRange : nodesWithRanges) {
|
||||
registerProblem(nodeWithRange.first, nodeWithRange.second, message, localQuickFix, asError);
|
||||
registerProblem(nodeWithRange.first, nodeWithRange.second, message, asError, fixes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -530,41 +525,27 @@ public abstract class CompatibilityVisitor extends PyAnnotator {
|
||||
protected void registerForAllMatchingVersions(@NotNull Predicate<LanguageLevel> levelPredicate,
|
||||
@NotNull @Nls String suffix,
|
||||
@NotNull Iterable<? extends PsiElement> nodes,
|
||||
@Nullable LocalQuickFix localQuickFix) {
|
||||
LocalQuickFix... fixes) {
|
||||
final List<Pair<? extends PsiElement, TextRange>> nodesWithRanges =
|
||||
ContainerUtil.map(nodes, node -> Pair.createNonNull(node, node.getTextRange()));
|
||||
registerForAllMatchingVersions(levelPredicate, suffix, nodesWithRanges, localQuickFix, true);
|
||||
registerForAllMatchingVersions(levelPredicate, suffix, nodesWithRanges, true, fixes);
|
||||
}
|
||||
|
||||
protected void registerForAllMatchingVersions(@NotNull Predicate<LanguageLevel> levelPredicate,
|
||||
@NotNull @Nls String suffix,
|
||||
@NotNull PsiElement node,
|
||||
@NotNull TextRange range,
|
||||
@Nullable LocalQuickFix localQuickFix,
|
||||
boolean asError) {
|
||||
boolean asError,
|
||||
LocalQuickFix... fixes) {
|
||||
final List<Pair<? extends PsiElement, TextRange>> nodesWithRanges = Collections.singletonList(Pair.createNonNull(node, range));
|
||||
registerForAllMatchingVersions(levelPredicate, suffix, nodesWithRanges, localQuickFix, asError);
|
||||
registerForAllMatchingVersions(levelPredicate, suffix, nodesWithRanges, asError, fixes);
|
||||
}
|
||||
|
||||
protected void registerForAllMatchingVersions(@NotNull Predicate<LanguageLevel> levelPredicate,
|
||||
@NotNull @Nls String suffix,
|
||||
@NotNull PsiElement node,
|
||||
@Nullable LocalQuickFix localQuickFix,
|
||||
boolean asError) {
|
||||
registerForAllMatchingVersions(levelPredicate, suffix, node, node.getTextRange(), localQuickFix, asError);
|
||||
}
|
||||
|
||||
protected void registerForAllMatchingVersions(@NotNull Predicate<LanguageLevel> levelPredicate,
|
||||
@NotNull @Nls String suffix,
|
||||
@NotNull PsiElement node,
|
||||
@Nullable LocalQuickFix localQuickFix) {
|
||||
registerForAllMatchingVersions(levelPredicate, suffix, node, localQuickFix, true);
|
||||
}
|
||||
|
||||
protected void registerForAllMatchingVersions(@NotNull Predicate<LanguageLevel> levelPredicate,
|
||||
@NotNull @Nls String suffix,
|
||||
@NotNull PsiElement node) {
|
||||
registerForAllMatchingVersions(levelPredicate, suffix, node, null, true);
|
||||
LocalQuickFix... fixes) {
|
||||
registerForAllMatchingVersions(levelPredicate, suffix, node, node.getTextRange(), true, fixes);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -737,10 +718,11 @@ public abstract class CompatibilityVisitor extends PyAnnotator {
|
||||
if (node.getOperator() != PyTokenTypes.OR) return;
|
||||
|
||||
final PsiFile file = node.getContainingFile();
|
||||
final boolean isInAnnotation = PsiTreeUtil.getParentOfType(node, PyAnnotation.class, false, ScopeOwner.class) != null;
|
||||
if (file == null ||
|
||||
file instanceof PyFile &&
|
||||
((PyFile)file).hasImportFromFuture(FutureFeature.ANNOTATIONS) &&
|
||||
PsiTreeUtil.getParentOfType(node, PyAnnotation.class, false, ScopeOwner.class) != null) {
|
||||
isInAnnotation) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -761,10 +743,16 @@ public abstract class CompatibilityVisitor extends PyAnnotator {
|
||||
|
||||
final Ref<PyType> refType = PyTypingTypeProvider.getType(node, context);
|
||||
if (refType != null && refType.get() instanceof PyUnionType) {
|
||||
registerForAllMatchingVersions(level -> level.isOlderThan(LanguageLevel.PYTHON310),
|
||||
PyPsiBundle.message("INSP.compatibility.new.union.syntax.not.available.in.earlier.version"),
|
||||
node,
|
||||
new ReplaceWithOldStyleUnionQuickFix());
|
||||
if (isInAnnotation) {
|
||||
registerForAllMatchingVersions(level -> level.isOlderThan(LanguageLevel.PYTHON310),
|
||||
PyPsiBundle.message("INSP.compatibility.new.union.syntax.not.available.in.earlier.version"),
|
||||
node, new ReplaceWithOldStyleUnionQuickFix(), new AddFromFutureImportAnnotationsQuickFix());
|
||||
}
|
||||
else {
|
||||
registerForAllMatchingVersions(level -> level.isOlderThan(LanguageLevel.PYTHON310),
|
||||
PyPsiBundle.message("INSP.compatibility.new.union.syntax.not.available.in.earlier.version"),
|
||||
node, new ReplaceWithOldStyleUnionQuickFix());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -829,4 +817,17 @@ public abstract class CompatibilityVisitor extends PyAnnotator {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class AddFromFutureImportAnnotationsQuickFix implements LocalQuickFix {
|
||||
@Override
|
||||
public @NotNull String getFamilyName() {
|
||||
return PyPsiBundle.message("QFIX.add.from.future.import.annotations");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
final PsiFile file = descriptor.getPsiElement().getContainingFile();
|
||||
AddImportHelper.addOrUpdateFromImportStatement(file, "__future__", "annotations", null, AddImportHelper.ImportPriority.FUTURE, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.intellij.codeInspection.ProblemHighlightType;
|
||||
import com.intellij.codeInspection.ex.ProblemDescriptorImpl;
|
||||
import com.intellij.codeInspection.ex.QuickFixWrapper;
|
||||
import com.intellij.codeInspection.util.InspectionMessage;
|
||||
import com.intellij.lang.annotation.AnnotationBuilder;
|
||||
import com.intellij.lang.annotation.AnnotationHolder;
|
||||
import com.intellij.lang.annotation.HighlightSeverity;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
@@ -52,16 +53,22 @@ public class UnsupportedFeatures extends CompatibilityVisitor {
|
||||
@Override
|
||||
protected void registerProblem(@NotNull PsiElement node,
|
||||
@NotNull TextRange range,
|
||||
@NotNull @InspectionMessage String message,
|
||||
@Nullable LocalQuickFix localQuickFix,
|
||||
boolean asError) {
|
||||
@NotNull String message,
|
||||
boolean asError,
|
||||
LocalQuickFix @NotNull ... fixes) {
|
||||
if (range.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
HighlightSeverity severity = asError ? HighlightSeverity.ERROR : HighlightSeverity.WARNING;
|
||||
if (localQuickFix != null) {
|
||||
getHolder().newAnnotation(severity, message).range(range).withFix(createIntention(node, message, localQuickFix)).create();
|
||||
if (fixes.length > 0) {
|
||||
AnnotationBuilder annotationBuilder = getHolder().newAnnotation(severity, message).range(range);
|
||||
for (LocalQuickFix fix: fixes) {
|
||||
if (fix != null) {
|
||||
annotationBuilder = annotationBuilder.withFix(createIntention(node, message, fix));
|
||||
}
|
||||
}
|
||||
annotationBuilder.create();
|
||||
}
|
||||
else {
|
||||
getHolder().newAnnotation(severity, message).range(range).create();
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
class A:
|
||||
pass
|
||||
|
||||
assert isinstance(A(), <warning descr="Python versions 2.7, 3.5, 3.6, 3.7, 3.8, 3.9 do not allow writing union types as X | Y"><caret>int | str</warning>)
|
||||
@@ -0,0 +1,4 @@
|
||||
class A:
|
||||
pass
|
||||
|
||||
assert issubclass(A, <warning descr="Python versions 2.7, 3.5, 3.6, 3.7, 3.8, 3.9 do not allow writing union types as X | Y"><caret>int | str</warning>)
|
||||
@@ -0,0 +1,5 @@
|
||||
from typing import Union
|
||||
|
||||
|
||||
def foo() -> <warning descr="Python versions 2.7, 3.5, 3.6, 3.7, 3.8, 3.9 do not allow writing union types as X | Y"><caret>int | Union[str, bool]</warning>:
|
||||
return 42
|
||||
@@ -0,0 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Union
|
||||
|
||||
|
||||
def foo() -> int | Union[str, bool]:
|
||||
return 42
|
||||
@@ -0,0 +1,4 @@
|
||||
from typing import Union
|
||||
|
||||
|
||||
a<warning descr="Python versions 2.7, 3.5 do not support variable annotations">: <warning descr="Python versions 2.7, 3.5, 3.6, 3.7, 3.8, 3.9 do not allow writing union types as X | Y"><caret>int | Union[str, bool]</warning></warning> = 42
|
||||
@@ -0,0 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Union
|
||||
|
||||
|
||||
a: <caret>int | Union[str, bool] = 42
|
||||
@@ -66,4 +66,12 @@ public abstract class PyQuickFixTestCase extends PyTestCase {
|
||||
myFixture.launchAction(intentionAction);
|
||||
myFixture.checkResultByFile(testFileName + ".py", testFileName + "_after.py", true);
|
||||
}
|
||||
|
||||
protected void doNegativeTest(@NotNull Class inspectionClass, @NotNull String hint) {
|
||||
final var testFileName = getTestName(true);
|
||||
myFixture.enableInspections(inspectionClass);
|
||||
myFixture.configureByFile(testFileName + ".py");
|
||||
myFixture.checkHighlighting(true, false, false);
|
||||
assertEmpty(myFixture.filterAvailableIntentions(hint));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. and contributors. 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.quickFixes
|
||||
|
||||
import com.jetbrains.python.PyPsiBundle
|
||||
import com.jetbrains.python.PyQuickFixTestCase
|
||||
import com.jetbrains.python.inspections.PyCompatibilityInspection
|
||||
|
||||
class PyAddFromFutureImportAnnotationsQuickFixTest: PyQuickFixTestCase() {
|
||||
// PY-44974
|
||||
fun testNotSuggestedOnBitwiseOrUnionInIsInstance() {
|
||||
doNegativeTest(PyPsiBundle.message("QFIX.add.from.future.import.annotations"))
|
||||
}
|
||||
|
||||
// PY-44974
|
||||
fun testNotSuggestedOnBitwiseOrUnionInIsSubclass() {
|
||||
doNegativeTest(PyPsiBundle.message("QFIX.add.from.future.import.annotations"))
|
||||
}
|
||||
|
||||
// PY-44974
|
||||
fun testSuggestedOnBitwiseOrUnionInReturnAnnotation() {
|
||||
doQuickFixTest(PyCompatibilityInspection::class.java,
|
||||
PyPsiBundle.message("QFIX.add.from.future.import.annotations"))
|
||||
}
|
||||
|
||||
// PY-44974
|
||||
fun testSuggestedOnBitwiseOrUnionInVariableAnnotation() {
|
||||
doQuickFixTest(PyCompatibilityInspection::class.java, PyPsiBundle.message("QFIX.add.from.future.import.annotations"))
|
||||
}
|
||||
|
||||
private fun doNegativeTest(hint: String) {
|
||||
super.doNegativeTest(PyCompatibilityInspection::class.java, hint)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user