summaryrefslogtreecommitdiff
path: root/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/string_format.py
blob: 83c27734b6b25034936a90d3e61464323f6db694 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
"""Ansible specific pylint plugin for checking format string usage."""
# (c) 2018, Matt Martz <matt@sivel.net>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# -*- coding: utf-8 -*-
from __future__ import annotations

import astroid

# support pylint 2.x and 3.x -- remove when supporting only 3.x
try:
    from pylint.interfaces import IAstroidChecker
except ImportError:
    class IAstroidChecker:
        """Backwards compatibility for 2.x / 3.x support."""

try:
    from pylint.checkers.utils import check_messages
except ImportError:
    from pylint.checkers.utils import only_required_for_messages as check_messages

from pylint.checkers import BaseChecker
from pylint.checkers import utils

MSGS = {
    'E9305': ("disabled",  # kept for backwards compatibility with inline ignores, remove after 2.14 is EOL
              "ansible-format-automatic-specification",
              "disabled"),
    'E9390': ("bytes object has no .format attribute",
              "ansible-no-format-on-bytestring",
              "Used when a bytestring was used as a PEP 3101 format string "
              "as Python3 bytestrings do not have a .format attribute",
              {'minversion': (3, 0)}),
}


class AnsibleStringFormatChecker(BaseChecker):
    """Checks string formatting operations to ensure that the format string
    is valid and the arguments match the format string.
    """

    __implements__ = (IAstroidChecker,)
    name = 'string'
    msgs = MSGS

    @check_messages(*(MSGS.keys()))
    def visit_call(self, node):
        """Visit a call 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 _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


def register(linter):
    """required method to auto register this checker """
    linter.register_checker(AnsibleStringFormatChecker(linter))