Example #1
0
    def _check_new_format(self, node, func):
        """ Check the new string formatting """
        if (isinstance(node.func, astroid.Attribute)
                and not isinstance(node.func.expr, astroid.Const)):
            return
        try:
            strnode = next(func.bound.infer())
        except astroid.InferenceError:
            return
        if not isinstance(strnode, astroid.Const):
            return

        if isinstance(strnode.value, bytes):
            self.add_message('ansible-no-format-on-bytestring', node=node)
            return
        if not isinstance(strnode.value, str):
            return

        if node.starargs or node.kwargs:
            return
        try:
            num_args = parse_format_method_string(strnode.value)[1]
        except utils.IncompleteFormatString:
            return

        if num_args:
            self.add_message('ansible-format-automatic-specification',
                             node=node)
            return
Example #2
0
    def _check_format_string(self, node, format_arg):
        """Checks that format string tokens match the supplied arguments.

        Args:
          node (astroid.node_classes.NodeNG): AST node to be checked.
          format_arg (int): Index of the format string in the node arguments.
        """
        num_args = _count_supplied_tokens(node.args[format_arg + 1:])
        if not num_args:
            # If no args were supplied the string is not interpolated and can contain
            # formatting characters - it's used verbatim. Don't check any further.
            return

        format_string = node.args[format_arg].value
        required_num_args = 0
        if isinstance(format_string, bytes):
            format_string = format_string.decode()
        if isinstance(format_string, str):
            try:
                if self._format_style == "old":
                    keyword_args, required_num_args, _, _ = utils.parse_format_string(
                        format_string)
                    if keyword_args:
                        # Keyword checking on logging strings is complicated by
                        # special keywords - out of scope.
                        return
                elif self._format_style == "new":
                    (
                        keyword_arguments,
                        implicit_pos_args,
                        explicit_pos_args,
                    ) = utils.parse_format_method_string(format_string)

                    keyword_args_cnt = len(
                        set(k for k, l in keyword_arguments
                            if not isinstance(k, int)))
                    required_num_args = (keyword_args_cnt + implicit_pos_args +
                                         explicit_pos_args)
                else:
                    self.add_message(
                        "logging-format-interpolation",
                        node=node,
                        args=self._format_style_args,
                    )
                    return
            except utils.UnsupportedFormatCharacter as ex:
                char = format_string[ex.index]
                self.add_message(
                    "logging-unsupported-format",
                    node=node,
                    args=(char, ord(char), ex.index),
                )
                return
            except utils.IncompleteFormatString:
                self.add_message("logging-format-truncated", node=node)
                return
        if num_args > required_num_args:
            self.add_message("logging-too-many-args", node=node)
        elif num_args < required_num_args:
            self.add_message("logging-too-few-args", node=node)
Example #3
0
    def visit_call(self, node):
        """Make sure "print_function" is imported if necessary"""
        if (isinstance(node.func, astroid.nodes.Name)
                and node.func.name == "print"):
            if "print_function" in node.root().future_imports:

                def previous(node):
                    if node is not None:
                        parent = node.parent
                    previous = node.previous_sibling()
                    if previous is None:
                        return parent
                    return previous

                prev = previous(node)
                while prev is not None:
                    if (isinstance(prev, astroid.nodes.ImportFrom)
                            and prev.modname == "__future__"
                            and "print_function"
                            in (name_alias[0] for name_alias in prev.names)):
                        return
                    prev = previous(prev)

                self.add_message("print-without-import",
                                 node=node,
                                 confidence=HIGH)
            else:
                self.add_message("print-without-import",
                                 node=node,
                                 confidence=HIGH)

        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":
                if isinstance(node.func, astroid.Attribute) and not isinstance(
                        node.func.expr, astroid.Const):
                    return
                if node.starargs or node.kwargs:
                    return
                try:
                    strnode = next(func.bound.infer())
                except astroid.InferenceError:
                    return
                if not (isinstance(strnode, astroid.Const)
                        and isinstance(strnode.value, str)):
                    return
                try:
                    fields, num_args, manual_pos = (
                        utils.parse_format_method_string(strnode.value))
                except utils.IncompleteFormatString:
                    self.add_message("bad-format-string", node=node)
                if num_args != 0:
                    self.add_message("implicit-format-spec",
                                     node=node,
                                     confidence=HIGH)
