From 8c1353537d0920d8137d5ea9d22843da67e41d9a Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Fri, 5 Jan 2018 15:35:05 -0800 Subject: [PATCH] Add string_format pylint plugin. --- test/sanity/pylint/plugins/string_format.py | 86 +++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 test/sanity/pylint/plugins/string_format.py diff --git a/test/sanity/pylint/plugins/string_format.py b/test/sanity/pylint/plugins/string_format.py new file mode 100644 index 0000000000..c9808cf399 --- /dev/null +++ b/test/sanity/pylint/plugins/string_format.py @@ -0,0 +1,86 @@ +# (c) 2018, Matt Martz +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import sys + +import six + +import astroid +from pylint.interfaces import IAstroidChecker +from pylint.checkers import BaseChecker +from pylint.checkers import utils +from pylint.checkers.utils import check_messages +from pylint.checkers.strings import parse_format_method_string + +_PY3K = sys.version_info[:2] >= (3, 0) + +MSGS = { + 'E9305': ("Format string contains automatic field numbering " + "specification", + "ansible-format-automatic-specification", + "Used when a PEP 3101 format string contains automatic " + "field numbering (e.g. '{}').", + {'minversion': (2, 6)}), + '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): + 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 _PY3K and isinstance(strnode.value, six.binary_type): + self.add_message('ansible-no-format-on-bytestring', node=node) + return + if not isinstance(strnode.value, six.string_types): + return + + if node.starargs or node.kwargs: + return + try: + fields, num_args, manual_pos = parse_format_method_string(strnode.value) + except utils.IncompleteFormatString: + return + + if num_args: + self.add_message('ansible-format-automatic-specification', + node=node) + return + + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(AnsibleStringFormatChecker(linter))