diff --git a/HACKING.rst b/HACKING.rst index e3d49e77..64a6ddcc 100644 --- a/HACKING.rst +++ b/HACKING.rst @@ -152,6 +152,8 @@ Example:: }, } +Do not use ``locals()`` for formatting strings, it is not clear as using +explicit dictionaries and can hide errors during refactoring. Calling Methods --------------- diff --git a/hacking/core.py b/hacking/core.py index d1cd1dfd..ff757327 100755 --- a/hacking/core.py +++ b/hacking/core.py @@ -586,6 +586,28 @@ def hacking_docstring_multiline_start(physical_line, previous_logical, tokens): "should start with a summary") +@flake8ext +def hacking_no_locals(logical_line, physical_line, tokens): + """Do not use locals() for string formatting. + + Okay: 'locals()' + Okay: 'locals' + Okay: locals() + Okay: print(locals()) + H501: print("%(something)" % locals()) + Okay: print("%(something)" % locals()) # noqa + """ + if pep8.noqa(physical_line): + return + for_formatting = False + for token_type, text, start, _, _ in tokens: + if text == "%" and token_type == tokenize.OP: + for_formatting = True + if (for_formatting and token_type == tokenize.NAME and text == + "locals" and "locals()" in logical_line): + yield (start[1], "H501: Do not use locals() for string formatting") + + @flake8ext def hacking_no_cr(physical_line): r"""Check that we only use newlines not carriage returns. diff --git a/setup.cfg b/setup.cfg index ac41d068..ce4e4abe 100644 --- a/setup.cfg +++ b/setup.cfg @@ -41,6 +41,7 @@ flake8.extension = H402 = hacking.core:hacking_docstring_one_line H403 = hacking.core:hacking_docstring_multiline_end H404 = hacking.core:hacking_docstring_multiline_start + H501 = hacking.core:hacking_no_locals H601 = hacking.core:hacking_no_cr H700 = hacking.core:hacking_localization_strings H801 = hacking.core:OnceGitCheckCommitTitleBug