Highlight the case when type comment and annotation are both specified (PY-20530)

This commit is contained in:
Semyon Proshev
2018-06-13 23:12:48 +03:00
parent 9c0002fb67
commit 746a89b076
5 changed files with 79 additions and 0 deletions
@@ -102,6 +102,25 @@ class PyTypeHintsInspection : PyInspection() {
}
}
override fun visitPyElement(node: PyElement?) {
super.visitPyElement(node)
if (node is PyTypeCommentOwner && node is PyAnnotationOwner && node.typeCommentAnnotation != null) {
val message = "Type(s) specified both in type comment and annotation"
if (node is PyFunction) {
if (node.annotationValue != null || node.parameterList.parameters.any { it is PyNamedParameter && it.annotationValue != null }) {
registerProblem(node.typeComment, message, RemoveElementQuickFix("Remove type comment"))
registerProblem(node.nameIdentifier, message, RemoveFunctionAnnotations())
}
}
else if (node.annotationValue != null) {
registerProblem(node.typeComment, message, RemoveElementQuickFix("Remove type comment"))
registerProblem(node.annotation, message, RemoveElementQuickFix("Remove annotation"))
}
}
}
private fun checkTypeVarPlacement(call: PyCallExpression, target: PyExpression?) {
if (target == null) {
registerProblem(call, "A 'TypeVar()' expression must always directly be assigned to a variable")
@@ -509,6 +528,29 @@ class PyTypeHintsInspection : PyInspection() {
}
}
private class RemoveElementQuickFix(private val description: String) : LocalQuickFix {
override fun getFamilyName() = description
override fun applyFix(project: Project, descriptor: ProblemDescriptor) = descriptor.psiElement.delete()
}
private class RemoveFunctionAnnotations : LocalQuickFix {
override fun getFamilyName() = "Remove function annotations"
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val function = (descriptor.psiElement.parent as? PyFunction) ?: return
function.annotation?.delete()
function.parameterList.parameters
.asSequence()
.filterIsInstance<PyNamedParameter>()
.mapNotNull { it.annotation }
.forEach { it.delete() }
}
}
private class ReplaceWithTargetNameQuickFix(private val targetName: String) : LocalQuickFix {
override fun getFamilyName() = "Replace with target name"
@@ -0,0 +1,3 @@
def <warning descr="Type(s) specified both in type comment and annotation">b<caret>ar</warning>(a: int) -> int:
<warning descr="Type(s) specified both in type comment and annotation"># type: (int) -> int</warning>
pass
@@ -0,0 +1,3 @@
def bar(a):
# type: (int) -> int
pass
@@ -584,6 +584,32 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase {
"e2, (f2, g2), h2 = undefined() # type: <warning descr=\"Type comment cannot be matched with unpacked variables\">int, (str), str</warning>");
}
// PY-20530
public void testAnnotationAndTypeComment() {
runWithLanguageLevel(
LanguageLevel.PYTHON36,
() -> doTestByText(
"a<warning descr=\"Type(s) specified both in type comment and annotation\">: int</warning> = None <warning descr=\"Type(s) specified both in type comment and annotation\"># type: int</warning>\n" +
"\n" +
"def foo(a<warning descr=\"Type(s) specified both in type comment and annotation\">: int</warning> <warning descr=\"Type(s) specified both in type comment and annotation\"># type: int</warning>\n" +
" ,):\n" +
" pass\n" +
"\n" +
"def <warning descr=\"Type(s) specified both in type comment and annotation\">bar</warning>(a: int) -> int:\n" +
" <warning descr=\"Type(s) specified both in type comment and annotation\"># type: (int) -> int</warning>\n" +
" pass\n" +
" \n" +
"def <warning descr=\"Type(s) specified both in type comment and annotation\">baz1</warning>(a: int):\n" +
" <warning descr=\"Type(s) specified both in type comment and annotation\"># type: (int) -> int</warning>\n" +
" pass\n" +
" \n" +
"def <warning descr=\"Type(s) specified both in type comment and annotation\">baz2</warning>(a) -> int:\n" +
" <warning descr=\"Type(s) specified both in type comment and annotation\"># type: (int) -> int</warning>\n" +
" pass"
)
);
}
@NotNull
@Override
protected Class<? extends PyInspection> getInspectionClass() {
@@ -95,4 +95,9 @@ class PyTypeHintsQuickFixTest : PyQuickFixTestCase() {
fun testSelfInTypeComment() {
doQuickFixTest(PyTypeHintsInspection::class.java, "Replace with type name")
}
// PY-20530
fun testFunctionAnnotationAndTypeComment() {
doQuickFixTest(PyTypeHintsInspection::class.java, "Remove function annotations", LanguageLevel.PYTHON35)
}
}