Repository: wemake-services/wemake-python-styleguide

Issue:
Method `given_function_called` should only take the function name into account.
# Bug report

## What's wrong

The method `given_function_called(node: ast.Call, to_check: Container[str]) -> str` in `logic.tree.functions` is described as a method that returns the name of the function being called in `node`, in case it is included in `to_check`. For example:
 ```python
# Let's imagine we are visiting the Call node in `print(123, 456)` stored in `node`
called_function = given_function_called(node, ['print'])
print(called_function)
# Prints `print`
# But, if we are visiting `datetime.timedelta(days=1)`
called_function = given_function_called(node, ['timedelta'])
print(called_function)
# Prints an empty string, as if `timedelta` had not been called. The way for it to be shown is:
called_function = given_function_called(node, ['datetime.timedelta'])
print(called_function)
# Prints `datetime.timedelta`
```

This is related to https://github.com/wemake-services/wemake-python-styleguide/pull/1676#discussion_r508471791

## How is that should be

```python
# If we are visiting `datetime.timedelta(days=1)`
called_function = given_function_called(node, ['timedelta'])
print(called_function)
# Prints `timedelta`
```


Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1688?src=pr&el=h1) Report
> Merging [#1688](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1688?src=pr&el=desc) into [master](https://codecov.io/gh/wemake-services/wemake-python-styleguide/commit/c75c5b6dd812c8f0a953e3ae78887c5c7269c930?el=desc) will **not change** coverage.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1688/graphs/tree.svg?width=650&height=150&src=pr&token=yMZCKw0D02)](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1688?src=pr&el=tree)

```diff
@@            Coverage Diff            @@
##            master     #1688   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          105       105           
  Lines         5629      5610   -19     
  Branches      1255      1252    -3     
=========================================
- Hits          5629      5610   -19     
```


| [Impacted Files](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1688?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [wemake\_python\_styleguide/logic/tree/functions.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1688/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2xvZ2ljL3RyZWUvZnVuY3Rpb25zLnB5) | `100.00% <100.00%> (ø)` | |
| [wemake\_python\_styleguide/compat/nodes.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1688/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2NvbXBhdC9ub2Rlcy5weQ==) | `100.00% <0.00%> (ø)` | |
| [wemake\_python\_styleguide/logic/walrus.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1688/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2xvZ2ljL3dhbHJ1cy5weQ==) | `100.00% <0.00%> (ø)` | |
| [wemake\_python\_styleguide/compat/routing.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1688/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2NvbXBhdC9yb3V0aW5nLnB5) | `100.00% <0.00%> (ø)` | |
| [wemake\_python\_styleguide/logic/safe\_eval.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1688/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2xvZ2ljL3NhZmVfZXZhbC5weQ==) | `100.00% <0.00%> (ø)` | |
| [wemake\_python\_styleguide/compat/packaging.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1688/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2NvbXBhdC9wYWNrYWdpbmcucHk=) | `100.00% <0.00%> (ø)` | |
| [wemake\_python\_styleguide/visitors/ast/functions.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1688/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL3Zpc2l0b3JzL2FzdC9mdW5jdGlvbnMucHk=) | `100.00% <0.00%> (ø)` | |
| [wemake\_python\_styleguide/visitors/ast/operators.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1688/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL3Zpc2l0b3JzL2FzdC9vcGVyYXRvcnMucHk=) | `100.00% <0.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1688?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1688?src=pr&el=footer). Last update [c75c5b6...e0e01ef](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1688?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 13bd2e3e..735b36cc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,7 @@ Semantic versioning in our case means:
 - WPS531: Forbids testing conditions to just return booleans when it is possible to simply return the condition itself
 - Forbids to use unsafe infinite loops
 - Forbids to use raw strings `r''` when not necessary
+- Add parameter to `given_function_called` so that it checks if a function has been called by its name regardless of the modules or classes it is in
 - Forbids to use too complex f-strings
 
 ### Bugfixes
diff --git a/wemake_python_styleguide/logic/tree/functions.py b/wemake_python_styleguide/logic/tree/functions.py
index d8920de7..7e4203af 100644
--- a/wemake_python_styleguide/logic/tree/functions.py
+++ b/wemake_python_styleguide/logic/tree/functions.py
@@ -10,20 +10,21 @@ from wemake_python_styleguide.types import (
 )
 
 
-def given_function_called(node: Call, to_check: Container[str]) -> str:
+def given_function_called(
+    node: Call,
+    to_check: Container[str],
+    *,
+    split_modules: bool = False,
+) -> str:
     """
     Returns function name if it is called and contained in the container.
 
-    >>> import ast
-    >>> module = ast.parse('print(123, 456)')
-    >>> given_function_called(module.body[0].value, ['print'])
-    'print'
-
-    >>> given_function_called(module.body[0].value, ['adjust'])
-    ''
-
+    If `split_modules`, takes the modules or objects into account. Otherwise,
+    it only cares about the function's name.
     """
     function_name = source.node_to_string(node.func)
+    if split_modules:
+        function_name = function_name.split('.')[-1]
     if function_name in to_check:
         return function_name
     return ''


Test patch:
diff --git a/tests/test_logic/test_tree/test_functions.py b/tests/test_logic/test_tree/test_functions.py
new file mode 100644
index 00000000..f9bdd7bc
--- /dev/null
+++ b/tests/test_logic/test_tree/test_functions.py
@@ -0,0 +1,59 @@
+import pytest
+
+from wemake_python_styleguide.logic.tree import functions
+
+
+@pytest.mark.parametrize(('function_call', 'function_name'), [
+    # Simple builtin functions
+    ('print("Hello world!")', 'print'),
+    ('int("10")', 'int'),
+    ('bool(1)', 'bool'),
+    ('open("/tmp/file.txt", "r")', 'open'),
+    ('str(10)', 'str'),
+
+    # Functions in modules
+    ('datetime.timedelta(days=1)', 'datetime.timedelta'),
+    ('cmath.sqrt(100)', 'cmath.sqrt'),
+
+    # Functions in (made up) objects
+    ('dt.strftime("%H:%M")', 'dt.strftime'),
+    ('obj.funct()', 'obj.funct'),
+])
+def test_given_function_called_no_split(
+    parse_ast_tree, function_call: str, function_name: str,
+) -> None:
+    """Test given_function_called without splitting the modules."""
+    tree = parse_ast_tree(function_call)
+    node = tree.body[0].value
+    called_function = functions.given_function_called(node, [function_name])
+    assert called_function == function_name
+
+
+@pytest.mark.parametrize(('function_call', 'function_name'), [
+    # Simple builtin functions
+    ('print("Hello world!")', 'print'),
+    ('int("10")', 'int'),
+    ('bool(1)', 'bool'),
+    ('open("/tmp/file.txt", "r")', 'open'),
+    ('str(10)', 'str'),
+
+    # Functions in modules
+    ('datetime.timedelta(days=1)', 'timedelta'),
+    ('cmath.sqrt(100)', 'sqrt'),
+
+    # Functions in (made up) objects
+    ('dt.strftime("%H:%M")', 'strftime'),
+    ('obj.funct()', 'funct'),
+])
+def test_given_function_called_with_split(
+    parse_ast_tree, function_call: str, function_name: str,
+) -> None:
+    """Test given_function_called splitting the modules."""
+    tree = parse_ast_tree(function_call)
+    node = tree.body[0].value
+    called_function = functions.given_function_called(
+        node,
+        [function_name],
+        split_modules=True,
+    )
+    assert called_function == function_name