Example #4
0
    def _check_format_string(self, node, format_arg):
        """Checks that format string tokens match the supplied arguments.

        Args:
          node (astroid.node_classes.NodeNG): AST node to be checked.
          format_arg (int): Index of the format string in the node arguments.
        """
        num_args = _count_supplied_tokens(node.args[format_arg + 1 :])
        if not num_args:
            # If no args were supplied the string is not interpolated and can contain
            # formatting characters - it's used verbatim. Don't check any further.
            return
        format_string = node.args[format_arg].value
        if not isinstance(format_string, str):
            # If the log format is constant non-string (e.g. logging.debug(5)),
            # ensure there are no arguments.
            required_num_args = 0
        else:
            try:
                if self._format_style == "old":
                    keyword_args, required_num_args, _, _ = utils.parse_format_string(
                        format_string
                    )
                    if keyword_args:
                        # Keyword checking on logging strings is complicated by
                        # special keywords - out of scope.
                        return
                elif self._format_style == "new":
                    keyword_arguments, implicit_pos_args, explicit_pos_args = utils.parse_format_method_string(
                        format_string
                    )

                    keyword_args_cnt = len(
                        set(k for k, l in keyword_arguments if not isinstance(k, int))
                    )
                    required_num_args = (
                        keyword_args_cnt + implicit_pos_args + explicit_pos_args
                    )
            except utils.UnsupportedFormatCharacter as ex:
                char = format_string[ex.index]
                self.add_message(
                    "logging-unsupported-format",
                    node=node,
                    args=(char, ord(char), ex.index),
                )
                return
            except utils.IncompleteFormatString:
                self.add_message("logging-format-truncated", node=node)
                return
        if num_args > required_num_args:
            self.add_message("logging-too-many-args", node=node)
        elif num_args < required_num_args:
            self.add_message("logging-too-few-args", node=node)
Example #5
0
    def _check_format_string(self, node, format_arg):
        """Checks that format string tokens match the supplied arguments.

        Args:
          node (astroid.node_classes.NodeNG): AST node to be checked.
          format_arg (int): Index of the format string in the node arguments.
        """
        num_args = _count_supplied_tokens(node.args[format_arg + 1:])
        if not num_args:
            # If no args were supplied, then all format strings are valid -
            # don't check any further.
            return
        format_string = node.args[format_arg].value
        if not isinstance(format_string, str):
            # If the log format is constant non-string (e.g. logging.debug(5)),
            # ensure there are no arguments.
            required_num_args = 0
        else:
            try:
                if self._format_style == "%":
                    keyword_args, required_num_args, _, _ = utils.parse_format_string(
                        format_string)
                    if keyword_args:
                        # Keyword checking on logging strings is complicated by
                        # special keywords - out of scope.
                        return
                elif self._format_style == "{":
                    keys, num_args, manual_pos_arg = utils.parse_format_method_string(
                        format_string)

                    kargs = len(
                        set(k for k, l in keys if not isinstance(k, int)))
                    required_num_args = kargs + num_args + manual_pos_arg
            except utils.UnsupportedFormatCharacter as ex:
                char = format_string[ex.index]
                self.add_message(
                    "logging-unsupported-format",
                    node=node,
                    args=(char, ord(char), ex.index),
                )
                return
            except utils.IncompleteFormatString:
                self.add_message("logging-format-truncated", node=node)
                return
        if num_args > required_num_args:
            self.add_message("logging-too-many-args", node=node)
        elif num_args < required_num_args:
            self.add_message("logging-too-few-args", node=node)
Example #6
0
def test_parse_format_method_string():
    samples = [
        ("{}", 1),
        ("{}:{}", 2),
        ("{field}", 1),
        ("{:5}", 1),
        ("{:10}", 1),
        ("{field:10}", 1),
        ("{field:10}{{}}", 1),
        ("{:5}{!r:10}", 2),
        ("{:5}{}{{}}{}", 3),
        ("{0}{1}{0}", 2),
        ("Coordinates: {latitude}, {longitude}", 2),
        ("X: {0[0]};  Y: {0[1]}", 1),
        ("{:*^30}", 1),
        ("{!r:}", 1),
    ]
    for fmt, count in samples:
        keys, num_args, pos_args = utils.parse_format_method_string(fmt)
        keyword_args = len(set(k for k, l in keys if not isinstance(k, int)))
        assert keyword_args + num_args + pos_args == count
