-
Notifications
You must be signed in to change notification settings - Fork 888
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
361 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
import collections | ||
import datetime as dt | ||
import logging | ||
import operator | ||
import random | ||
import re | ||
import time | ||
import types | ||
from operator import attrgetter, itemgetter, methodcaller | ||
from types import MappingProxyType | ||
|
||
|
||
# B006 | ||
# Allow immutable literals/calls/comprehensions | ||
def this_is_okay(value=(1, 2, 3)): | ||
... | ||
|
||
|
||
async def and_this_also(value=tuple()): | ||
pass | ||
|
||
|
||
def frozenset_also_okay(value=frozenset()): | ||
pass | ||
|
||
|
||
def mappingproxytype_okay( | ||
value=MappingProxyType({}), value2=types.MappingProxyType({}) | ||
): | ||
pass | ||
|
||
|
||
def re_compile_ok(value=re.compile("foo")): | ||
pass | ||
|
||
|
||
def operators_ok( | ||
v=operator.attrgetter("foo"), | ||
v2=operator.itemgetter("foo"), | ||
v3=operator.methodcaller("foo"), | ||
): | ||
pass | ||
|
||
|
||
def operators_ok_unqualified( | ||
v=attrgetter("foo"), | ||
v2=itemgetter("foo"), | ||
v3=methodcaller("foo"), | ||
): | ||
pass | ||
|
||
|
||
def kwonlyargs_immutable(*, value=()): | ||
... | ||
|
||
|
||
# Flag mutable literals/comprehensions | ||
|
||
|
||
def this_is_wrong(value=[1, 2, 3]): | ||
... | ||
|
||
|
||
def this_is_also_wrong(value={}): | ||
... | ||
|
||
|
||
def and_this(value=set()): | ||
... | ||
|
||
|
||
def this_too(value=collections.OrderedDict()): | ||
... | ||
|
||
|
||
async def async_this_too(value=collections.defaultdict()): | ||
... | ||
|
||
|
||
def dont_forget_me(value=collections.deque()): | ||
... | ||
|
||
|
||
# N.B. we're also flagging the function call in the comprehension | ||
def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]): | ||
pass | ||
|
||
|
||
def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}): | ||
pass | ||
|
||
|
||
def set_comprehension_also_not_okay(default={i**2 for i in range(3)}): | ||
pass | ||
|
||
|
||
def kwonlyargs_mutable(*, value=[]): | ||
... | ||
|
||
|
||
# Recommended approach for mutable defaults | ||
def do_this_instead(value=None): | ||
if value is None: | ||
value = set() | ||
|
||
|
||
# B008 | ||
# Flag function calls as default args (including if they are part of a sub-expression) | ||
def in_fact_all_calls_are_wrong(value=time.time()): | ||
... | ||
|
||
|
||
def f(when=dt.datetime.now() + dt.timedelta(days=7)): | ||
pass | ||
|
||
|
||
def can_even_catch_lambdas(a=(lambda x: x)()): | ||
... | ||
|
||
|
||
# Recommended approach for function calls as default args | ||
LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
def do_this_instead_of_calls_in_defaults(logger=LOGGER): | ||
# That makes it more obvious that this one value is reused. | ||
... | ||
|
||
|
||
# Handle inf/infinity/nan special case | ||
def float_inf_okay(value=float("inf")): | ||
pass | ||
|
||
|
||
def float_infinity_okay(value=float("infinity")): | ||
pass | ||
|
||
|
||
def float_plus_infinity_okay(value=float("+infinity")): | ||
pass | ||
|
||
|
||
def float_minus_inf_okay(value=float("-inf")): | ||
pass | ||
|
||
|
||
def float_nan_okay(value=float("nan")): | ||
pass | ||
|
||
|
||
def float_minus_NaN_okay(value=float("-NaN")): | ||
pass | ||
|
||
|
||
def float_infinity_literal(value=float("1e999")): | ||
pass | ||
|
||
|
||
# But don't allow standard floats | ||
def float_int_is_wrong(value=float(3)): | ||
pass | ||
|
||
|
||
def float_str_not_inf_or_nan_is_wrong(value=float("3.14")): | ||
pass | ||
|
||
|
||
# B006 and B008 | ||
# We should handle arbitrary nesting of these B008. | ||
def nested_combo(a=[float(3), dt.datetime.now()]): | ||
pass | ||
|
||
|
||
# Don't flag nested B006 since we can't guarantee that | ||
# it isn't made mutable by the outer operation. | ||
def no_nested_b006(a=map(lambda s: s.upper(), ["a", "b", "c"])): | ||
pass | ||
|
||
|
||
# B008-ception. | ||
def nested_b008(a=random.randint(0, dt.datetime.now().year)): | ||
pass | ||
|
||
|
||
# Ignore lambda contents since they are evaluated at call time. | ||
def foo(f=lambda x: print(x)): | ||
f(1) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
use rustpython_ast::{Arguments, ExprKind}; | ||
|
||
use crate::ast::types::{CheckLocator, Range}; | ||
use crate::check_ast::Checker; | ||
use crate::checks::{Check, CheckKind}; | ||
|
||
/// B006 | ||
pub fn mutable_argument_default(checker: &mut Checker, arguments: &Arguments) { | ||
for expr in arguments | ||
.defaults | ||
.iter() | ||
.chain(arguments.kw_defaults.iter()) | ||
{ | ||
match &expr.node { | ||
ExprKind::List { .. } | ||
| ExprKind::Dict { .. } | ||
| ExprKind::Set { .. } | ||
| ExprKind::ListComp { .. } | ||
| ExprKind::DictComp { .. } | ||
| ExprKind::SetComp { .. } => { | ||
checker.add_check(Check::new( | ||
CheckKind::MutableArgumentDefault, | ||
checker.locate_check(Range::from_located(expr)), | ||
)); | ||
} | ||
ExprKind::Call { func, .. } => match &func.node { | ||
ExprKind::Name { id, .. } | ||
if id == "dict" | ||
|| id == "list" | ||
|| id == "set" | ||
|| id == "Counter" | ||
|| id == "OrderedDict" | ||
|| id == "defaultdict" | ||
|| id == "deque" => | ||
{ | ||
checker.add_check(Check::new( | ||
CheckKind::MutableArgumentDefault, | ||
checker.locate_check(Range::from_located(expr)), | ||
)); | ||
} | ||
ExprKind::Attribute { value, attr, .. } | ||
if (attr == "Counter" | ||
|| attr == "OrderedDict" | ||
|| attr == "defaultdict" | ||
|| attr == "deque") => | ||
{ | ||
match &value.node { | ||
ExprKind::Name { id, .. } if id == "collections" => { | ||
checker.add_check(Check::new( | ||
CheckKind::MutableArgumentDefault, | ||
checker.locate_check(Range::from_located(expr)), | ||
)); | ||
} | ||
_ => {} | ||
} | ||
} | ||
_ => {} | ||
}, | ||
_ => {} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.