mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Initialize type hints inspection (PY-28243)
Implement checks about typevar name, placement and redefinition
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
This inspection detects invalid usages of type hints.
|
||||
</body>
|
||||
</html>
|
||||
@@ -424,6 +424,7 @@
|
||||
<localInspection language="Python" shortName="PyNamedTupleInspection" suppressId="PyNamedTuple" displayName="Namedtuple definition" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WARNING" implementationClass="com.jetbrains.python.inspections.PyNamedTupleInspection"/>
|
||||
<localInspection language="Python" shortName="PyDataclassInspection" suppressId="PyDataclass" displayName="Dataclass definition and usages" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WARNING" implementationClass="com.jetbrains.python.inspections.PyDataclassInspection"/>
|
||||
<localInspection language="Python" shortName="PyProtocolInspection" suppressId="PyProtocol" displayName="Protocol definition and usages" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WARNING" implementationClass="com.jetbrains.python.inspections.PyProtocolInspection"/>
|
||||
<localInspection language="Python" shortName="PyTypeHintsInspection" suppressId="PyTypeHints" displayName="Type hints definitions and usages" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WARNING" implementationClass="com.jetbrains.python.inspections.PyTypeHintsInspection"/>
|
||||
|
||||
<defaultLiveTemplatesProvider implementation="com.jetbrains.python.codeInsight.liveTemplates.PyDefaultLiveTemplatesProvider"/>
|
||||
<liveTemplateContext implementation="com.jetbrains.python.codeInsight.liveTemplates.PythonTemplateContextType$General"/>
|
||||
|
||||
@@ -82,7 +82,7 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
private static final String DEQUE = "typing.Deque";
|
||||
private static final String TUPLE = "typing.Tuple";
|
||||
private static final String CLASS_VAR = "typing.ClassVar";
|
||||
private static final String TYPE_VAR = "typing.TypeVar";
|
||||
public static final String TYPE_VAR = "typing.TypeVar";
|
||||
private static final String CHAIN_MAP = "typing.ChainMap";
|
||||
private static final String UNION = "typing.Union";
|
||||
private static final String OPTIONAL = "typing.Optional";
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// 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.inspections
|
||||
|
||||
import com.intellij.codeInsight.controlflow.ControlFlowUtil
|
||||
import com.intellij.codeInspection.LocalInspectionToolSession
|
||||
import com.intellij.codeInspection.ProblemsHolder
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import com.intellij.psi.util.QualifiedName
|
||||
import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache
|
||||
import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil
|
||||
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider
|
||||
import com.jetbrains.python.psi.*
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil
|
||||
|
||||
class PyTypeHintsInspection : PyInspection() {
|
||||
|
||||
override fun buildVisitor(holder: ProblemsHolder,
|
||||
isOnTheFly: Boolean,
|
||||
session: LocalInspectionToolSession): PsiElementVisitor = Visitor(holder, session)
|
||||
|
||||
private class Visitor(holder: ProblemsHolder, session: LocalInspectionToolSession) : PyInspectionVisitor(holder, session) {
|
||||
|
||||
override fun visitPyCallExpression(node: PyCallExpression?) {
|
||||
super.visitPyCallExpression(node)
|
||||
|
||||
if (node != null) {
|
||||
val callee = node.callee as? PyReferenceExpression
|
||||
if (callee != null &&
|
||||
QualifiedName.fromDottedString(PyTypingTypeProvider.TYPE_VAR) in PyResolveUtil.resolveImportedElementQNameLocally(callee)) {
|
||||
val target = (node.parent as? PyAssignmentStatement)?.targetsToValuesMapping?.firstOrNull { it.second == node }?.first
|
||||
|
||||
checkTypeVarPlacement(node, target)
|
||||
checkTypeVarRedefinition(target)
|
||||
|
||||
if (target != null) {
|
||||
checkTypeVarName(node, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkTypeVarPlacement(call: PyCallExpression, target: PyExpression?) {
|
||||
if (target == null) {
|
||||
registerProblem(call, "A 'TypeVar()' expression must always directly be assigned to a variable")
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkTypeVarName(call: PyCallExpression, target: PyExpression) {
|
||||
val name = call.getArgument(0, "name", PyStringLiteralExpression::class.java)
|
||||
|
||||
if (name != null && name.stringValue != target.name) {
|
||||
registerProblem(name, "The argument to 'TypeVar()' must be a string equal to the variable name to which it is assigned")
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkTypeVarRedefinition(target: PyExpression?) {
|
||||
val scopeOwner = ScopeUtil.getScopeOwner(target) ?: return
|
||||
val name = target?.name ?: return
|
||||
|
||||
val instructions = ControlFlowCache.getControlFlow(scopeOwner).instructions
|
||||
val startInstruction = ControlFlowUtil.findInstructionNumberByElement(instructions, target)
|
||||
|
||||
ControlFlowUtil.iteratePrev(
|
||||
startInstruction,
|
||||
instructions,
|
||||
{ instruction ->
|
||||
if (instruction is ReadWriteInstruction &&
|
||||
instruction.num() != startInstruction &&
|
||||
name == instruction.name &&
|
||||
instruction.access.isWriteAccess) {
|
||||
registerProblem(target, "Type variables must not be redefined")
|
||||
ControlFlowUtil.Operation.BREAK
|
||||
}
|
||||
else {
|
||||
ControlFlowUtil.Operation.NEXT
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// 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.inspections;
|
||||
|
||||
import com.jetbrains.python.fixtures.PyInspectionTestCase;
|
||||
import com.jetbrains.python.psi.LanguageLevel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class PyTypeHintsInspectionTest extends PyInspectionTestCase {
|
||||
|
||||
// PY-28243
|
||||
public void testTypeVarAndTargetName() {
|
||||
doTestByText("from typing import TypeVar\n" +
|
||||
"\n" +
|
||||
"T0 = TypeVar('T0')\n" +
|
||||
"T1 = TypeVar(<warning descr=\"The argument to 'TypeVar()' must be a string equal to the variable name to which it is assigned\">'T2'</warning>)");
|
||||
}
|
||||
|
||||
// PY-28243
|
||||
public void testTypeVarPlacement() {
|
||||
runWithLanguageLevel(
|
||||
LanguageLevel.PYTHON36,
|
||||
() -> doTestByText("from typing import List, TypeVar\n" +
|
||||
"\n" +
|
||||
"T0 = TypeVar('T0')\n" +
|
||||
"a: List[T0]\n" +
|
||||
"b: List[<warning descr=\"A 'TypeVar()' expression must always directly be assigned to a variable\">TypeVar('T1')</warning>]")
|
||||
);
|
||||
}
|
||||
|
||||
// PY-28243
|
||||
public void testTypeVarRedefinition() {
|
||||
doTestByText("from typing import TypeVar\n" +
|
||||
"\n" +
|
||||
"T0 = TypeVar('T0')\n" +
|
||||
"print(T0)\n" +
|
||||
"<warning descr=\"Type variables must not be redefined\">T0</warning> = TypeVar('T0')");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Class<? extends PyInspection> getInspectionClass() {
|
||||
return PyTypeHintsInspection.class;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user