PY-20280 Fixed: Warn if class variable listed in __slots__

Introduce PyDunderSlotsInspection which detects invalid definition of __slots__ in a class
This commit is contained in:
Semyon Proshev
2016-08-10 17:29:49 +03:00
parent d9961eee78
commit 3251485499
5 changed files with 138 additions and 1 deletions
@@ -0,0 +1,5 @@
<html>
<body>
This inspection detects invalid definition of __slots__ in a class.
</body>
</html>
+1 -1
View File
@@ -383,7 +383,7 @@
<localInspection language="Python" shortName="PyAbstractClassInspection" suppressId="PyAbstractClass" displayName="Class must implement all abstract methods" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WEAK WARNING" implementationClass="com.jetbrains.python.inspections.PyAbstractClassInspection"/>
<localInspection language="Python" shortName="PyPep8NamingInspection" suppressId="PyPep8Naming" displayName="PEP 8 naming convention violation" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WEAK WARNING" implementationClass="com.jetbrains.python.inspections.PyPep8NamingInspection"/>
<localInspection language="Python" shortName="PyAssignmentToLoopOrWithParameterInspection" suppressId="PyAssignmentToLoopOrWithParameter" displayName="Assignment to 'for' loop or 'with' statement parameter" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WEAK WARNING" implementationClass="com.jetbrains.python.inspections.PyAssignmentToLoopOrWithParameterInspection"/>
<localInspection language="Python" shortName="PyDunderSlotsInspection" suppressId="PyDunderSlots" displayName="Definition of __slots__ in a class" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WARNING" implementationClass="com.jetbrains.python.inspections.PyDunderSlotsInspection"/>
<localInspection language="Python" shortName="PyMissingTypeHintsInspection" suppressId="PyMissingTypeHints" displayName="Missing type hinting for function definition" groupKey="INSP.GROUP.python" enabledByDefault="false" level="WEAK WARNING" implementationClass="com.jetbrains.python.inspections.PyMissingTypeHintsInspection"/>
<defaultLiveTemplatesProvider implementation="com.jetbrains.python.codeInsight.liveTemplates.PyDefaultLiveTemplatesProvider"/>
@@ -0,0 +1,66 @@
/*
* 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
import com.intellij.codeInspection.LocalInspectionToolSession
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.PsiElementVisitor
import com.jetbrains.python.PyNames
import com.jetbrains.python.psi.*
import com.jetbrains.python.psi.impl.PyPsiUtils
class PyDunderSlotsInspection : 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 visitPyClass(node: PyClass?) {
if (node != null && LanguageLevel.forElement(node).isAtLeast(LanguageLevel.PYTHON30)) {
val slots = findSlotsValue(node)
when (slots) {
is PySequenceExpression -> slots
.elements
.asSequence()
.filterIsInstance<PyStringLiteralExpression>()
.forEach {
processSlot(node, it)
}
is PyStringLiteralExpression -> processSlot(node, slots)
}
}
}
private fun findSlotsValue(pyClass: PyClass): PyExpression? {
val target = pyClass.findClassAttribute(PyNames.SLOTS, false, myTypeEvalContext) as? PyTargetExpression
val value = target?.findAssignedValue()
return PyPsiUtils.flattenParens(value)
}
private fun processSlot(pyClass: PyClass, slot: PyStringLiteralExpression) {
val name = slot.stringValue
if (pyClass.findClassAttribute(name, false, myTypeEvalContext) != null) {
registerProblem(slot, "'$name' in __slots__ conflicts with class variable")
}
}
}
}
@@ -0,0 +1,62 @@
# PY-20280: one slot in list
class Foo(object):
__slots__ = [<warning descr="'foo' in __slots__ conflicts with class variable">'foo'</warning>]
foo = 1
# PY-20280: one slot in tuple
class Foo(object):
__slots__ = (<warning descr="'foo' in __slots__ conflicts with class variable">'foo'</warning>)
foo = 1
# PY-20280: one slot
class Foo(object):
__slots__ = <warning descr="'foo' in __slots__ conflicts with class variable">'foo'</warning>
foo = 1
# PY-20280: two slots in list
class Foo(object):
__slots__ = [<warning descr="'foo' in __slots__ conflicts with class variable">'foo'</warning>, 'bar']
foo = 1
# PY-20280: two slots in tuple
class Foo(object):
__slots__ = (<warning descr="'foo' in __slots__ conflicts with class variable">'foo'</warning>, 'bar')
foo = 1
# PY-20280: slots in base and class variable in derived
class Base(object):
__slots__ = 'foo'
class Derived(Base):
foo = 1
# PY-20280: class variable in base and slots in derived
class Base(object):
foo = 1
class Derived(Base):
__slots__ = 'foo'
# PY-20280: slots in base and derived, class variable in derived
class Base(object):
__slots__ = 'foo'
class Derived(Base):
__slots__ = 'bar'
foo = 1
# PY-20280: slots in base and derived, class variable in base
class Base(object):
__slots__ = 'bar'
foo = 1
class Derived(Base):
__slots__ = 'foo'
@@ -328,4 +328,8 @@ public class PythonInspectionsTest extends PyTestCase {
public void testPyShadowingNamesInspection() {
doHighlightingTest(PyShadowingNamesInspection.class);
}
public void testPyDunderSlotsInspection() {
runWithLanguageLevel(LanguageLevel.PYTHON30, () -> doHighlightingTest(PyDunderSlotsInspection.class));
}
}