def _check_cmp_argument(self, node): # Check that the `cmp` argument is used kwargs = [] if (isinstance(node.func, astroid.Attribute) and node.func.attrname == 'sort'): inferred = utils.safe_infer(node.func.expr) if not inferred: return builtins_list = "{}.list".format(bases.BUILTINS) if (isinstance(inferred, astroid.List) or inferred.qname() == builtins_list): kwargs = node.keywords elif (isinstance(node.func, astroid.Name) and node.func.name == 'sorted'): inferred = utils.safe_infer(node.func) if not inferred: return builtins_sorted = "{}.sorted".format(bases.BUILTINS) if inferred.qname() == builtins_sorted: kwargs = node.keywords for kwarg in kwargs or []: if kwarg.arg == 'cmp': self.add_message('using-cmp-argument', node=node) return
def get_constant_values(self, node, key): try: ass = node.locals[key][-1] except KeyError: return try: xs = safe_infer(ass).get_children() except AttributeError: return return [(x, x.value) for x in xs if isinstance(safe_infer(x), astng.Const)]
def visit_callfunc(self, node): func = utils.safe_infer(node.func) if (isinstance(func, astroid.BoundMethod) and isinstance(func.bound, astroid.Instance) and func.bound.name in ('str', 'unicode', 'bytes') and func.name in ('strip', 'lstrip', 'rstrip') and node.args): arg = utils.safe_infer(node.args[0]) if not isinstance(arg, astroid.Const): return if len(arg.value) != len(set(arg.value)): self.add_message('bad-str-strip-call', node=node, args=(func.bound.name, func.name))
def visit_callfunc(self, node): func = utils.safe_infer(node.func) if ( isinstance(func, astroid.BoundMethod) and isinstance(func.bound, astroid.Instance) and func.bound.name in ("str", "unicode", "bytes") and func.name in ("strip", "lstrip", "rstrip") and node.args ): arg = utils.safe_infer(node.args[0]) if not isinstance(arg, astroid.Const): return if len(arg.value) != len(set(arg.value)): self.add_message("E1310", node=node, args=(func.bound.name, func.name))
def possible_exc_types(node): """ Gets all of the possible raised exception types for the given raise node. .. note:: Caught exception types are ignored. :param node: The raise node to find exception types for. :type node: astroid.node_classes.NodeNG :returns: A list of exception types possibly raised by :param:`node`. :rtype: list(str) """ excs = [] if isinstance(node.exc, astroid.Name): inferred = safe_infer(node.exc) if inferred: excs = [inferred.name] elif (isinstance(node.exc, astroid.Call) and isinstance(node.exc.func, astroid.Name)): target = safe_infer(node.exc.func) if isinstance(target, astroid.ClassDef): excs = [target.name] elif isinstance(target, astroid.FunctionDef): for ret in target.nodes_of_class(astroid.Return): if ret.frame() != target: # return from inner function - ignore it continue val = safe_infer(ret.value) if (val and isinstance(val, (astroid.Instance, astroid.ClassDef)) and inherit_from_std_ex(val)): excs.append(val.name) elif node.exc is None: handler = node.parent while handler and not isinstance(handler, astroid.ExceptHandler): handler = handler.parent if handler and handler.type: inferred_excs = astroid.unpack_infer(handler.type) excs = (exc.name for exc in inferred_excs if exc is not astroid.Uninferable) try: return set(exc for exc in excs if not node_ignores_exception(node, exc)) except astroid.InferenceError: return ()
def _check_try_except_raise(self, node): def gather_exceptions_from_handler(handler): exceptions = [] if handler.type: exceptions_in_handler = utils.safe_infer(handler.type) if isinstance(exceptions_in_handler, astroid.Tuple): exceptions = { exception for exception in exceptions_in_handler.elts if isinstance(exception, astroid.Name) } elif exceptions_in_handler: exceptions = [exceptions_in_handler] return exceptions bare_raise = False handler_having_bare_raise = None excs_in_bare_handler = [] for handler in node.handlers: if bare_raise: # check that subsequent handler is not parent of handler which had bare raise. # since utils.safe_infer can fail for bare except, check it before. # also break early if bare except is followed by bare except. excs_in_current_handler = gather_exceptions_from_handler(handler) if not excs_in_current_handler: bare_raise = False break for exc_in_current_handler in excs_in_current_handler: inferred_current = utils.safe_infer(exc_in_current_handler) if any( utils.is_subclass_of( utils.safe_infer(exc_in_bare_handler), inferred_current ) for exc_in_bare_handler in excs_in_bare_handler ): bare_raise = False break # `raise` as the first operator inside the except handler if _is_raising([handler.body[0]]): # flags when there is a bare raise if handler.body[0].exc is None: bare_raise = True handler_having_bare_raise = handler excs_in_bare_handler = gather_exceptions_from_handler(handler) if bare_raise: self.add_message("try-except-raise", node=handler_having_bare_raise)
def visit_call(self, node): """Visit a Call node.""" if hasattr(node, "func"): infer = utils.safe_infer(node.func) if infer and infer.root().name == "qutebrowser.config.config": if getattr(node.func, "attrname", None) in ("get", "set"): self._check_config(node)
def nodeisinstance(node, klasses, check_base_classes=True): if not isinstance(node, astng.Class): return False for base in node.bases: val = safe_infer(base) if not val: continue if type(val).__name__ == '_Yes': continue nodes = [val] if check_base_classes: try: nodes = chain([val], val.ancestors()) except TypeError: pass for node in nodes: qual = '%s.%s' % (node.root().name, node.name) if qual in klasses: return True return False
def visit_callfunc(self, node): func = utils.safe_infer(node.func) if isinstance(func, astroid.BoundMethod) and func.name == "format": # If there's a .format() call, run the code below if isinstance(node.func.expr, (astroid.Name, astroid.Const)): # This is for: # foo = 'Foo {} bar' # print(foo.format(blah) for inferred in node.func.expr.infer(): if not hasattr(inferred, "value"): # If there's no value attribute, it's not worth # checking. continue if BAD_FORMATTING_SLOT.findall(inferred.value): if self.config.un_indexed_curly_braces_always_error or sys.version_info[:2] < (2, 7): msgid = "E1320" else: msgid = "W1320" self.add_message(msgid, node=inferred, args=inferred.value) elif not hasattr(node.func.expr, "value"): # If it does not have an value attribute, it's not worth # checking return elif isinstance(node.func.expr.value, astroid.Name): # No need to check these either return elif BAD_FORMATTING_SLOT.findall(node.func.expr.value): if self.config.un_indexed_curly_braces_always_error or sys.version_info[:2] < (2, 7): msgid = "E1320" else: msgid = "W1320" self.add_message("E1320", node=node, args=node.func.expr.value)
def visit_raise(self, node): """visit raise possibly inferring value""" # ignore empty raise if node.exc is None: return if PY3K and node.cause: cause = safe_infer(node.cause) if cause is YES or cause is None: return if isinstance(cause, astroid.Const): if cause.value is not None: self.add_message('bad-exception-context', node=node) elif (not isinstance(cause, astroid.Class) and not inherit_from_std_ex(cause)): self.add_message('bad-exception-context', node=node) expr = node.exc if self._check_raise_value(node, expr): return else: try: value = next(unpack_infer(expr)) except astroid.InferenceError: return self._check_raise_value(node, value)
def _determine_function_name_type(node): """Determine the name type whose regex the a function's name should match. :param node: A function node. :returns: One of ('function', 'method', 'attr') """ if not node.is_method(): return 'function' if node.decorators: decorators = node.decorators.nodes else: decorators = [] for decorator in decorators: # If the function is a property (decorated with @property # or @abc.abstractproperty), the name type is 'attr'. if (isinstance(decorator, astroid.Name) or (isinstance(decorator, astroid.Getattr) and decorator.attrname == 'abstractproperty')): infered = safe_infer(decorator) if infered and infered.qname() in PROPERTY_CLASSES: return 'attr' # If the function is decorated using the prop_method.{setter,getter} # form, treat it like an attribute as well. elif (isinstance(decorator, astroid.Getattr) and decorator.attrname in ('setter', 'deleter')): return 'attr' return 'method'
def _check_catching_non_exception(self, handler, exc, part): if isinstance(exc, astroid.Tuple): # Check if it is a tuple of exceptions. inferred = [safe_infer(elt) for elt in exc.elts] if any(node is astroid.YES for node in inferred): # Don't emit if we don't know every component. return if all(node and inherit_from_std_ex(node) for node in inferred): return if not isinstance(exc, astroid.ClassDef): # Don't emit the warning if the infered stmt # is None, but the exception handler is something else, # maybe it was redefined. if isinstance(exc, astroid.Const) and exc.value is None: if (isinstance(handler.type, astroid.Const) and handler.type.value is None) or handler.type.parent_of( exc ): # If the exception handler catches None or # the exception component, which is None, is # defined by the entire exception handler, then # emit a warning. self.add_message("catching-non-exception", node=handler.type, args=(part.as_string(),)) else: self.add_message("catching-non-exception", node=handler.type, args=(part.as_string(),)) return if not inherit_from_std_ex(exc) and exc.name not in self.builtin_exceptions: if has_known_bases(exc): self.add_message("catching-non-exception", node=handler.type, args=(exc.name,))
def _check_raising_stopiteration_in_generator_next_call(self, node): """Check if a StopIteration exception is raised by the call to next function If the next value has a default value, then do not add message. :param node: Check to see if this Call node is a next function :type node: :class:`astroid.node_classes.Call` """ def _looks_like_infinite_iterator(param): inferred = utils.safe_infer(param) if inferred is not None or inferred is not astroid.Uninferable: return inferred.qname() in KNOWN_INFINITE_ITERATORS return False inferred = utils.safe_infer(node.func) if getattr(inferred, 'name', '') == 'next': frame = node.frame() # The next builtin can only have up to two # positional arguments and no keyword arguments has_sentinel_value = len(node.args) > 1 if (isinstance(frame, astroid.FunctionDef) and frame.is_generator() and not has_sentinel_value and not utils.node_ignores_exception(node, StopIteration) and not _looks_like_infinite_iterator(node.args[0])): self.add_message('stop-iteration-return', node=node)
def _check_in_slots(self, node): """ Check that the given assattr node is defined in the class slots. """ infered = safe_infer(node.expr) if infered and isinstance(infered, Instance): klass = infered._proxied if '__slots__' not in klass.locals or not klass.newstyle: return slots = klass.slots() if slots is None: return # If any ancestor doesn't use slots, the slots # defined for this class are superfluous. if any('__slots__' not in ancestor.locals and ancestor.name != 'object' for ancestor in klass.ancestors()): return if not any(slot.value == node.attrname for slot in slots): # If we have a '__dict__' in slots, then # assigning any name is valid. if not any(slot.value == '__dict__' for slot in slots): if _is_attribute_property(node.attrname, klass): # Properties circumvent the slots mechanism, # so we should not emit a warning for them. return self.add_message('assigning-non-slot', args=(node.attrname, ), node=node)
def _check_consider_get(self, node): def type_and_name_are_equal(node_a, node_b): for _type in [astroid.Name, astroid.AssignName]: if all(isinstance(_node, _type) for _node in [node_a, node_b]): return node_a.name == node_b.name if all(isinstance(_node, astroid.Const) for _node in [node_a, node_b]): return node_a.value == node_b.value return False if_block_ok = ( isinstance(node.test, astroid.Compare) and len(node.body) == 1 and isinstance(node.body[0], astroid.Assign) and isinstance(node.body[0].value, astroid.Subscript) and type_and_name_are_equal(node.body[0].value.value, node.test.ops[0][1]) and type_and_name_are_equal(node.body[0].value.slice.value, node.test.left) and len(node.body[0].targets) == 1 and isinstance(utils.safe_infer(node.test.ops[0][1]), astroid.Dict)) if if_block_ok and not node.orelse: self.add_message('consider-using-get', node=node) elif (if_block_ok and len(node.orelse) == 1 and isinstance(node.orelse[0], astroid.Assign) and type_and_name_are_equal(node.orelse[0].targets[0], node.body[0].targets[0]) and len(node.orelse[0].targets) == 1): self.add_message('consider-using-get', node=node)
def _check_open_encoding(self, node): """Check that an open() call always has an encoding set.""" try: mode_arg = utils.get_argument_from_call(node, position=1, keyword='mode') except utils.NoSuchArgumentError: mode_arg = None _encoding = None try: _encoding = utils.get_argument_from_call(node, position=2) except utils.NoSuchArgumentError: try: _encoding = utils.get_argument_from_call(node, keyword='encoding') except utils.NoSuchArgumentError: pass if _encoding is None: if mode_arg is not None: mode = utils.safe_infer(mode_arg) if (mode_arg is not None and isinstance(mode, astroid.Const) and 'b' in getattr(mode, 'value', '')): # Files opened as binary don't need an encoding. return else: self.add_message('open-without-encoding', node=node)
def visit_call(self, node): """Visit a Call node.""" if hasattr(node, 'func'): infer = utils.safe_infer(node.func) if infer: if getattr(node.func, 'name', None) == 'set_trace': self.add_message('set-trace', node=node)
def visit_callfunc(self, node): inferred = safe_infer(node.func) if inferred is not None: if isinstance(inferred, astng.Function): key = (inferred.parent.frame().qname(), inferred.name) # Find unqualified function calls if not isinstance(node.func, astng.Getattr) and key in self.BAD_FUNCTIONS: self.add_message(self.BAD_FUNCTIONS[key], line=node.lineno) if node.args is not None: for arg in node.args: if isinstance(arg, astng.Keyword): newkey = key + (arg.arg,) if newkey in self.BAD_ARGUMENTS: self.add_message(self.BAD_ARGUMENTS[newkey], line=node.lineno) elif isinstance(inferred, astng.Class): key = (inferred.parent.frame().qname(), inferred.name) if key in self.BAD_CLASSES: self.add_message(self.BAD_CLASSES[key], line=node.lineno) if key == ('operator', 'itemgetter'): children = list(node.get_children()) if len(children) > 2: self.add_message('W8407', line=node.lineno) elif key == ('operator', 'attrgetter'): children = list(node.get_children()) if len(children) > 2: self.add_message('W8408', line=node.lineno) elif isinstance(inferred, astng.UnboundMethod): key = (inferred.parent.frame().qname(), inferred.name) if key == ('__builtin__.str', 'startswith'): if isinstance(node.args[0], astng.Tuple): self.add_message('W8405', line=node.lineno) elif key == ('__builtin__.str', 'endswith'): if isinstance(node.args[0], astng.Tuple): self.add_message('W8406', line=node.lineno)
def _check_log_method(self, node, name): """Checks calls to logging.log(level, format, *format_args).""" if name == 'log': if node.starargs or node.kwargs or len(node.args) < 2: # Either a malformed call, star args, or double-star args. Beyond # the scope of this checker. return format_pos = 1 elif name in CHECKED_CONVENIENCE_FUNCTIONS: if node.starargs or node.kwargs or not node.args: # Either no args, star args, or double-star args. Beyond the # scope of this checker. return format_pos = 0 else: return if isinstance(node.args[format_pos], astroid.BinOp): binop = node.args[format_pos] emit = binop.op == '%' if binop.op == '+': total_number_of_strings = sum( 1 for operand in (binop.left, binop.right) if self._is_operand_literal_str(utils.safe_infer(operand)) ) emit = total_number_of_strings > 0 if emit: self.add_message('logging-not-lazy', node=node) elif isinstance(node.args[format_pos], astroid.Call): self._check_call_func(node.args[format_pos]) elif isinstance(node.args[format_pos], astroid.Const): self._check_format_string(node, format_pos) elif isinstance(node.args[format_pos], (astroid.FormattedValue, astroid.JoinedStr)): self.add_message('logging-fstring-interpolation', node=node)
def visit_asyncwith(self, node): for ctx_mgr, _ in node.items: inferred = checker_utils.safe_infer(ctx_mgr) if inferred is None or inferred is astroid.Uninferable: continue if isinstance(inferred, bases.AsyncGenerator): # Check if we are dealing with a function decorated # with contextlib.asynccontextmanager. if decorated_with(inferred.parent, self._async_generators): continue else: try: inferred.getattr("__aenter__") inferred.getattr("__aexit__") except exceptions.NotFoundError: if isinstance(inferred, astroid.Instance): # If we do not know the bases of this class, # just skip it. if not checker_utils.has_known_bases(inferred): continue # Just ignore mixin classes. if self._ignore_mixin_members: if inferred.name[-5:].lower() == "mixin": continue else: continue self.add_message( "not-async-context-manager", node=node, args=(inferred.name,) )
def visit_assign(self, node): """check that if assigning to a function call, the function is possibly returning something valuable """ if not isinstance(node.value, astroid.CallFunc): return function_node = safe_infer(node.value.func) # skip class, generator and incomplete function definition if not (isinstance(function_node, astroid.Function) and function_node.root().fully_defined()): return if function_node.is_generator() \ or function_node.is_abstract(pass_is_abstract=False): return returns = list(function_node.nodes_of_class(astroid.Return, skip_klass=astroid.Function)) if len(returns) == 0: self.add_message('assignment-from-no-return', node=node) else: for rnode in returns: if not (isinstance(rnode.value, astroid.Const) and rnode.value.value is None or rnode.value is None): break else: self.add_message('assignment-from-none', node=node)
def visit_asyncwith(self, node): for ctx_mgr, _ in node.items: infered = checker_utils.safe_infer(ctx_mgr) if infered is None or infered is astroid.YES: continue if isinstance(infered, astroid.Instance): try: infered.getattr('__aenter__') infered.getattr('__aexit__') except exceptions.NotFoundError: if isinstance(infered, astroid.Instance): # If we do not know the bases of this class, # just skip it. if not checker_utils.has_known_bases(infered): continue # Just ignore mixin classes. if self._ignore_mixin_members: if infered.name[-5:].lower() == 'mixin': continue else: continue self.add_message('not-async-context-manager', node=node, args=(infered.name, ))
def visit_callfunc(self, node): """check that called method are infered to callable objects """ called = safe_infer(node.func) # only function, generator and object defining __call__ are allowed if called is not None and not called.callable(): self.add_message('E1102', node=node, args=node.func.as_string())
def visit_callfunc(self, node): """Visit a CallFunc node.""" if hasattr(node, 'func'): infer = utils.safe_infer(node.func) if infer and infer.root().name == '_io': if getattr(node.func, 'name', None) in ('open', 'file'): self._check_open_encoding(node)
def visit_call(self, node): func = utils.safe_infer(node.func) if (isinstance(func, astroid.BoundMethod) and isinstance(func.bound, astroid.Instance) and func.bound.name in ('str', 'unicode', 'bytes')): if func.name == 'format': self._check_new_format(node, func)
def visit_slice(self, node): # Check the type of each part of the slice for index in (node.lower, node.upper, node.step): if index is None: continue index_type = safe_infer(index) if index_type is None or index_type is astroid.YES: continue # Constants must of type int or None if isinstance(index_type, astroid.Const): if isinstance(index_type.value, (int, type(None))): continue # Instance values must be of type int, None or an object # with __index__ elif isinstance(index_type, astroid.Instance): if index_type.pytype() in (BUILTINS + '.int', BUILTINS + '.NoneType'): continue try: index_type.getattr('__index__') return except astroid.NotFoundError: pass # Anything else is an error self.add_message('invalid-slice-index', node=node)
def visit_call(self, node): """Visit a Call node.""" if hasattr(node, 'func'): infer = utils.safe_infer(node.func) if infer and infer.root().name == 'qutebrowser.config.config': if getattr(node.func, 'attrname', None) in ['get', 'set']: self._check_config(node)
def visit_subscript(self, node): supported_protocol = None if isinstance(node.value, (astroid.ListComp, astroid.DictComp)): return if node.ctx == astroid.Load: supported_protocol = supports_getitem msg = "unsubscriptable-object" elif node.ctx == astroid.Store: supported_protocol = supports_setitem msg = "unsupported-assignment-operation" elif node.ctx == astroid.Del: supported_protocol = supports_delitem msg = "unsupported-delete-operation" if isinstance(node.value, astroid.SetComp): self.add_message(msg, args=node.value.as_string(), node=node.value) return if is_inside_abstract_class(node): return inferred = safe_infer(node.value) if inferred is None or inferred is astroid.YES: return if not supported_protocol(inferred): self.add_message(msg, args=node.value.as_string(), node=node.value)
def _no_context_variadic(node, variadic_name, variadic_type, variadics): """Verify if the given call node has variadic nodes without context This is a workaround for handling cases of nested call functions which don't have the specific call context at hand. Variadic arguments (variable positional arguments and variable keyword arguments) are inferred, inherently wrong, by astroid as a Tuple, respectively a Dict with empty elements. This can lead pylint to believe that a function call receives too few arguments. """ statement = node.statement() for name in statement.nodes_of_class(astroid.Name): if name.name != variadic_name: continue inferred = safe_infer(name) if isinstance(inferred, (astroid.List, astroid.Tuple)): length = len(inferred.elts) elif isinstance(inferred, astroid.Dict): length = len(inferred.items) else: continue inferred_statement = inferred.statement() if not length and isinstance(inferred_statement, astroid.FunctionDef): is_in_starred_context = _has_parent_of_type(node, variadic_type, statement) used_as_starred_argument = _is_name_used_as_variadic(name, variadics) if is_in_starred_context or used_as_starred_argument: return True return False
def _no_context_variadic(node): """Verify if the given call node has variadic nodes without context This is a workaround for handling cases of nested call functions which don't have the specific call context at hand. Variadic arguments (variable positional arguments and variable keyword arguments) are inferred, inherently wrong, by astroid as a Tuple, respectively a Dict with empty elements. This can lead pylint to believe that a function call receives too few arguments. """ for arg in node.args: if not isinstance(arg, astroid.Starred): continue inferred = safe_infer(arg.value) if isinstance(inferred, astroid.Tuple): length = len(inferred.elts) elif isinstance(inferred, astroid.Dict): length = len(inferred.items) else: return False if not length and isinstance(inferred.statement(), astroid.FunctionDef): return True return False
def _check_in_slots(self, node): """ Check that the given assattr node is defined in the class slots. """ infered = safe_infer(node.expr) if infered and isinstance(infered, Instance): klass = infered._proxied if '__slots__' not in klass.locals or not klass.newstyle: return slots = klass.slots() # If any ancestor doesn't use slots, the slots # defined for this class are superfluous. if any('__slots__' not in ancestor.locals and ancestor.name != 'object' for ancestor in klass.ancestors()): return if not any(slot.value == node.attrname for slot in slots): # If we have a '__dict__' in slots, then # assigning any name is valid. if not any(slot.value == '__dict__' for slot in slots): self.add_message('assigning-non-slot', args=(node.attrname, ), node=node)
def visit_asyncwith(self, node: nodes.AsyncWith) -> None: for ctx_mgr, _ in node.items: inferred = checker_utils.safe_infer(ctx_mgr) if inferred is None or inferred is astroid.Uninferable: continue if isinstance(inferred, nodes.AsyncFunctionDef): # Check if we are dealing with a function decorated # with contextlib.asynccontextmanager. if decorated_with(inferred, self._async_generators): continue elif isinstance(inferred, astroid.bases.AsyncGenerator): # Check if we are dealing with a function decorated # with contextlib.asynccontextmanager. if decorated_with(inferred.parent, self._async_generators): continue else: try: inferred.getattr("__aenter__") inferred.getattr("__aexit__") except astroid.exceptions.NotFoundError: if isinstance(inferred, astroid.Instance): # If we do not know the bases of this class, # just skip it. if not checker_utils.has_known_bases(inferred): continue # Ignore mixin classes if they match the rgx option. if self._ignore_mixin_members and self._mixin_class_rgx.match( inferred.name ): continue else: continue self.add_message( "not-async-context-manager", node=node, args=(inferred.name,) )
def _check_use_maxsplit_arg(self, node: astroid.Call) -> None: """Add message when accessing first or last elements of a str.split() or str.rsplit().""" # Check if call is split() or rsplit() if ( isinstance(node.func, astroid.Attribute) and node.func.attrname in ("split", "rsplit") and isinstance(utils.safe_infer(node.func), astroid.BoundMethod) ): try: utils.get_argument_from_call(node, 0, "sep") except utils.NoSuchArgumentError: return try: # Ignore if maxsplit arg has been set utils.get_argument_from_call(node, 1, "maxsplit") return except utils.NoSuchArgumentError: pass if isinstance(node.parent, astroid.Subscript): try: subscript_value = utils.get_subscript_const_value(node.parent).value except utils.InferredTypeError: return if subscript_value in (-1, 0): fn_name = node.func.attrname new_fn = "rsplit" if subscript_value == -1 else "split" new_name = ( node.func.as_string().rsplit(fn_name, maxsplit=1)[0] + new_fn + f"({node.args[0].as_string()}, maxsplit=1)[{subscript_value}]" ) self.add_message("use-maxsplit-arg", node=node, args=(new_name,))
def nodeisinstance(node, klasses, check_base_classes=True): if not isinstance(node, astng.Class): return False for base in node.bases: val = safe_infer(base) if not val: continue if isinstance(val, astng.bases._Yes): continue nodes = [val] if check_base_classes: try: nodes = chain([val], val.ancestors()) except TypeError: pass for node in nodes: qual = '%s.%s' % (node.root().name, node.name) if qual in klasses: return True return False
def visit_tuple(self, tuple_node): if PY3K or not tuple_node.elts: self._checker.add_message('raising-bad-type', node=self._node, args='tuple') return # On Python 2, using the following is not an error: # raise (ZeroDivisionError, None) # raise (ZeroDivisionError, ) # What's left to do is to check that the first # argument is indeed an exception. Verifying the other arguments # is not the scope of this check. first = tuple_node.elts[0] inferred = utils.safe_infer(first) if not inferred or inferred is astroid.Uninferable: return if (isinstance(inferred, astroid.Instance) and inferred.__class__.__name__ != 'Instance'): # TODO: explain why self.visit_default(tuple_node) else: self.visit(inferred)
def visit_call(self, node): # Check both for uses of os.putenv and os.setenv and modifying calls # to the os.environ object, such as os.environ.update if not isinstance(node, astroid.CallFunc): return function_node = safe_infer(node.func) if not isinstance(function_node, (astroid.Function, astroid.BoundMethod)): return # If the function is from the os or posix modules, look for calls that # modify the environment if function_node.root().name in ("os", os.name) and \ function_node.name in ("putenv", "unsetenv"): self.add_message("environment-modify", node=node) # Look for methods bound to the environ dict if isinstance(function_node, astroid.BoundMethod) and \ isinstance(function_node.bound, astroid.Dict) and \ function_node.bound.root().name in ("os", os.name) and \ function_node.bound.name == "environ" and \ function_node.name in ("clear", "pop", "popitem", "setdefault", "update"): self.add_message("environment-modify", node=node)
def _is_builtin(node, function): inferred = utils.safe_infer(node) if not inferred: return False return utils.is_builtin_object(inferred) and inferred.name == function
def _check_dict_consider_namedtuple_dataclass(self, node: nodes.Dict) -> None: """Check if dictionary values can be replaced by Namedtuple or Dataclass.""" if not (isinstance(node.parent, (nodes.Assign, nodes.AnnAssign)) and isinstance(node.parent.parent, nodes.Module) or isinstance(node.parent, nodes.AnnAssign) and isinstance(node.parent.target, nodes.AssignName) and utils.is_assign_name_annotated_with( node.parent.target, "Final")): # If dict is not part of an 'Assign' or 'AnnAssign' node in # a module context OR 'AnnAssign' with 'Final' annotation, skip check. return # All dict_values are itself dict nodes if len(node.items) > 1 and all( isinstance(dict_value, nodes.Dict) for _, dict_value in node.items): KeyTupleT = Tuple[Type[nodes.NodeNG], str] # Makes sure all keys are 'Const' string nodes keys_checked: Set[KeyTupleT] = set() for _, dict_value in node.items: dict_value = cast(nodes.Dict, dict_value) for key, _ in dict_value.items: key_tuple = (type(key), key.as_string()) if key_tuple in keys_checked: continue inferred = safe_infer(key) if not (isinstance(inferred, nodes.Const) and inferred.pytype() == "builtins.str"): return keys_checked.add(key_tuple) # Makes sure all subdicts have at least 1 common key key_tuples: List[Tuple[KeyTupleT, ...]] = [] for _, dict_value in node.items: dict_value = cast(nodes.Dict, dict_value) key_tuples.append( tuple((type(key), key.as_string()) for key, _ in dict_value.items)) keys_intersection: Set[KeyTupleT] = set(key_tuples[0]) for sub_key_tuples in key_tuples[1:]: keys_intersection.intersection_update(sub_key_tuples) if not keys_intersection: return self.add_message("consider-using-namedtuple-or-dataclass", node=node) return # All dict_values are itself either list or tuple nodes if len(node.items) > 1 and all( isinstance(dict_value, (nodes.List, nodes.Tuple)) for _, dict_value in node.items): # Make sure all sublists have the same length > 0 list_length = len(node.items[0][1].elts) if list_length == 0: return for _, dict_value in node.items[1:]: dict_value = cast(Union[nodes.List, nodes.Tuple], dict_value) if len(dict_value.elts) != list_length: return # Make sure at least one list entry isn't a dict for _, dict_value in node.items: dict_value = cast(Union[nodes.List, nodes.Tuple], dict_value) if all( isinstance(entry, nodes.Dict) for entry in dict_value.elts): return self.add_message("consider-using-namedtuple-or-dataclass", node=node) return
def _is_node_return_ended(self, node): """Check if the node ends with an explicit return statement. Args: node (astroid.NodeNG): node to be checked. Returns: bool: True if the node ends with an explicit statement, False otherwise. """ #Â Recursion base case if isinstance(node, astroid.Return): return True if isinstance(node, astroid.Call): try: funcdef_node = node.func.infered()[0] if self._is_function_def_never_returning(funcdef_node): return True except astroid.InferenceError: pass # Avoid the check inside while loop as we don't know #Â if they will be completed if isinstance(node, astroid.While): return True if isinstance(node, astroid.Raise): # a Raise statement doesn't need to end with a return statement # but if the exception raised is handled, then the handler has to # ends with a return statement if not node.exc: # Ignore bare raises return True if not utils.is_node_inside_try_except(node): # If the raise statement is not inside a try/except statement #Â then the exception is raised and cannot be caught. No need #Â to infer it. return True exc = utils.safe_infer(node.exc) if exc is None or exc is astroid.Uninferable: return False exc_name = exc.pytype().split('.')[-1] handlers = utils.get_exception_handlers(node, exc_name) handlers = list(handlers) if handlers is not None else [] if handlers: # among all the handlers handling the exception at least one # must end with a return statement return any( self._is_node_return_ended(_handler) for _handler in handlers) # if no handlers handle the exception then it's ok return True if isinstance(node, astroid.If): # if statement is returning if there are exactly two return statements in its #Â children : one for the body part, the other for the orelse part # Do not check if inner function definition are return ended. return_stmts = [ self._is_node_return_ended(_child) for _child in node.get_children() if not isinstance(_child, astroid.FunctionDef) ] return sum(return_stmts) == 2 #Â recurses on the children of the node except for those which are except handler # because one cannot be sure that the handler will really be used return any( self._is_node_return_ended(_child) for _child in node.get_children() if not isinstance(_child, astroid.ExceptHandler))
def _check_use_implicit_booleaness_not_comparison( self, node: nodes.Compare ) -> None: """Check for left side and right side of the node for empty literals""" is_left_empty_literal = utils.is_base_container( node.left ) or utils.is_empty_dict_literal(node.left) # Check both left-hand side and right-hand side for literals for operator, comparator in node.ops: is_right_empty_literal = utils.is_base_container( comparator ) or utils.is_empty_dict_literal(comparator) # Using Exclusive OR (XOR) to compare between two side. # If two sides are both literal, it should be different error. if is_right_empty_literal ^ is_left_empty_literal: # set target_node to opposite side of literal target_node = node.left if is_right_empty_literal else comparator literal_node = comparator if is_right_empty_literal else node.left # Infer node to check target_instance = utils.safe_infer(target_node) if target_instance is None: continue mother_classes = self.base_classes_of_node(target_instance) is_base_comprehension_type = any( t in mother_classes for t in ("tuple", "list", "dict", "set") ) # Only time we bypass check is when target_node is not inherited by # collection literals and have its own __bool__ implementation. if not is_base_comprehension_type and self.instance_has_bool( target_instance ): continue # No need to check for operator when visiting compare node if operator in {"==", "!=", ">=", ">", "<=", "<"}: collection_literal = "{}" if isinstance(literal_node, nodes.List): collection_literal = "[]" if isinstance(literal_node, nodes.Tuple): collection_literal = "()" instance_name = "x" if isinstance(target_node, nodes.Call) and target_node.func: instance_name = f"{target_node.func.as_string()}(...)" elif isinstance(target_node, (nodes.Attribute, nodes.Name)): instance_name = target_node.as_string() original_comparison = ( f"{instance_name} {operator} {collection_literal}" ) suggestion = ( f"{instance_name}" if operator == "!=" else f"not {instance_name}" ) self.add_message( "use-implicit-booleaness-not-comparison", args=( original_comparison, suggestion, ), node=node, )
def visit_call(self, node): """Visits each function call in a lint check. Args: node: Call. The current function call node. """ called = checker_utils.safe_infer(node.func) try: # For the rationale behind the Pylint pragma below, # see https://stackoverflow.com/a/35701863/8115428 called, implicit_args, callable_name = ( typecheck._determine_callable(called)) # pylint: disable=protected-access except ValueError: return if called.args.args is None: # Built-in functions have no argument information. return if len(called.argnames()) != len(set(called.argnames())): return # Build the set of keyword arguments and count the positional arguments. call_site = astroid.arguments.CallSite.from_call(node) if call_site.has_invalid_arguments() or ( call_site.has_invalid_keywords()): return num_positional_args = len(call_site.positional_arguments) keyword_args = list(call_site.keyword_arguments.keys()) already_filled_positionals = getattr(called, 'filled_positionals', 0) already_filled_keywords = getattr(called, 'filled_keywords', {}) keyword_args += list(already_filled_keywords) num_positional_args += already_filled_positionals num_positional_args += implicit_args # Analyze the list of formal parameters. num_mandatory_parameters = len(called.args.args) - len( called.args.defaults) parameters = [] parameter_name_to_index = {} for i, arg in enumerate(called.args.args): if isinstance(arg, astroid.Tuple): name = None else: assert isinstance(arg, astroid.AssignName) name = arg.name parameter_name_to_index[name] = i if i >= num_mandatory_parameters: defval = called.args.defaults[i - num_mandatory_parameters] else: defval = None parameters.append([(name, defval), False]) num_positional_args_unused = num_positional_args # Check that all parameters with a default value have # been called explicitly. for [(name, defval), _] in parameters: if defval: if name is None: display_name = '<tuple>' else: display_name = repr(name) if name not in keyword_args and ( num_positional_args_unused > (num_mandatory_parameters)) and (callable_name != 'constructor'): # This try/except block tries to get the function # name. Since each node may differ, multiple # blocks have been used. try: func_name = node.func.attrname except AttributeError: try: func_name = node.func.name except AttributeError: func_name = node.func self.add_message('non-explicit-keyword-args', node=node, args=(display_name, callable_name, func_name)) num_positional_args_unused -= 1
def visit_binop(self, node): if node.op != "%": return left = node.left args = node.right if not (isinstance(left, astroid.Const) and isinstance(left.value, str)): return format_string = left.value try: ( required_keys, required_num_args, required_key_types, required_arg_types, ) = utils.parse_format_string(format_string) except utils.UnsupportedFormatCharacter as exc: formatted = format_string[exc.index] self.add_message( "bad-format-character", node=node, args=(formatted, ord(formatted), exc.index), ) return except utils.IncompleteFormatString: self.add_message("truncated-format-string", node=node) return if required_keys and required_num_args: # The format string uses both named and unnamed format # specifiers. self.add_message("mixed-format-string", node=node) elif required_keys: # The format string uses only named format specifiers. # Check that the RHS of the % operator is a mapping object # that contains precisely the set of keys required by the # format string. if isinstance(args, astroid.Dict): keys = set() unknown_keys = False for k, _ in args.items: if isinstance(k, astroid.Const): key = k.value if isinstance(key, str): keys.add(key) else: self.add_message( "bad-format-string-key", node=node, args=key ) else: # One of the keys was something other than a # constant. Since we can't tell what it is, # suppress checks for missing keys in the # dictionary. unknown_keys = True if not unknown_keys: for key in required_keys: if key not in keys: self.add_message( "missing-format-string-key", node=node, args=key ) for key in keys: if key not in required_keys: self.add_message( "unused-format-string-key", node=node, args=key ) for key, arg in args.items: if not isinstance(key, astroid.Const): continue format_type = required_key_types.get(key.value, None) arg_type = utils.safe_infer(arg) if ( format_type is not None and arg_type not in (None, astroid.Uninferable) and not arg_matches_format_type(arg_type, format_type) ): self.add_message( "bad-string-format-type", node=node, args=(arg_type.pytype(), format_type), ) elif isinstance(args, (OTHER_NODES, astroid.Tuple)): type_name = type(args).__name__ self.add_message("format-needs-mapping", node=node, args=type_name) # else: # The RHS of the format specifier is a name or # expression. It may be a mapping object, so # there's nothing we can check. else: # The format string uses only unnamed format specifiers. # Check that the number of arguments passed to the RHS of # the % operator matches the number required by the format # string. args_elts = () if isinstance(args, astroid.Tuple): rhs_tuple = utils.safe_infer(args) num_args = None if hasattr(rhs_tuple, "elts"): args_elts = rhs_tuple.elts num_args = len(args_elts) elif isinstance(args, (OTHER_NODES, (astroid.Dict, astroid.DictComp))): args_elts = [args] num_args = 1 else: # The RHS of the format specifier is a name or # expression. It could be a tuple of unknown size, so # there's nothing we can check. num_args = None if num_args is not None: if num_args > required_num_args: self.add_message("too-many-format-args", node=node) elif num_args < required_num_args: self.add_message("too-few-format-args", node=node) for arg, format_type in zip(args_elts, required_arg_types): if not arg: continue arg_type = utils.safe_infer(arg) if ( arg_type not in ( None, astroid.Uninferable, ) and not arg_matches_format_type(arg_type, format_type) ): self.add_message( "bad-string-format-type", node=node, args=(arg_type.pytype(), format_type), )
def visit_callfunc(self, node): if not is_model(node.frame()): # We only care about fields attached to models return val = safe_infer(node) if not val or not val.root().name.startswith( 'django.db.models.fields'): # Not a field return assname = '(unknown name)' x = node.parent.get_children().next() if isinstance(x, astng.AssName): assname = x.name self.field_count += 1 # Parse kwargs options = dict([(option, None) for option in ( 'null', 'blank', 'unique', 'default', 'auto_now', 'primary_key', 'auto_now_add', 'verify_exists', 'related_name', 'max_length', 'unique_for_date', 'unique_for_month', 'unique_for_year', )]) for arg in node.args: if not isinstance(arg, astng.Keyword): continue for option in options.keys(): if arg.arg == option: try: options[option] = safe_infer(arg.value).value except AttributeError: # Don't lint this field if we cannot infer everything return if not val.name.lower().startswith('null'): for option in ('null', 'blank'): if options[option] is False: self.add_message('W6015', node=node, args=( assname, option, )) # Field type specific checks if val.name in ('CharField', 'TextField'): if options['null']: self.add_message('W6000', node=node, args=(assname, )) if val.name == 'CharField' and \ options['max_length'] > self.config.max_charfield_length: self.add_message('W6007', node=node, args=( assname, options['max_length'], self.config.max_charfield_length, )) elif val.name == 'BooleanField': if options['default']: self.add_message('W6012', node=node, args=(assname, )) elif val.name == 'ForeignKey': val = safe_infer(node.args[0]) if isinstance(val, astng.Const) and val.value == 'self': self.add_message('W6001', node=node, args=(assname, )) elif not options['related_name']: self.add_message('W6006', node=node, args=(assname, )) if options['primary_key'] and options['unique'] is False: self.add_message('W6014', node=node, args=(assname, )) elif options['primary_key'] or options['unique']: self.add_message('W6013', node=node, args=(assname, )) elif val.name == 'URLField': if options['verify_exists'] is None: self.add_message('W6011', node=node, args=(assname, )) elif val.name in ('PositiveSmallIntegerField', 'SmallIntegerField'): self.add_message('W6010', node=node, args=(assname, val.name)) elif val.name == 'NullBooleanField': self.add_message('W6009', node=node, args=(assname, )) elif val.name == 'ManyToManyField': if options['null']: self.add_message('W6016', node=node, args=(assname, )) # Generic checks if options['null'] and not options['blank']: self.add_message('W6004', node=node, args=(assname, )) if options['auto_now'] or options['auto_now_add']: self.add_message('W6008', node=node, args=(assname, )) for suffix in ('date', 'month', 'year'): if options['unique_for_%s' % suffix]: self.add_message('W6005', node=node, args=(assname, suffix))
def _check_using_constant_test( self, node: nodes.If | nodes.IfExp | nodes.Comprehension, test: nodes.NodeNG | None, ) -> None: const_nodes = ( nodes.Module, nodes.GeneratorExp, nodes.Lambda, nodes.FunctionDef, nodes.ClassDef, astroid.bases.Generator, astroid.UnboundMethod, astroid.BoundMethod, nodes.Module, ) structs = (nodes.Dict, nodes.Tuple, nodes.Set, nodes.List) # These nodes are excepted, since they are not constant # values, requiring a computation to happen. except_nodes = ( nodes.Call, nodes.BinOp, nodes.BoolOp, nodes.UnaryOp, nodes.Subscript, ) inferred = None emit = isinstance(test, (nodes.Const, ) + structs + const_nodes) maybe_generator_call = None if not isinstance(test, except_nodes): inferred = utils.safe_infer(test) if inferred is astroid.Uninferable and isinstance( test, nodes.Name): emit, maybe_generator_call = BasicChecker._name_holds_generator( test) # Emit if calling a function that only returns GeneratorExp (always tests True) elif isinstance(test, nodes.Call): maybe_generator_call = test if maybe_generator_call: inferred_call = utils.safe_infer(maybe_generator_call.func) if isinstance(inferred_call, nodes.FunctionDef): # Can't use all(x) or not any(not x) for this condition, because it # will return True for empty generators, which is not what we want. all_returns_were_generator = None for return_node in inferred_call._get_return_nodes_skip_functions( ): if not isinstance(return_node.value, nodes.GeneratorExp): all_returns_were_generator = False break all_returns_were_generator = True if all_returns_were_generator: self.add_message("using-constant-test", node=node, confidence=INFERENCE) return if emit: self.add_message("using-constant-test", node=test, confidence=INFERENCE) elif isinstance(inferred, const_nodes): # If the constant node is a FunctionDef or Lambda then # it may be an illicit function call due to missing parentheses call_inferred = None try: if isinstance(inferred, nodes.FunctionDef): call_inferred = inferred.infer_call_result() elif isinstance(inferred, nodes.Lambda): call_inferred = inferred.infer_call_result(node) except astroid.InferenceError: call_inferred = None if call_inferred: try: for inf_call in call_inferred: if inf_call != astroid.Uninferable: self.add_message( "missing-parentheses-for-call-in-test", node=test, confidence=INFERENCE, ) break except astroid.InferenceError: pass self.add_message("using-constant-test", node=test, confidence=INFERENCE)
def _check_try_except_raise(self, node): def gather_exceptions_from_handler( handler, ) -> typing.Optional[typing.List[astroid.node_classes.NodeNG]]: exceptions = [] # type: typing.List[astroid.node_classes.NodeNG] if handler.type: exceptions_in_handler = utils.safe_infer(handler.type) if isinstance(exceptions_in_handler, astroid.Tuple): exceptions = list({ exception for exception in exceptions_in_handler.elts if isinstance(exception, astroid.Name) }) elif exceptions_in_handler: exceptions = [exceptions_in_handler] else: # Break when we cannot infer anything reliably. return None return exceptions bare_raise = False handler_having_bare_raise = None excs_in_bare_handler = [] for handler in node.handlers: if bare_raise: # check that subsequent handler is not parent of handler which had bare raise. # since utils.safe_infer can fail for bare except, check it before. # also break early if bare except is followed by bare except. excs_in_current_handler = gather_exceptions_from_handler( handler) if not excs_in_current_handler: bare_raise = False break if excs_in_bare_handler is None: # It can be `None` when the inference failed break for exc_in_current_handler in excs_in_current_handler: inferred_current = utils.safe_infer(exc_in_current_handler) if any( utils.is_subclass_of( utils.safe_infer(exc_in_bare_handler), inferred_current) for exc_in_bare_handler in excs_in_bare_handler): bare_raise = False break # `raise` as the first operator inside the except handler if _is_raising([handler.body[0]]): # flags when there is a bare raise if handler.body[0].exc is None: bare_raise = True handler_having_bare_raise = handler excs_in_bare_handler = gather_exceptions_from_handler( handler) else: if bare_raise: self.add_message("try-except-raise", node=handler_having_bare_raise)
def visit_callfunc(self, node): """check that called functions/methods are inferred to callable objects, and that the arguments passed to the function match the parameters in the inferred function's definition """ # Build the set of keyword arguments, checking for duplicate keywords, # and count the positional arguments. keyword_args = set() num_positional_args = 0 for arg in node.args: if isinstance(arg, astroid.Keyword): keyword = arg.arg if keyword in keyword_args: self.add_message('duplicate-keyword-arg', node=node, args=keyword) keyword_args.add(keyword) else: num_positional_args += 1 called = safe_infer(node.func) # only function, generator and object defining __call__ are allowed if called is not None and not called.callable(): self.add_message('not-callable', node=node, args=node.func.as_string()) try: called, implicit_args, callable_name = _determine_callable(called) except ValueError: # Any error occurred during determining the function type, most of # those errors are handled by different warnings. return num_positional_args += implicit_args if called.args.args is None: # Built-in functions have no argument information. return if len(called.argnames()) != len(set(called.argnames())): # Duplicate parameter name (see E9801). We can't really make sense # of the function call in this case, so just return. return # Analyze the list of formal parameters. num_mandatory_parameters = len(called.args.args) - len(called.args.defaults) parameters = [] parameter_name_to_index = {} for i, arg in enumerate(called.args.args): if isinstance(arg, astroid.Tuple): name = None # Don't store any parameter names within the tuple, since those # are not assignable from keyword arguments. else: if isinstance(arg, astroid.Keyword): name = arg.arg else: assert isinstance(arg, astroid.AssName) # This occurs with: # def f( (a), (b) ): pass name = arg.name parameter_name_to_index[name] = i if i >= num_mandatory_parameters: defval = called.args.defaults[i - num_mandatory_parameters] else: defval = None parameters.append([(name, defval), False]) kwparams = {} for i, arg in enumerate(called.args.kwonlyargs): if isinstance(arg, astroid.Keyword): name = arg.arg else: assert isinstance(arg, astroid.AssName) name = arg.name kwparams[name] = [called.args.kw_defaults[i], False] # Match the supplied arguments against the function parameters. # 1. Match the positional arguments. for i in range(num_positional_args): if i < len(parameters): parameters[i][1] = True elif called.args.vararg is not None: # The remaining positional arguments get assigned to the *args # parameter. break else: # Too many positional arguments. self.add_message('too-many-function-args', node=node, args=(callable_name,)) break # 2. Match the keyword arguments. for keyword in keyword_args: if keyword in parameter_name_to_index: i = parameter_name_to_index[keyword] if parameters[i][1]: # Duplicate definition of function parameter. self.add_message('redundant-keyword-arg', node=node, args=(keyword, callable_name)) else: parameters[i][1] = True elif keyword in kwparams: if kwparams[keyword][1]: # XXX is that even possible? # Duplicate definition of function parameter. self.add_message('redundant-keyword-arg', node=node, args=(keyword, callable_name)) else: kwparams[keyword][1] = True elif called.args.kwarg is not None: # The keyword argument gets assigned to the **kwargs parameter. pass else: # Unexpected keyword argument. self.add_message('unexpected-keyword-arg', node=node, args=(keyword, callable_name)) # 3. Match the *args, if any. Note that Python actually processes # *args _before_ any keyword arguments, but we wait until after # looking at the keyword arguments so as to make a more conservative # guess at how many values are in the *args sequence. if node.starargs is not None: for i in range(num_positional_args, len(parameters)): [(name, defval), assigned] = parameters[i] # Assume that *args provides just enough values for all # non-default parameters after the last parameter assigned by # the positional arguments but before the first parameter # assigned by the keyword arguments. This is the best we can # get without generating any false positives. if (defval is not None) or assigned: break parameters[i][1] = True # 4. Match the **kwargs, if any. if node.kwargs is not None: for i, [(name, defval), assigned] in enumerate(parameters): # Assume that *kwargs provides values for all remaining # unassigned named parameters. if name is not None: parameters[i][1] = True else: # **kwargs can't assign to tuples. pass # Check that any parameters without a default have been assigned # values. for [(name, defval), assigned] in parameters: if (defval is None) and not assigned: if name is None: display_name = '<tuple>' else: display_name = repr(name) self.add_message('no-value-for-parameter', node=node, args=(display_name, callable_name)) for name in kwparams: defval, assigned = kwparams[name] if defval is None and not assigned: self.add_message('missing-kwoa', node=node, args=(name, callable_name))
def _is_call2custom_manager(node): """Checks if the call is being done to a custom queryset manager.""" called = safe_infer(node.func) funcdef = getattr(called, '_proxied', None) return _is_custom_qs_manager(funcdef)
def _check_raise_value(self, node, expr): """check for bad values, string exception and class inheritance """ value_found = True if isinstance(expr, astroid.Const): value = expr.value if not isinstance(value, str): # raising-string will be emitted from python3 porting checker. self.add_message('raising-bad-type', node=node, args=value.__class__.__name__) elif ((isinstance(expr, astroid.Name) and expr.name in ('None', 'True', 'False')) or isinstance(expr, (astroid.List, astroid.Dict, astroid.Tuple, astroid.Module, astroid.Function))): emit = True if not PY3K and isinstance(expr, astroid.Tuple): # On Python 2, using the following is not an error: # raise (ZeroDivisionError, None) # raise (ZeroDivisionError, ) # What's left to do is to check that the first # argument is indeed an exception. # Verifying the other arguments is not # the scope of this check. first = expr.elts[0] inferred = safe_infer(first) if isinstance(inferred, Instance): # pylint: disable=protected-access inferred = inferred._proxied if (inferred is YES or isinstance(inferred, astroid.Class) and inherit_from_std_ex(inferred)): emit = False if emit: self.add_message('raising-bad-type', node=node, args=expr.name) elif ( (isinstance(expr, astroid.Name) and expr.name == 'NotImplemented') or (isinstance(expr, astroid.CallFunc) and isinstance(expr.func, astroid.Name) and expr.func.name == 'NotImplemented')): self.add_message('notimplemented-raised', node=node) elif isinstance(expr, (Instance, astroid.Class)): if isinstance(expr, Instance): # pylint: disable=protected-access expr = expr._proxied if (isinstance(expr, astroid.Class) and not inherit_from_std_ex(expr)): if expr.newstyle: self.add_message('raising-non-exception', node=node) else: if has_known_bases(expr): confidence = INFERENCE else: confidence = INFERENCE_FAILURE self.add_message('nonstandard-exception', node=node, confidence=confidence) else: value_found = False else: value_found = False return value_found
def visit_callfunc(self, node): """check that called functions/methods are inferred to callable objects, and that the arguments passed to the function match the parameters in the inferred function's definition """ # Build the set of keyword arguments, checking for duplicate keywords, # and count the positional arguments. keyword_args = set() num_positional_args = 0 for arg in node.args: if isinstance(arg, astng.Keyword): keyword = arg.arg if keyword in keyword_args: self.add_message('E1122', node=node, args=keyword) keyword_args.add(keyword) else: num_positional_args += 1 called = safe_infer(node.func) # only function, generator and object defining __call__ are allowed if called is not None and not called.callable(): self.add_message('E1102', node=node, args=node.func.as_string()) # Note that BoundMethod is a subclass of UnboundMethod (huh?), so must # come first in this 'if..else'. if isinstance(called, astng.BoundMethod): # Bound methods have an extra implicit 'self' argument. num_positional_args += 1 elif isinstance(called, astng.UnboundMethod): if called.decorators is not None: for d in called.decorators.nodes: if isinstance(d, astng.Name) and (d.name == 'classmethod'): # Class methods have an extra implicit 'cls' argument. num_positional_args += 1 break elif (isinstance(called, astng.Function) or isinstance(called, astng.Lambda)): pass else: return if called.args.args is None: # Built-in functions have no argument information. return if len(called.argnames()) != len(set(called.argnames())): # Duplicate parameter name (see E9801). We can't really make sense # of the function call in this case, so just return. return # Analyze the list of formal parameters. num_mandatory_parameters = len(called.args.args) - len( called.args.defaults) parameters = [] parameter_name_to_index = {} for i, arg in enumerate(called.args.args): if isinstance(arg, astng.Tuple): name = None # Don't store any parameter names within the tuple, since those # are not assignable from keyword arguments. else: if isinstance(arg, astng.Keyword): name = arg.arg else: assert isinstance(arg, astng.AssName) # This occurs with: # def f( (a), (b) ): pass name = arg.name parameter_name_to_index[name] = i if i >= num_mandatory_parameters: defval = called.args.defaults[i - num_mandatory_parameters] else: defval = None parameters.append([(name, defval), False]) # Match the supplied arguments against the function parameters. # 1. Match the positional arguments. for i in range(num_positional_args): if i < len(parameters): parameters[i][1] = True elif called.args.vararg is not None: # The remaining positional arguments get assigned to the *args # parameter. break else: # Too many positional arguments. self.add_message('E1121', node=node) break # 2. Match the keyword arguments. for keyword in keyword_args: if keyword in parameter_name_to_index: i = parameter_name_to_index[keyword] if parameters[i][1]: # Duplicate definition of function parameter. self.add_message('E1124', node=node, args=keyword) else: parameters[i][1] = True elif called.args.kwarg is not None: # The keyword argument gets assigned to the **kwargs parameter. pass else: # Unexpected keyword argument. self.add_message('E1123', node=node, args=keyword) # 3. Match the *args, if any. Note that Python actually processes # *args _before_ any keyword arguments, but we wait until after # looking at the keyword arguments so as to make a more conservative # guess at how many values are in the *args sequence. if node.starargs is not None: for i in range(num_positional_args, len(parameters)): [(name, defval), assigned] = parameters[i] # Assume that *args provides just enough values for all # non-default parameters after the last parameter assigned by # the positional arguments but before the first parameter # assigned by the keyword arguments. This is the best we can # get without generating any false positives. if (defval is not None) or assigned: break parameters[i][1] = True # 4. Match the **kwargs, if any. if node.kwargs is not None: for i, [(name, defval), assigned] in enumerate(parameters): # Assume that *kwargs provides values for all remaining # unassigned named parameters. if name is not None: parameters[i][1] = True else: # **kwargs can't assign to tuples. pass # Check that any parameters without a default have been assigned # values. for [(name, defval), assigned] in parameters: if (defval is None) and not assigned: if name is None: display = '<tuple>' else: display_name = repr(name) self.add_message('E1120', node=node, args=display_name)
def visit_call(self, node): infer_node = utils.safe_infer(node.func) if utils.is_builtin_object(infer_node) and infer_node.name == 'print': self.add_message('print-used', node=node) if ('fields' == self.get_func_lib(node.func) and isinstance(node.parent, astroid.Assign) and isinstance(node.parent.parent, astroid.ClassDef)): args = misc.join_node_args_kwargs(node) index = 0 field_name = '' if (isinstance(node.parent, astroid.Assign) and node.parent.targets and isinstance( node.parent.targets[0], astroid.AssignName)): field_name = (node.parent.targets[0].name.replace('_', ' ')) is_related = bool( [1 for kw in node.keywords or [] if kw.arg == 'related']) for argument in args: argument_aux = argument # Check this 'name = fields.Char("name")' if (not is_related and isinstance(argument, astroid.Const) and (index == FIELDS_METHOD.get( argument.parent.func.attrname, 0)) and (argument.value in [field_name.capitalize(), field_name.title()])): self.add_message('attribute-string-redundant', node=node) if isinstance(argument, astroid.Keyword): argument_aux = argument.value deprecated = self.config.deprecated_field_parameters if argument.arg in ['compute', 'search', 'inverse'] and \ isinstance(argument_aux, astroid.Const) and \ isinstance(argument_aux.value, string_types) and \ not argument_aux.value.startswith( '_' + argument.arg + '_'): self.add_message('method-' + argument.arg, node=argument_aux) # Check if the param string is equal to the name # of variable elif not is_related and argument.arg == 'string' and \ (isinstance(argument_aux, astroid.Const) and argument_aux.value in [field_name.capitalize(), field_name.title()]): self.add_message('attribute-string-redundant', node=node) elif (argument.arg in deprecated): self.add_message('renamed-field-parameter', node=node, args=(argument.arg, deprecated[argument.arg])) if isinstance(argument_aux, astroid.Call) and \ isinstance(argument_aux.func, astroid.Name) and \ argument_aux.func.name == '_': self.add_message('translation-field', node=argument_aux) index += 1 # Check cr.commit() if isinstance(node, astroid.Call) and \ isinstance(node.func, astroid.Attribute) and \ node.func.attrname == 'commit' and \ self.get_cursor_name(node.func) in self.config.cursor_expr: self.add_message('invalid-commit', node=node) if (isinstance(node, astroid.Call) and isinstance(node.func, astroid.Attribute) and node.func.attrname == 'with_context' and not node.keywords and node.args): # with_context(**ctx) is considered a keywords # So, if only one args is received it is overridden self.add_message('context-overridden', node=node, args=(node.args[0].as_string(), )) # Call the message_post() base_dirname = os.path.basename( os.path.normpath(os.path.dirname(self.linter.current_file))) if (base_dirname != 'tests' and isinstance(node, astroid.Call) and isinstance(node.func, astroid.Attribute) and node.func.attrname == 'message_post'): for arg in itertools.chain(node.args, node.keywords or []): if isinstance(arg, astroid.Keyword): keyword = arg.arg value = arg.value else: keyword = '' value = arg if keyword and keyword not in ('subject', 'body'): continue as_string = '' # case: message_post(body='String') if isinstance(value, astroid.Const): as_string = value.as_string() # case: message_post(body='String %s' % (...)) elif (isinstance(value, astroid.BinOp) and value.op == '%' and isinstance(value.left, astroid.Const) # The right part is translatable only if it's a # function or a list of functions and not (isinstance(value.right, ( astroid.Call, astroid.Tuple, astroid.List)) and all([ isinstance(child, astroid.Call) for child in getattr(value.right, 'elts', []) ]))): as_string = value.left.as_string() # case: message_post(body='String {...}'.format(...)) elif (isinstance(value, astroid.Call) and isinstance(value.func, astroid.Attribute) and isinstance(value.func.expr, astroid.Const) and value.func.attrname == 'format'): as_string = value.func.expr.as_string() if as_string: keyword = keyword and '%s=' % keyword self.add_message('translation-required', node=node, args=('message_post', keyword, as_string)) # Call _(...) with variables into the term to be translated if (isinstance(node.func, astroid.Name) and node.func.name == '_' and node.args): wrong = '' right = '' arg = node.args[0] # case: _('...' % (variables)) if isinstance(arg, astroid.BinOp) and arg.op == '%': wrong = '%s %% %s' % (arg.left.as_string(), arg.right.as_string()) right = '_(%s) %% %s' % (arg.left.as_string(), arg.right.as_string()) # Case: _('...'.format(variables)) elif (isinstance(arg, astroid.Call) and isinstance(arg.func, astroid.Attribute) and isinstance(arg.func.expr, astroid.Const) and arg.func.attrname == 'format'): self.add_message('str-format-used', node=node) wrong = arg.as_string() params_as_string = ', '.join([ x.as_string() for x in itertools.chain(arg.args, arg.keywords or []) ]) right = '_(%s).format(%s)' % (arg.func.expr.as_string(), params_as_string) if wrong and right: self.add_message('translation-contains-variable', node=node, args=(wrong, right)) # translation-positional-used: Check "string to translate" # to check "%s %s..." used where the position can't be changed str2translate = arg.as_string() printf_args = (misc.WrapperModuleChecker. _get_printf_str_args_kwargs(str2translate)) if isinstance(printf_args, tuple) and len(printf_args) >= 2: # Return tuple for %s and dict for %(varname)s # Check just the following cases "%s %s..." self.add_message('translation-positional-used', node=node, args=(str2translate, )) # SQL Injection if self._check_sql_injection_risky(node): self.add_message('sql-injection', node=node) # external-request-timeout lib_alias = self.get_func_lib(node.func) # Use dict "self._from_imports" to know the source library of the method lib_original = self._from_imports.get(lib_alias) or lib_alias func_name = self.get_func_name(node.func) lib_original_func_name = ( # If it using "requests.request()" "%s.%s" % (lib_original, func_name) if lib_original # If it using "from requests import request;request()" else self._from_imports.get(func_name)) if lib_original_func_name in self.config.external_request_timeout_methods: for argument in misc.join_node_args_kwargs(node): if not isinstance(argument, astroid.Keyword): continue if argument.arg == 'timeout': break else: self.add_message('external-request-timeout', node=node, args=(lib_original_func_name, ))
def visit_binop(self, node): if node.op != '%': return left = node.left args = node.right if not (isinstance(left, astroid.Const) and isinstance(left.value, six.string_types)): return format_string = left.value try: required_keys, required_num_args = \ utils.parse_format_string(format_string) except utils.UnsupportedFormatCharacter as e: c = format_string[e.index] self.add_message('bad-format-character', node=node, args=(c, ord(c), e.index)) return except utils.IncompleteFormatString: self.add_message('truncated-format-string', node=node) return if required_keys and required_num_args: # The format string uses both named and unnamed format # specifiers. self.add_message('mixed-format-string', node=node) elif required_keys: # The format string uses only named format specifiers. # Check that the RHS of the % operator is a mapping object # that contains precisely the set of keys required by the # format string. if isinstance(args, astroid.Dict): keys = set() unknown_keys = False for k, _ in args.items: if isinstance(k, astroid.Const): key = k.value if isinstance(key, six.string_types): keys.add(key) else: self.add_message('bad-format-string-key', node=node, args=key) else: # One of the keys was something other than a # constant. Since we can't tell what it is, # supress checks for missing keys in the # dictionary. unknown_keys = True if not unknown_keys: for key in required_keys: if key not in keys: self.add_message('missing-format-string-key', node=node, args=key) for key in keys: if key not in required_keys: self.add_message('unused-format-string-key', node=node, args=key) elif isinstance(args, OTHER_NODES + (astroid.Tuple,)): type_name = type(args).__name__ self.add_message('format-needs-mapping', node=node, args=type_name) # else: # The RHS of the format specifier is a name or # expression. It may be a mapping object, so # there's nothing we can check. else: # The format string uses only unnamed format specifiers. # Check that the number of arguments passed to the RHS of # the % operator matches the number required by the format # string. if isinstance(args, astroid.Tuple): rhs_tuple = utils.safe_infer(args) num_args = None if rhs_tuple not in (None, astroid.Uninferable): num_args = len(rhs_tuple.elts) elif isinstance(args, OTHER_NODES + (astroid.Dict, astroid.DictComp)): num_args = 1 else: # The RHS of the format specifier is a name or # expression. It could be a tuple of unknown size, so # there's nothing we can check. num_args = None if num_args is not None: if num_args > required_num_args: self.add_message('too-many-format-args', node=node) elif num_args < required_num_args: self.add_message('too-few-format-args', node=node)
def visit_call(self, node): infer_node = utils.safe_infer(node.func) if utils.is_builtin_object(infer_node) and infer_node.name == 'print': self.add_message('print-used', node=node) if ('fields' == self.get_func_lib(node.func) and isinstance(node.parent, astroid.Assign) and isinstance(node.parent.parent, astroid.ClassDef)): args = misc.join_node_args_kwargs(node) index = 0 field_name = '' if (isinstance(node.parent, astroid.Assign) and node.parent.targets and isinstance(node.parent.targets[0], astroid.AssignName)): field_name = (node.parent.targets[0].name .replace('_', ' ')) for argument in args: argument_aux = argument # Check this 'name = fields.Char("name")' if (isinstance(argument, astroid.Const) and (index == FIELDS_METHOD.get(argument.parent.func.attrname, 0)) and (argument.value in [field_name.capitalize(), field_name.title()])): self.add_message('attribute-string-redundant', node=node) if isinstance(argument, astroid.Keyword): argument_aux = argument.value deprecated = self.config.deprecated_field_parameters if argument.arg in ['compute', 'search', 'inverse'] and \ isinstance(argument_aux, astroid.Const) and \ isinstance(argument_aux.value, string_types) and \ not argument_aux.value.startswith( '_' + argument.arg + '_'): self.add_message('method-' + argument.arg, node=argument_aux) # Check if the param string is equal to the name # of variable elif argument.arg == 'string' and \ (isinstance(argument_aux, astroid.Const) and argument_aux.value in [field_name.capitalize(), field_name.title()]): self.add_message( 'attribute-string-redundant', node=node) elif (argument.arg in deprecated): self.add_message( 'renamed-field-parameter', node=node, args=(argument.arg, deprecated[argument.arg]) ) if isinstance(argument_aux, astroid.Call) and \ isinstance(argument_aux.func, astroid.Name) and \ argument_aux.func.name == '_': self.add_message('translation-field', node=argument_aux) index += 1 # Check cr.commit() if isinstance(node, astroid.Call) and \ isinstance(node.func, astroid.Attribute) and \ node.func.attrname == 'commit' and \ self.get_cursor_name(node.func) in self.config.cursor_expr: self.add_message('invalid-commit', node=node) # Call the message_post() if (isinstance(node, astroid.Call) and isinstance(node.func, astroid.Attribute) and node.func.attrname == 'message_post'): for arg in itertools.chain(node.args, node.keywords or []): if isinstance(arg, astroid.Keyword): keyword = arg.arg value = arg.value else: keyword = '' value = arg if keyword and keyword not in ('subject', 'body'): continue as_string = '' # case: message_post(body='String') if isinstance(value, astroid.Const): as_string = value.as_string() # case: message_post(body='String %s' % (...)) elif (isinstance(value, astroid.BinOp) and value.op == '%' and isinstance(value.left, astroid.Const) # The right part is translatable only if it's a # function or a list of functions and not ( isinstance(value.right, ( astroid.Call, astroid.Tuple, astroid.List)) and all([ isinstance(child, astroid.Call) for child in getattr(value.right, 'elts', []) ]))): as_string = value.left.as_string() # case: message_post(body='String {...}'.format(...)) elif (isinstance(value, astroid.Call) and isinstance(value.func, astroid.Attribute) and isinstance(value.func.expr, astroid.Const) and value.func.attrname == 'format'): as_string = value.func.expr.as_string() if as_string: keyword = keyword and '%s=' % keyword self.add_message( 'translation-required', node=node, args=('message_post', keyword, as_string)) # Call _(...) with variables into the term to be translated if (isinstance(node.func, astroid.Name) and node.func.name == '_' and node.args): wrong = '' right = '' arg = node.args[0] # case: _('...' % (variables)) if isinstance(arg, astroid.BinOp) and arg.op == '%': wrong = '%s %% %s' % ( arg.left.as_string(), arg.right.as_string()) right = '_(%s) %% %s' % ( arg.left.as_string(), arg.right.as_string()) # Case: _('...'.format(variables)) elif (isinstance(arg, astroid.Call) and isinstance(arg.func, astroid.Attribute) and isinstance(arg.func.expr, astroid.Const) and arg.func.attrname == 'format'): self.add_message('str-format-used', node=node) wrong = arg.as_string() params_as_string = ', '.join([ x.as_string() for x in itertools.chain(arg.args, arg.keywords or [])]) right = '_(%s).format(%s)' % ( arg.func.expr.as_string(), params_as_string) if wrong and right: self.add_message( 'translation-contains-variable', node=node, args=(wrong, right)) # translation-positional-used: Check "string to translate" # to check "%s %s..." used where the position can't be changed str2translate = arg.as_string() printf_args = ( misc.WrapperModuleChecker. _get_printf_str_args_kwargs(str2translate)) if isinstance(printf_args, tuple) and len(printf_args) >= 2: # Return tuple for %s and dict for %(varname)s # Check just the following cases "%s %s..." self.add_message('translation-positional-used', node=node, args=(str2translate,)) # SQL Injection if isinstance(node, astroid.Call) and node.args and \ isinstance(node.func, astroid.Attribute) and \ node.func.attrname in ('execute', 'executemany') and \ self.get_cursor_name(node.func) in self.config.cursor_expr: first_arg = node.args[0] risky = self._check_node_for_sqli_risk(first_arg) if not risky: for node_assignation in self._get_assignation_nodes(first_arg): risky = self._check_node_for_sqli_risk(node_assignation) if risky: break if risky: self.add_message('sql-injection', node=node)
def _check_uninferable_callfunc(self, node): """ Check that the given uninferable CallFunc node does not call an actual function. """ if not isinstance(node.func, astroid.Getattr): return # Look for properties. First, obtain # the lhs of the Getattr node and search the attribute # there. If that attribute is a property or a subclass of properties, # then most likely it's not callable. # TODO: since astroid doesn't understand descriptors very well # we will not handle them here, right now. expr = node.func.expr klass = safe_infer(expr) if (klass is None or klass is astroid.YES or not isinstance(klass, astroid.Instance)): return try: attrs = klass._proxied.getattr(node.func.attrname) except astroid.NotFoundError: return stop_checking = False for attr in attrs: if attr is astroid.YES: continue if stop_checking: break if not isinstance(attr, astroid.Function): continue # Decorated, see if it is decorated with a property if not attr.decorators: continue for decorator in attr.decorators.nodes: if not isinstance(decorator, astroid.Name): continue try: for infered in decorator.infer(): property_like = False if isinstance(infered, astroid.Class): if (infered.root().name == BUILTINS and infered.name == 'property'): property_like = True else: for ancestor in infered.ancestors(): if (ancestor.name == 'property' and ancestor.root().name == BUILTINS): property_like = True break if property_like: self.add_message('not-callable', node=node, args=node.func.as_string()) stop_checking = True break except InferenceError: pass if stop_checking: break
def visit_call(self, node): """check that called functions/methods are inferred to callable objects, and that the arguments passed to the function match the parameters in the inferred function's definition """ # Build the set of keyword arguments, checking for duplicate keywords, # and count the positional arguments. call_site = astroid.arguments.CallSite.from_call(node) num_positional_args = len(call_site.positional_arguments) keyword_args = list(call_site.keyword_arguments.keys()) # Determine if we don't have a context for our call and we use variadics. if isinstance(node.scope(), astroid.FunctionDef): has_no_context_positional_variadic = _no_context_variadic_positional(node) has_no_context_keywords_variadic = _no_context_variadic_keywords(node) else: has_no_context_positional_variadic = has_no_context_keywords_variadic = False called = safe_infer(node.func) # only function, generator and object defining __call__ are allowed if called and not called.callable(): self.add_message('not-callable', node=node, args=node.func.as_string()) self._check_uninferable_callfunc(node) try: called, implicit_args, callable_name = _determine_callable(called) except ValueError: # Any error occurred during determining the function type, most of # those errors are handled by different warnings. return num_positional_args += implicit_args if called.args.args is None: # Built-in functions have no argument information. return if len(called.argnames()) != len(set(called.argnames())): # Duplicate parameter name (see duplicate-argument). We can't really # make sense of the function call in this case, so just return. return # Warn about duplicated keyword arguments, such as `f=24, **{'f': 24}` for keyword in call_site.duplicated_keywords: self.add_message('repeated-keyword', node=node, args=(keyword, )) if call_site.has_invalid_arguments() or call_site.has_invalid_keywords(): # Can't make sense of this. return # Analyze the list of formal parameters. num_mandatory_parameters = len(called.args.args) - len(called.args.defaults) parameters = [] parameter_name_to_index = {} for i, arg in enumerate(called.args.args): if isinstance(arg, astroid.Tuple): name = None # Don't store any parameter names within the tuple, since those # are not assignable from keyword arguments. else: assert isinstance(arg, astroid.AssignName) # This occurs with: # def f( (a), (b) ): pass name = arg.name parameter_name_to_index[name] = i if i >= num_mandatory_parameters: defval = called.args.defaults[i - num_mandatory_parameters] else: defval = None parameters.append([(name, defval), False]) kwparams = {} for i, arg in enumerate(called.args.kwonlyargs): if isinstance(arg, astroid.Keyword): name = arg.arg else: assert isinstance(arg, astroid.AssignName) name = arg.name kwparams[name] = [called.args.kw_defaults[i], False] # Match the supplied arguments against the function parameters. # 1. Match the positional arguments. for i in range(num_positional_args): if i < len(parameters): parameters[i][1] = True elif called.args.vararg is not None: # The remaining positional arguments get assigned to the *args # parameter. break else: # Too many positional arguments. self.add_message('too-many-function-args', node=node, args=(callable_name,)) break # 2. Match the keyword arguments. for keyword in keyword_args: if keyword in parameter_name_to_index: i = parameter_name_to_index[keyword] if parameters[i][1]: # Duplicate definition of function parameter. # Might be too hardcoded, but this can actually # happen when using str.format and `self` is passed # by keyword argument, as in `.format(self=self)`. # It's perfectly valid to so, so we're just skipping # it if that's the case. if not (keyword == 'self' and called.qname() == STR_FORMAT): self.add_message('redundant-keyword-arg', node=node, args=(keyword, callable_name)) else: parameters[i][1] = True elif keyword in kwparams: if kwparams[keyword][1]: # XXX is that even possible? # Duplicate definition of function parameter. self.add_message('redundant-keyword-arg', node=node, args=(keyword, callable_name)) else: kwparams[keyword][1] = True elif called.args.kwarg is not None: # The keyword argument gets assigned to the **kwargs parameter. pass else: # Unexpected keyword argument. self.add_message('unexpected-keyword-arg', node=node, args=(keyword, callable_name)) # 3. Match the **kwargs, if any. if node.kwargs: for i, [(name, defval), assigned] in enumerate(parameters): # Assume that *kwargs provides values for all remaining # unassigned named parameters. if name is not None: parameters[i][1] = True else: # **kwargs can't assign to tuples. pass # Check that any parameters without a default have been assigned # values. for [(name, defval), assigned] in parameters: if (defval is None) and not assigned: if name is None: display_name = '<tuple>' else: display_name = repr(name) # TODO(cpopa): this should be removed after PyCQA/astroid/issues/177 if not has_no_context_positional_variadic: self.add_message('no-value-for-parameter', node=node, args=(display_name, callable_name)) for name in kwparams: defval, assigned = kwparams[name] if defval is None and not assigned and not has_no_context_keywords_variadic: self.add_message('missing-kwoa', node=node, args=(name, callable_name))
def visit_index(self, node): if not node.parent or not hasattr(node.parent, "value"): return # Look for index operations where the parent is a sequence type. # If the types can be determined, only allow indices to be int, # slice or instances with __index__. parent_type = safe_infer(node.parent.value) if not isinstance(parent_type, (astroid.ClassDef, astroid.Instance)): return if not has_known_bases(parent_type): return # Determine what method on the parent this index will use # The parent of this node will be a Subscript, and the parent of that # node determines if the Subscript is a get, set, or delete operation. if node.parent.ctx is astroid.Store: methodname = '__setitem__' elif node.parent.ctx is astroid.Del: methodname = '__delitem__' else: methodname = '__getitem__' # Check if this instance's __getitem__, __setitem__, or __delitem__, as # appropriate to the statement, is implemented in a builtin sequence # type. This way we catch subclasses of sequence types but skip classes # that override __getitem__ and which may allow non-integer indices. try: methods = parent_type.getattr(methodname) if methods is astroid.YES: return itemmethod = methods[0] except (exceptions.NotFoundError, IndexError): return if not isinstance(itemmethod, astroid.FunctionDef): return if itemmethod.root().name != BUILTINS: return if not itemmethod.parent: return if itemmethod.parent.name not in SEQUENCE_TYPES: return # For ExtSlice objects coming from visit_extslice, no further # inference is necessary, since if we got this far the ExtSlice # is an error. if isinstance(node, astroid.ExtSlice): index_type = node else: index_type = safe_infer(node) if index_type is None or index_type is astroid.YES: return # Constants must be of type int if isinstance(index_type, astroid.Const): if isinstance(index_type.value, int): return # Instance values must be int, slice, or have an __index__ method elif isinstance(index_type, astroid.Instance): if index_type.pytype() in (BUILTINS + '.int', BUILTINS + '.slice'): return try: index_type.getattr('__index__') return except exceptions.NotFoundError: pass elif isinstance(index_type, astroid.Slice): # Delegate to visit_slice. A slice can be present # here after inferring the index node, which could # be a `slice(...)` call for instance. return self.visit_slice(index_type) # Anything else is an error self.add_message('invalid-sequence-index', node=node)
def _check_use_maxsplit_arg(self, node: astroid.Call) -> None: """Add message when accessing first or last elements of a str.split() or str.rsplit().""" # Check if call is split() or rsplit() if not (isinstance(node.func, astroid.Attribute) and node.func.attrname in ("split", "rsplit") and isinstance( utils.safe_infer(node.func), astroid.BoundMethod)): return try: utils.get_argument_from_call(node, 0, "sep") except utils.NoSuchArgumentError: return try: # Ignore if maxsplit arg has been set utils.get_argument_from_call(node, 1, "maxsplit") return except utils.NoSuchArgumentError: pass if isinstance(node.parent, astroid.Subscript): try: subscript_value = utils.get_subscript_const_value( node.parent).value except utils.InferredTypeError: return # Check for cases where variable (Name) subscripts may be mutated within a loop if isinstance(node.parent.slice, astroid.Name): # Check if loop present within the scope of the node scope = node.scope() for loop_node in scope.nodes_of_class( (astroid.For, astroid.While)): loop_node = cast(astroid.node_classes.NodeNG, loop_node) if not loop_node.parent_of(node): continue # Check if var is mutated within loop (Assign/AugAssign) for assignment_node in loop_node.nodes_of_class( astroid.AugAssign): assignment_node = cast(astroid.AugAssign, assignment_node) if node.parent.slice.name == assignment_node.target.name: return for assignment_node in loop_node.nodes_of_class( astroid.Assign): assignment_node = cast(astroid.Assign, assignment_node) if node.parent.slice.name in [ n.name for n in assignment_node.targets ]: return if subscript_value in (-1, 0): fn_name = node.func.attrname new_fn = "rsplit" if subscript_value == -1 else "split" new_name = ( node.func.as_string().rsplit(fn_name, maxsplit=1)[0] + new_fn + f"({node.args[0].as_string()}, maxsplit=1)[{subscript_value}]" ) self.add_message("use-maxsplit-arg", node=node, args=(new_name, ))
def _check_new_format_specifiers(self, node, fields, named): """ Check attribute and index access in the format string ("{0.a}" and "{0[a]}"). """ for key, specifiers in fields: # Obtain the argument. If it can't be obtained # or inferred, skip this check. if key == "": # {[0]} will have an unnamed argument, defaulting # to 0. It will not be present in `named`, so use the value # 0 for it. key = 0 if isinstance(key, numbers.Number): try: argname = utils.get_argument_from_call(node, key) except utils.NoSuchArgumentError: continue else: if key not in named: continue argname = named[key] if argname in (astroid.Uninferable, None): continue try: argument = utils.safe_infer(argname) except astroid.InferenceError: continue if not specifiers or not argument: # No need to check this key if it doesn't # use attribute / item access continue if argument.parent and isinstance(argument.parent, astroid.Arguments): # Ignore any object coming from an argument, # because we can't infer its value properly. continue previous = argument parsed = [] for is_attribute, specifier in specifiers: if previous is astroid.Uninferable: break parsed.append((is_attribute, specifier)) if is_attribute: try: previous = previous.getattr(specifier)[0] except astroid.NotFoundError: if ( hasattr(previous, "has_dynamic_getattr") and previous.has_dynamic_getattr() ): # Don't warn if the object has a custom __getattr__ break path = get_access_path(key, parsed) self.add_message( "missing-format-attribute", args=(specifier, path), node=node, ) break else: warn_error = False if hasattr(previous, "getitem"): try: previous = previous.getitem(astroid.Const(specifier)) except ( astroid.AstroidIndexError, astroid.AstroidTypeError, astroid.AttributeInferenceError, ): warn_error = True except astroid.InferenceError: break if previous is astroid.Uninferable: break else: try: # Lookup __getitem__ in the current node, # but skip further checks, because we can't # retrieve the looked object previous.getattr("__getitem__") break except astroid.NotFoundError: warn_error = True if warn_error: path = get_access_path(key, parsed) self.add_message( "invalid-format-index", args=(specifier, path), node=node ) break try: previous = next(previous.infer()) except astroid.InferenceError: # can't check further if we can't infer it break
def _looks_like_infinite_iterator(param): inferred = utils.safe_infer(param) if inferred is not None or inferred is not astroid.Uninferable: return inferred.qname() in KNOWN_INFINITE_ITERATORS return False
def _get_raise_target(node): if isinstance(node.exc, nodes.Call): func = node.exc.func if isinstance(func, (nodes.Name, nodes.Attribute)): return utils.safe_infer(func) return None
def _detect_replacable_format_call(self, node: nodes.Const) -> None: """Check whether a string is used in a call to format() or '%' and whether it can be replaced by an f-string """ if (isinstance(node.parent, nodes.Attribute) and node.parent.attrname == "format"): # Don't warn on referencing / assigning .format without calling it if not isinstance(node.parent.parent, nodes.Call): return if node.parent.parent.args: for arg in node.parent.parent.args: # If star expressions with more than 1 element are being used if isinstance(arg, nodes.Starred): inferred = utils.safe_infer(arg.value) if (isinstance(inferred, astroid.List) and len(inferred.elts) > 1): return # Backslashes can't be in f-string expressions if "\\" in arg.as_string(): return elif node.parent.parent.keywords: keyword_args = [ i[0] for i in utils.parse_format_method_string(node.value)[0] ] for keyword in node.parent.parent.keywords: # If keyword is used multiple times if keyword_args.count(keyword.arg) > 1: return keyword = utils.safe_infer(keyword.value) # If lists of more than one element are being unpacked if isinstance(keyword, nodes.Dict): if len(keyword.items) > 1 and len(keyword_args) > 1: return # If all tests pass, then raise message self.add_message( "consider-using-f-string", node=node, line=node.lineno, col_offset=node.col_offset, ) elif isinstance(node.parent, nodes.BinOp) and node.parent.op == "%": # Backslashes can't be in f-string expressions if "\\" in node.parent.right.as_string(): return inferred_right = utils.safe_infer(node.parent.right) # If dicts or lists of length > 1 are used if isinstance(inferred_right, nodes.Dict): if len(inferred_right.items) > 1: return elif isinstance(inferred_right, nodes.List): if len(inferred_right.elts) > 1: return # If all tests pass, then raise message self.add_message( "consider-using-f-string", node=node, line=node.lineno, col_offset=node.col_offset, )