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 _check_config(self, node): """Check that the arguments to config.get(...) are valid. FIXME: We should check all ConfigManager calls. https://github.com/The-Compiler/qutebrowser/issues/107 """ try: sect_arg = utils.get_argument_from_call(node, position=0, keyword="sectname") opt_arg = utils.get_argument_from_call(node, position=1, keyword="optname") except utils.NoSuchArgumentError: return sect_arg = utils.safe_infer(sect_arg) opt_arg = utils.safe_infer(opt_arg) if not (isinstance(sect_arg, astroid.Const) and isinstance(opt_arg, astroid.Const)): return try: configdata.DATA[sect_arg.value][opt_arg.value] except KeyError: self.add_message("bad-config-call", node=node, args=(sect_arg.value, opt_arg.value))
def _check_config(self, node): """Check that the arguments to config.get(...) are valid.""" try: sect_arg = utils.get_argument_from_call(node, position=0, keyword='sectname') opt_arg = utils.get_argument_from_call(node, position=1, keyword='optname') except utils.NoSuchArgumentError: return sect_arg = utils.safe_infer(sect_arg) opt_arg = utils.safe_infer(opt_arg) if not (isinstance(sect_arg, astroid.Const) and isinstance(opt_arg, astroid.Const)): return try: configdata.DATA[sect_arg.value][opt_arg.value] except KeyError: self.add_message('bad-config-call', node=node, args=(sect_arg.value, opt_arg.value))
def _check_open_mode(self, node): """Check that the mode argument of an open or file call is valid.""" try: mode_arg = utils.get_argument_from_call(node, position=1, keyword="mode") except utils.NoSuchArgumentError: return if mode_arg: mode_arg = utils.safe_infer(mode_arg) if isinstance(mode_arg, astroid.Const) and not _check_mode_str(mode_arg.value): self.add_message("bad-open-mode", node=node, args=mode_arg.value)
def _check_open_mode(self, node): """Check that the mode argument of an open or file call is valid.""" try: mode_arg = utils.get_argument_from_call(node, position=1, keyword='mode') if mode_arg: mode_arg = utils.safe_infer(mode_arg) if (isinstance(mode_arg, astroid.Const) and not re.match(_VALID_OPEN_MODE_REGEX, mode_arg.value)): self.add_message('W1501', node=node, args=(mode_arg.value)) except (utils.NoSuchArgumentError, TypeError): pass
def _check_reversed(self, node): """ check that the argument to `reversed` is a sequence """ try: argument = safe_infer(get_argument_from_call(node, position=0)) except NoSuchArgumentError: self.add_message('missing-reversed-argument', node=node) else: if argument is astroid.YES: return if argument is None: # nothing was infered # try to see if we have iter() if isinstance(node.args[0], astroid.CallFunc): try: func = node.args[0].func.infer().next() except InferenceError: return if (getattr(func, 'name', None) == 'iter' and is_builtin_object(func)): self.add_message('bad-reversed-sequence', node=node) return if isinstance(argument, astroid.Instance): if (argument._proxied.name == 'dict' and is_builtin_object(argument._proxied)): self.add_message('bad-reversed-sequence', node=node) return elif any(ancestor.name == 'dict' and is_builtin_object(ancestor) for ancestor in argument._proxied.ancestors()): # mappings aren't accepted by reversed() self.add_message('bad-reversed-sequence', node=node) return for methods in REVERSED_METHODS: for meth in methods: try: argument.getattr(meth) except astroid.NotFoundError: break else: break else: # check if it is a .deque. It doesn't seem that # we can retrieve special methods # from C implemented constructs if argument._proxied.qname().endswith(".deque"): return self.add_message('bad-reversed-sequence', node=node) elif not isinstance(argument, (astroid.List, astroid.Tuple)): # everything else is not a proper sequence for reversed() self.add_message('bad-reversed-sequence', node=node)
def testGetArgumentFromCall(): node = astroid.extract_node("foo(a, not_this_one=1, this_one=2)") arg = utils.get_argument_from_call(node, position=2, keyword="this_one") assert 2 == arg.value node = astroid.extract_node("foo(a)") with pytest.raises(utils.NoSuchArgumentError): utils.get_argument_from_call(node, position=1) with pytest.raises(ValueError): utils.get_argument_from_call(node, None, None) name = utils.get_argument_from_call(node, position=0) assert name.name == "a"
def testGetArgumentFromCallExists(fn, kw): node = astroid.extract_node(fn) assert utils.get_argument_from_call(node, **kw) is not None
def testGetArgumentFromCallError(fn, kw): with pytest.raises(utils.NoSuchArgumentError): node = astroid.extract_node(fn) utils.get_argument_from_call(node, **kw)
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 testGetArgumentFromCall(self): node = test_utils.extract_node('foo(bar=3)') self.assertIsNotNone(utils.get_argument_from_call(node, keyword='bar')) with self.assertRaises(utils.NoSuchArgumentError): node = test_utils.extract_node('foo(3)') utils.get_argument_from_call(node, keyword='bar') with self.assertRaises(utils.NoSuchArgumentError): node = test_utils.extract_node('foo(one=a, two=b, three=c)') utils.get_argument_from_call(node, position=1) node = test_utils.extract_node('foo(a, b, c)') self.assertIsNotNone(utils.get_argument_from_call(node, position=1)) node = test_utils.extract_node('foo(a, not_this_one=1, this_one=2)') arg = utils.get_argument_from_call(node, position=2, keyword='this_one') self.assertEqual(2, arg.value) node = test_utils.extract_node('foo(a)') with self.assertRaises(utils.NoSuchArgumentError): utils.get_argument_from_call(node, position=1) with self.assertRaises(ValueError): utils.get_argument_from_call(node, None, None) name = utils.get_argument_from_call(node, position=0) self.assertEqual(name.name, 'a')
def _check_shallow_copy_environ(self, node): arg = utils.get_argument_from_call(node, position=0) for inferred in arg.inferred(): if inferred.qname() == OS_ENVIRON: self.add_message("shallow-copy-environ", node=node) break
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 infered, 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.YES, None): continue try: argument = argname.infer().next() except astroid.InferenceError: continue if not specifiers or argument is astroid.YES: # No need to check this key if it doesn't # use attribute / item access continue if argument.parent and isinstance(argument.parent, astroid.Arguments): # Check to see if our argument is kwarg or vararg, # and skip the check for this argument if so, because when inferring, # astroid will return empty objects (dicts and tuples) and # that can lead to false positives. if argname.name in (argument.parent.kwarg, argument.parent.vararg): continue previous = argument parsed = [] for is_attribute, specifier in specifiers: if previous is astroid.YES: 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(specifier) except (IndexError, TypeError): warn_error = True 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 = previous.infer().next() except astroid.InferenceError: # can't check further if we can't infer it break
def testGetArgumentFromCallExists( fn: str, kw: Union[Dict[str, int], Dict[str, str]]) -> None: node = astroid.extract_node(fn) assert utils.get_argument_from_call(node, **kw) is not None
def testGetArgumentFromCall(self): node = test_utils.extract_node('foo(bar=3)') self.assertIsNotNone(utils.get_argument_from_call(node, keyword='bar')) with self.assertRaises(utils.NoSuchArgumentError): node = test_utils.extract_node('foo(3)') utils.get_argument_from_call(node, keyword='bar') with self.assertRaises(utils.NoSuchArgumentError): node = test_utils.extract_node('foo(one=a, two=b, three=c)') utils.get_argument_from_call(node, position=1) node = test_utils.extract_node('foo(a, b, c)') self.assertIsNotNone(utils.get_argument_from_call(node, position=1)) node = test_utils.extract_node('foo(a, not_this_one=1, this_one=2)') arg = utils.get_argument_from_call(node, position=1, keyword='this_one') self.assertEqual(2, arg.value) node = test_utils.extract_node('foo(a)') with self.assertRaises(utils.NoSuchArgumentError): utils.get_argument_from_call(node, position=1) with self.assertRaises(ValueError): utils.get_argument_from_call(node, None, None) name = utils.get_argument_from_call(node, position=0) self.assertEqual(name.name, 'a')
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 infered, 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 = next(argname.infer()) except astroid.InferenceError: continue if not specifiers or argument is astroid.Uninferable: # 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 _check_shallow_copy_environ(self, node): arg = utils.get_argument_from_call(node, position=0) for inferred in arg.inferred(): if inferred.qname() == OS_ENVIRON: self.add_message('shallow-copy-environ', node=node) break
def includes_exc_info(node): try: exc_info = utils.get_argument_from_call(node, keyword='exc_info') except utils.NoSuchArgumentError: return False return bool(exc_info.value)
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 testGetArgumentFromCallError( fn: str, kw: Union[Dict[str, int], Dict[str, str]]) -> None: with pytest.raises(utils.NoSuchArgumentError): node = astroid.extract_node(fn) utils.get_argument_from_call(node, **kw)