Skip to content

Commit

Permalink
Add check to make sure only strings are assigned to __name__ (#3271)
Browse files Browse the repository at this point in the history
Close #583
  • Loading branch information
ninezerozeronine authored and PCManticore committed Dec 16, 2019
1 parent c4a954f commit dc83a86
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -356,3 +356,5 @@ contributors:
* Craig Henriques: contributor

* Matthijs Blom: contributor

* Andy Palmer: contributor
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ What's New in Pylint 2.5.0?

Release date: TBA

* Add a check for non string assignment to __name__ attribute.

Close #583

* `__pow__`, `__imatmul__`, `__trunc__`, `__floor__`, and `__ceil__` are recognized as special method names.

Close #3281
Expand Down
2 changes: 2 additions & 0 deletions doc/whatsnew/2.5.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,5 @@ separated list of regexes, that if a name matches will be always marked as a bla
* Mutable ``collections.*`` are now flagged as dangerous defaults.

* Add new --fail-under flag for setting the threshold for the score to fail overall tests. If the score is over the fail-under threshold, pylint will complete SystemExit with value 0 to indicate no errors.

* Add a new check (non-str-assignment-to-dunder-name) to ensure that only strings are assigned to ``__name__`` attributes
45 changes: 44 additions & 1 deletion pylint/checkers/typecheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
# Copyright (c) 2018 Konstantin <Github@pheanex.de>
# Copyright (c) 2018 Justin Li <justinnhli@users.noreply.github.com>
# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
# Copyright (c) 2019 Andy Palmer <contactninezerozeronine@gmail.com>

# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
# For details: https://github.com/PyCQA/pylint/blob/master/COPYING
Expand Down Expand Up @@ -389,6 +390,11 @@ def _missing_member_hint(owner, attrname, distance_threshold, max_choices):
"Emitted when the caller's argument names fully match the parameter "
"names in the function signature but do not have the same order.",
),
"W1115": (
"Non-string value assigned to __name__",
"non-str-assignment-to-dunder-name",
"Emitted when a non-string vaue is assigned to __name__",
),
}

# builtin sequence types in Python 2 and 3.
Expand Down Expand Up @@ -986,8 +992,20 @@ def _get_nomember_msgid_hint(self, node, owner):
hint = ""
return msg, hint

@check_messages("assignment-from-no-return", "assignment-from-none")
@check_messages(
"assignment-from-no-return",
"assignment-from-none",
"non-str-assignment-to-dunder-name",
)
def visit_assign(self, node):
"""
Process assignments in the AST.
"""

self._check_assignment_from_function_call(node)
self._check_dundername_is_string(node)

def _check_assignment_from_function_call(self, node):
"""check that if assigning to a function call, the function is
possibly returning something valuable
"""
Expand Down Expand Up @@ -1035,6 +1053,31 @@ def visit_assign(self, node):
else:
self.add_message("assignment-from-none", node=node)

def _check_dundername_is_string(self, node):
"""
Check a string is assigned to self.__name__
"""

# Check the left hand side of the assignment is <something>.__name__
lhs = node.targets[0]
if not isinstance(lhs, astroid.node_classes.AssignAttr):
return
if not lhs.attrname == "__name__":
return

# If the right hand side is not a string
rhs = node.value
if isinstance(rhs, astroid.Const) and isinstance(rhs.value, str):
return
inferred = utils.safe_infer(rhs)
if not inferred:
return
if not (
isinstance(inferred, astroid.Const) and isinstance(inferred.value, str)
):
# Add the message
self.add_message("non-str-assignment-to-dunder-name", node=node)

def _check_uninferable_call(self, node):
"""
Check that the given uninferable Call node does not
Expand Down
53 changes: 53 additions & 0 deletions tests/functional/n/non_str_assignment_to_dunder_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# pylint: disable=missing-module-docstring, missing-class-docstring
# pylint: disable=too-few-public-methods, missing-function-docstring
# pylint: disable=import-error

import random

from unknown import Unknown


class ExampleClass():
pass


def example_function():
pass


def returns_str():
return "abcd"


def returns_int():
return 0


def returns_tuple():
return 0, "abc"


# Might not be thorough if same hash seed is used in testing...
def returns_random_type():
if random.randint(0, 1) > 0:
return 0

return "abc"

ExampleClass.__name__ = 1 # [non-str-assignment-to-dunder-name]
ExampleClass.__name__ = True # [non-str-assignment-to-dunder-name]
ExampleClass.__name__ = returns_tuple() # [non-str-assignment-to-dunder-name]
ExampleClass.__name__ = returns_int() # [non-str-assignment-to-dunder-name]
ExampleClass.__name__ = "foo"
ExampleClass.__name__ = returns_str()
ExampleClass.__name__ = returns_random_type()
ExampleClass.__name__ = Unknown

example_function.__name__ = 1 # [non-str-assignment-to-dunder-name]
example_function.__name__ = True # [non-str-assignment-to-dunder-name]
example_function.__name__ = returns_tuple() # [non-str-assignment-to-dunder-name]
example_function.__name__ = returns_int() # [non-str-assignment-to-dunder-name]
example_function.__name__ = "foo"
example_function.__name__ = returns_str()
example_function.__name__ = returns_random_type()
example_function.__name__ = Unknown
8 changes: 8 additions & 0 deletions tests/functional/n/non_str_assignment_to_dunder_name.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
non-str-assignment-to-dunder-name:37::Non-string value assigned to __name__
non-str-assignment-to-dunder-name:38::Non-string value assigned to __name__
non-str-assignment-to-dunder-name:39::Non-string value assigned to __name__
non-str-assignment-to-dunder-name:40::Non-string value assigned to __name__
non-str-assignment-to-dunder-name:46::Non-string value assigned to __name__
non-str-assignment-to-dunder-name:47::Non-string value assigned to __name__
non-str-assignment-to-dunder-name:48::Non-string value assigned to __name__
non-str-assignment-to-dunder-name:49::Non-string value assigned to __name__

0 comments on commit dc83a86

Please sign in to comment.