mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-16055 Add quickfix and tests for functions with incorrect return annotation
Add quickfix for functions whose return type doesn't match their annotated type.
This commit is contained in:
@@ -23,6 +23,7 @@ import com.intellij.util.ArrayFactory;
|
||||
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
|
||||
import com.jetbrains.python.psi.stubs.PyFunctionStub;
|
||||
import com.jetbrains.python.psi.types.PyType;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -49,6 +50,9 @@ public interface PyFunction extends PsiNamedElement, StubBasedPsiElement<PyFunct
|
||||
@Nullable
|
||||
ASTNode getNameNode();
|
||||
|
||||
@Nullable
|
||||
PyType getReturnStatementType(TypeEvalContext typeEvalContext);
|
||||
|
||||
@Nullable
|
||||
PyType getReturnTypeFromDocString();
|
||||
|
||||
|
||||
@@ -125,6 +125,9 @@ QFIX.NAME.make.static=Make method static
|
||||
#PyMakeFunctionFromMethodQuickFix
|
||||
QFIX.NAME.make.function=Make function from method
|
||||
|
||||
#PyMakeFunctionReturnTypeQuickFix
|
||||
QFIX.NAME.make.$0.return.$1=Make ''{0}'' return ''{1}''
|
||||
|
||||
#PyImplementMethodsQuickFix
|
||||
QFIX.NAME.implement.methods=Implement abstract methods
|
||||
|
||||
|
||||
@@ -24,12 +24,12 @@ import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiElementVisitor;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.containers.hash.LinkedHashMap;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
|
||||
import com.jetbrains.python.documentation.PythonDocumentationProvider;
|
||||
import com.jetbrains.python.inspections.quickfix.PyMakeFunctionReturnTypeQuickFix;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.types.*;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
@@ -103,8 +103,11 @@ public class PyTypeCheckerInspection extends PyInspection {
|
||||
if (!PyTypeChecker.match(expected, actual, myTypeEvalContext)) {
|
||||
final String expectedName = PythonDocumentationProvider.getTypeName(expected, myTypeEvalContext);
|
||||
final String actualName = PythonDocumentationProvider.getTypeName(actual, myTypeEvalContext);
|
||||
PyMakeFunctionReturnTypeQuickFix localQuickFix = new PyMakeFunctionReturnTypeQuickFix(function, actualName, myTypeEvalContext);
|
||||
PyMakeFunctionReturnTypeQuickFix globalQuickFix = new PyMakeFunctionReturnTypeQuickFix(function, null, myTypeEvalContext);
|
||||
registerProblem(returnExpr != null ? returnExpr : node,
|
||||
String.format("Expected type '%s', got '%s' instead", expectedName, actualName));
|
||||
String.format("Expected type '%s', got '%s' instead", expectedName, actualName),
|
||||
localQuickFix, globalQuickFix);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,7 +144,7 @@ public class PyTypeCheckerInspection extends PyInspection {
|
||||
|
||||
@Override
|
||||
public void visitPyReturnStatement(PyReturnStatement node) {
|
||||
if (PsiTreeUtil.getParentOfType(node, ScopeOwner.class, true) == myFunction) {
|
||||
if (ScopeUtil.getScopeOwner(node) == myFunction) {
|
||||
myHasReturns = true;
|
||||
}
|
||||
}
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2000-2016 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.python.inspections.quickfix;
|
||||
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.ProblemDescriptor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.PsiComment;
|
||||
import com.intellij.psi.SmartPointerManager;
|
||||
import com.intellij.psi.SmartPsiElementPointer;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.documentation.PythonDocumentationProvider;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* @author lada
|
||||
*/
|
||||
public class PyMakeFunctionReturnTypeQuickFix implements LocalQuickFix {
|
||||
private final SmartPsiElementPointer<PyFunction> myFunction;
|
||||
private final SmartPsiElementPointer<PyAnnotation> myAnnotation;
|
||||
private final SmartPsiElementPointer<PsiComment> myTypeCommentAnnotation;
|
||||
private final String myReturnTypeName;
|
||||
|
||||
public PyMakeFunctionReturnTypeQuickFix(@NotNull PyFunction function, @Nullable String returnTypeName, @NotNull TypeEvalContext context) {
|
||||
final SmartPointerManager manager = SmartPointerManager.getInstance(function.getProject());
|
||||
myFunction = manager.createSmartPsiElementPointer(function);
|
||||
PyAnnotation annotation = function.getAnnotation();
|
||||
myAnnotation = annotation != null ? manager.createSmartPsiElementPointer(annotation) : null;
|
||||
PsiComment typeCommentAnnotation = function.getTypeComment();
|
||||
myTypeCommentAnnotation = typeCommentAnnotation != null ? manager.createSmartPsiElementPointer(typeCommentAnnotation) : null;
|
||||
myReturnTypeName = (returnTypeName == null) ? PythonDocumentationProvider.getTypeName(function.getReturnStatementType(context), context) : returnTypeName;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getName() {
|
||||
PyFunction function = myFunction.getElement();
|
||||
String functionName = function != null ? function.getName() : "function";
|
||||
return PyBundle.message("QFIX.NAME.make.$0.return.$1", functionName, myReturnTypeName);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getFamilyName() {
|
||||
return PyBundle.message("QFIX.NAME.make.$0.return.$1", "function", "inferred type");
|
||||
}
|
||||
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);
|
||||
if (myAnnotation != null) {
|
||||
final PyAnnotation annotation = myAnnotation.getElement();
|
||||
if (annotation != null) {
|
||||
final PyExpression annotationExpr = annotation.getValue();
|
||||
if (annotationExpr == null) return;
|
||||
annotationExpr.replace(elementGenerator.createExpressionFromText(LanguageLevel.PYTHON30, myReturnTypeName));
|
||||
}
|
||||
}
|
||||
else if (myTypeCommentAnnotation != null) {
|
||||
final PsiComment typeComment = myTypeCommentAnnotation.getElement();
|
||||
if (typeComment != null) {
|
||||
final StringBuilder typeCommentAnnotation = new StringBuilder(typeComment.getText());
|
||||
typeCommentAnnotation.delete(typeCommentAnnotation.indexOf("->"), typeCommentAnnotation.length());
|
||||
typeCommentAnnotation.append("-> ").append(myReturnTypeName);
|
||||
final PsiComment newTypeComment = elementGenerator.createFromText(LanguageLevel.PYTHON27, PsiComment.class, typeCommentAnnotation.toString());
|
||||
typeComment.replace(newTypeComment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -517,7 +517,7 @@ public class PyFunctionImpl extends PyBaseElementImpl<PyFunctionStub> implements
|
||||
|
||||
@Override
|
||||
public void visitPyReturnStatement(PyReturnStatement node) {
|
||||
if (PsiTreeUtil.getParentOfType(node, ScopeOwner.class, true) == myFunction) {
|
||||
if (ScopeUtil.getScopeOwner(node) == myFunction) {
|
||||
final PyExpression expr = node.getExpression();
|
||||
PyType returnType;
|
||||
returnType = expr == null ? PyNoneType.INSTANCE : myContext.getType(expr);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
def f(x):
|
||||
# type: (int) -> str
|
||||
return <warning descr="Expected type 'str', got 'int' instead">42<caret></warning>
|
||||
@@ -0,0 +1,3 @@
|
||||
def f(x):
|
||||
# type: (int) -> int
|
||||
return 42
|
||||
@@ -0,0 +1,2 @@
|
||||
def f(x) -> str:
|
||||
return <warning descr="Expected type 'str', got 'int' instead">42<caret></warning>
|
||||
@@ -0,0 +1,2 @@
|
||||
def f(x) -> int:
|
||||
return 42
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2000-2016 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.python.quickFixes;
|
||||
|
||||
import com.intellij.testFramework.TestDataPath;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.PyQuickFixTestCase;
|
||||
import com.jetbrains.python.inspections.PyTypeCheckerInspection;
|
||||
import com.jetbrains.python.psi.LanguageLevel;
|
||||
|
||||
/**
|
||||
* @author lada
|
||||
*/
|
||||
@TestDataPath("$CONTENT_ROOT/../testData/quickFixes/PyMakeFunctionReturnTypeQuickFixTest/")
|
||||
public class PyMakeFunctionReturnTypeQuickFixTest extends PyQuickFixTestCase {
|
||||
public void testOneReturn() {
|
||||
doQuickFixTest(PyTypeCheckerInspection.class, PyBundle.message("QFIX.NAME.make.$0.return.$1", "f", "int"), LanguageLevel.PYTHON27);
|
||||
}
|
||||
|
||||
public void testPy3OneReturn() {
|
||||
doQuickFixTest(PyTypeCheckerInspection.class, PyBundle.message("QFIX.NAME.make.$0.return.$1", "f", "int"), LanguageLevel.PYTHON30);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user