-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Python: Modernize py/not-named-self and py/not-named-cls queries #18599
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
526e235
fa76bf3
0bf8d4e
aa2c84e
e8adef1
3802a73
287cf01
61d5a69
f46a2a1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
/** Definitions for reasoning about the expected first argument names for methods. */ | ||
|
||
import python | ||
import semmle.python.ApiGraphs | ||
import DataFlow | ||
|
||
/** Holds if `f` is a method of the class `c`. */ | ||
private predicate methodOfClass(Function f, Class c) { f.getScope() = c } | ||
|
||
/** Holds if `c` is a metaclass. */ | ||
private predicate isMetaclass(Class c) { | ||
c = API::builtin("type").getASubclass*().asSource().asExpr().(ClassExpr).getInnerScope() | ||
} | ||
|
||
/** Holds if `f` is a class method. */ | ||
private predicate isClassMethod(Function f) { | ||
f.getADecorator() = API::builtin("classmethod").asSource().asExpr() | ||
or | ||
f.getName() in ["__new__", "__init_subclass__", "__metaclass__", "__class_getitem__"] | ||
} | ||
|
||
/** Holds if `f` is a static method. */ | ||
private predicate isStaticMethod(Function f) { | ||
f.getADecorator() = API::builtin("staticmethod").asSource().asExpr() | ||
} | ||
|
||
/** Holds if `c` is a Zope interface. */ | ||
private predicate isZopeInterface(Class c) { | ||
c = | ||
API::moduleImport("zope") | ||
.getMember("interface") | ||
.getMember("Interface") | ||
.getASubclass*() | ||
.asSource() | ||
.asExpr() | ||
.(ClassExpr) | ||
.getInnerScope() | ||
} | ||
|
||
/** | ||
* Holds if `f` is used in the initialisation of `c`. | ||
* This means `f` isn't being used as a normal method. | ||
* Ideally it should be a `@staticmethod`; however this wasn't possible prior to Python 3.10. | ||
* We exclude this case from the `not-named-self` query. | ||
* However there is potential for a new query that specifically covers and alerts for this case. | ||
*/ | ||
private predicate usedInInit(Function f, Class c) { | ||
methodOfClass(f, c) and | ||
exists(Call call | | ||
call.getScope() = c and | ||
DataFlow::localFlow(DataFlow::exprNode(f.getDefinition()), DataFlow::exprNode(call.getFunc())) | ||
) | ||
} | ||
|
||
/** Holds if the first parameter of `f` should be named `self`. */ | ||
predicate shouldBeSelf(Function f, Class c) { | ||
methodOfClass(f, c) and | ||
not isStaticMethod(f) and | ||
not isClassMethod(f) and | ||
not isMetaclass(c) and | ||
not isZopeInterface(c) and | ||
not usedInInit(f, c) | ||
} | ||
|
||
/** Holds if the first parameter of `f` should be named `cls`. */ | ||
predicate shouldBeCls(Function f, Class c) { | ||
methodOfClass(f, c) and | ||
not isStaticMethod(f) and | ||
( | ||
isClassMethod(f) and not isMetaclass(c) | ||
or | ||
isMetaclass(c) and not isClassMethod(f) | ||
) | ||
} | ||
|
||
/** Holds if the first parameter of `f` is named `self`. */ | ||
predicate firstArgNamedSelf(Function f) { f.getArgName(0) = "self" } | ||
|
||
/** Holds if the first parameter of `f` is named `cls`. */ | ||
predicate firstArgNamedCls(Function f) { | ||
exists(string argname | argname = f.getArgName(0) | | ||
argname = "cls" | ||
or | ||
/* Not PEP8, but relatively common */ | ||
argname = "mcls" | ||
) | ||
} | ||
|
||
/** Holds if the first parameter of `f` should be named `self`, but isn't. */ | ||
predicate firstArgShouldBeNamedSelfAndIsnt(Function f) { | ||
shouldBeSelf(f, _) and | ||
not firstArgNamedSelf(f) | ||
} | ||
|
||
/** Holds if `f` is a regular method of a metaclass, and its first argument is named `self`. */ | ||
private predicate metaclassNamedSelf(Function f, Class c) { | ||
methodOfClass(f, c) and | ||
firstArgNamedSelf(f) and | ||
isMetaclass(c) and | ||
not isClassMethod(f) | ||
} | ||
|
||
/** Holds if the first parameter of `f` should be named `cls`, but isn't. */ | ||
predicate firstArgShouldBeNamedClsAndIsnt(Function f) { | ||
shouldBeCls(f, _) and | ||
not firstArgNamedCls(f) and | ||
not metaclassNamedSelf(f, _) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This reads a bit confusing at the moment. If you want to group metaclass method argument named self with class arguments named class, could we at least rename Alternatively, rename |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
class Entry(object): | ||
@classmethod | ||
def make(klass): | ||
def make(self): | ||
return Entry() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,9 @@ | ||
class Point: | ||
def __init__(val, x, y): # first parameter is mis-named 'val' | ||
def __init__(val, x, y): # BAD: first parameter is mis-named 'val' | ||
val._x = x | ||
val._y = y | ||
|
||
class Point2: | ||
def __init__(self, x, y): # first parameter is correctly named 'self' | ||
def __init__(self, x, y): # GOOD: first parameter is correctly named 'self' | ||
self._x = x | ||
self._y = y |
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two predicates also exist in
DataFlowDispatch.qll
.