Example #7
0
    def _check_new_format(self, node, func):
        """Check the new string formatting. """
        # Skip format nodes which don't have an explicit string on the
        # left side of the format operation.
        # We do this because our inference engine can't properly handle
        # redefinitions of the original string.
        # Note that there may not be any left side at all, if the format method
        # has been assigned to another variable. See issue 351. For example:
        #
        #    fmt = 'some string {}'.format
        #    fmt('arg')
        if isinstance(node.func, astroid.Attribute) and not isinstance(
            node.func.expr, astroid.Const
        ):
            return
        if node.starargs or node.kwargs:
            return
        try:
            strnode = next(func.bound.infer())
        except astroid.InferenceError:
            return
        if not (isinstance(strnode, astroid.Const) and isinstance(strnode.value, str)):
            return
        try:
            call_site = astroid.arguments.CallSite.from_call(node)
        except astroid.InferenceError:
            return

        try:
            fields, num_args, manual_pos = utils.parse_format_method_string(
                strnode.value
            )
        except utils.IncompleteFormatString:
            self.add_message("bad-format-string", node=node)
            return

        positional_arguments = call_site.positional_arguments
        named_arguments = call_site.keyword_arguments
        named_fields = {field[0] for field in fields if isinstance(field[0], str)}
        if num_args and manual_pos:
            self.add_message("format-combined-specification", node=node)
            return

        check_args = False
        # Consider "{[0]} {[1]}" as num_args.
        num_args += sum(1 for field in named_fields if field == "")
        if named_fields:
            for field in named_fields:
                if field and field not in named_arguments:
                    self.add_message(
                        "missing-format-argument-key", node=node, args=(field,)
                    )
            for field in named_arguments:
                if field not in named_fields:
                    self.add_message(
                        "unused-format-string-argument", node=node, args=(field,)
                    )
            # num_args can be 0 if manual_pos is not.
            num_args = num_args or manual_pos
            if positional_arguments or num_args:
                empty = any(True for field in named_fields if field == "")
                if named_arguments or empty:
                    # Verify the required number of positional arguments
                    # only if the .format got at least one keyword argument.
                    # This means that the format strings accepts both
                    # positional and named fields and we should warn
                    # when one of the them is missing or is extra.
                    check_args = True
        else:
            check_args = True
        if check_args:
            # num_args can be 0 if manual_pos is not.
            num_args = num_args or manual_pos
            if len(positional_arguments) > num_args:
                self.add_message("too-many-format-args", node=node)
            elif len(positional_arguments) < num_args:
                self.add_message("too-few-format-args", node=node)

        self._detect_vacuous_formatting(node, positional_arguments)
        self._check_new_format_specifiers(node, fields, named_arguments)
Example #8
0
    def _check_new_format(self, node, func):
        """ Check the new string formatting. """
        # TODO: skip (for now) format nodes which don't have
        #       an explicit string on the left side of the format operation.
        #       We do this because our inference engine can't properly handle
        #       redefinitions of the original string.
        #       For more details, see issue 287.
        #
        # Note that there may not be any left side at all, if the format method
        # has been assigned to another variable. See issue 351. For example:
        #
        #    fmt = 'some string {}'.format
        #    fmt('arg')
        if isinstance(node.func, astroid.Attribute) and not isinstance(
            node.func.expr, astroid.Const
        ):
            return
        if node.starargs or node.kwargs:
            return
        try:
            strnode = next(func.bound.infer())
        except astroid.InferenceError:
            return
        if not (isinstance(strnode, astroid.Const) and isinstance(strnode.value, str)):
            return
        try:
            call_site = CallSite.from_call(node)
        except astroid.InferenceError:
            return

        try:
            fields, num_args, manual_pos = utils.parse_format_method_string(
                strnode.value
            )
        except utils.IncompleteFormatString:
            self.add_message("bad-format-string", node=node)
            return

        positional_arguments = call_site.positional_arguments
        named_arguments = call_site.keyword_arguments
        named_fields = {field[0] for field in fields if isinstance(field[0], str)}
        if num_args and manual_pos:
            self.add_message("format-combined-specification", node=node)
            return

        check_args = False
        # Consider "{[0]} {[1]}" as num_args.
        num_args += sum(1 for field in named_fields if field == "")
        if named_fields:
            for field in named_fields:
                if field and field not in named_arguments:
                    self.add_message(
                        "missing-format-argument-key", node=node, args=(field,)
                    )
            for field in named_arguments:
                if field not in named_fields:
                    self.add_message(
                        "unused-format-string-argument", node=node, args=(field,)
                    )
            # num_args can be 0 if manual_pos is not.
            num_args = num_args or manual_pos
            if positional_arguments or num_args:
                empty = any(True for field in named_fields if field == "")
                if named_arguments or empty:
                    # Verify the required number of positional arguments
                    # only if the .format got at least one keyword argument.
                    # This means that the format strings accepts both
                    # positional and named fields and we should warn
                    # when one of the them is missing or is extra.
                    check_args = True
        else:
            check_args = True
        if check_args:
            # num_args can be 0 if manual_pos is not.
            num_args = num_args or manual_pos
            if len(positional_arguments) > num_args:
                self.add_message("too-many-format-args", node=node)
            elif len(positional_arguments) < num_args:
                self.add_message("too-few-format-args", node=node)

        self._detect_vacuous_formatting(node, positional_arguments)
        self._check_new_format_specifiers(node, fields, named_arguments)
Example #9
0
    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,
            )