summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKound <carli.freudenberg@energymeteo.de>2022-01-10 22:48:46 +0100
committerGitHub <noreply@github.com>2022-01-10 22:48:46 +0100
commitd2475b42e3f4b17ca39eedd1ac65a929f70ca02e (patch)
tree89ad3b98f606b745dd91873fe0c5b6607469c368
parent83a4b8bdfcf31bc1f690de77794d9268a3c2d5ca (diff)
downloadpylint-git-d2475b42e3f4b17ca39eedd1ac65a929f70ca02e.tar.gz
Improve non ascii checker (#5643)
* split ``non-ascii-name`` into 3 different msgs - non-ascii-identifier (replaces non-ascii-name) - non-ascii-file-name (a warning) - non-ascii-module-import (only considering the namespace the import is imported in) Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com>
-rw-r--r--CONTRIBUTORS.txt3
-rw-r--r--ChangeLog9
-rw-r--r--doc/whatsnew/2.13.rst9
-rw-r--r--pylint/checkers/__init__.py9
-rw-r--r--pylint/checkers/base.py27
-rw-r--r--pylint/checkers/non_ascii_names.py201
-rw-r--r--pylint/constants.py1
-rw-r--r--pylint/testutils/checker_test_case.py15
-rw-r--r--tests/checkers/unittest_non_ascii_name.py279
-rw-r--r--tests/functional/n/non/non_ascii_name.rc3
-rw-r--r--tests/functional/n/non/non_ascii_name.txt4
-rw-r--r--tests/functional/n/non/non_ascii_name_backward_test_code.py7
-rw-r--r--tests/functional/n/non/non_ascii_name_backward_test_msg.py7
-rw-r--r--tests/functional/n/non_ascii_import/__init__.py0
-rw-r--r--tests/functional/n/non_ascii_import/non_ascii_import.py13
-rw-r--r--tests/functional/n/non_ascii_import/non_ascii_import_as_bad.py6
-rw-r--r--tests/functional/n/non_ascii_import/non_ascii_import_as_bad.txt1
-rw-r--r--tests/functional/n/non_ascii_import/non_ascii_import_as_okay.py12
-rw-r--r--tests/functional/n/non_ascii_import/non_ascii_import_from_as.py6
-rw-r--r--tests/functional/n/non_ascii_import/non_ascii_import_from_as.txt1
-rw-r--r--tests/functional/n/non_ascii_import/non_ascii_name_loł.py1
-rw-r--r--tests/functional/n/non_ascii_import/non_ascii_name_loł.txt1
-rw-r--r--tests/functional/n/non_ascii_name/__init__.py0
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.py4
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.rc2
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.txt1
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_decorator.py19
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_decorator.rc2
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_decorator.txt1
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_dict_kwargs.py13
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_for_loop.py14
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_for_loop.txt1
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_function.py19
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_function.txt1
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.py24
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.rc3
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.txt2
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.py25
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.rc2
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.txt2
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_inline_var.py8
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_inline_var.txt1
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.py17
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.rc3
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.txt1
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.py18
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.rc2
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.txt1
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_local.py9
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_local.txt1
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_pos_and_kwonly_function.py24
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_pos_and_kwonly_function.rc2
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_pos_and_kwonly_function.txt4
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_staticmethod.py17
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_staticmethod.txt1
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_try_except.py11
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_try_except.txt1
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_variable.py9
-rw-r--r--tests/functional/n/non_ascii_name/non_ascii_name_variable.txt2
-rw-r--r--tests/functional/n/non_ascii_name_class/__init__.py0
-rw-r--r--tests/functional/n/non_ascii_name_class/non_ascii_name_class.py23
-rw-r--r--tests/functional/n/non_ascii_name_class/non_ascii_name_class.txt1
-rw-r--r--tests/functional/n/non_ascii_name_class/non_ascii_name_class_attribute.py25
-rw-r--r--tests/functional/n/non_ascii_name_class/non_ascii_name_class_attribute.txt1
-rw-r--r--tests/functional/n/non_ascii_name_class/non_ascii_name_class_constant.py23
-rw-r--r--tests/functional/n/non_ascii_name_class/non_ascii_name_class_constant.txt1
-rw-r--r--tests/functional/n/non_ascii_name_class/non_ascii_name_class_method.py18
-rw-r--r--tests/functional/n/non_ascii_name_class/non_ascii_name_class_method.txt1
68 files changed, 945 insertions, 30 deletions
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index eee4da9a4..aa4fb2e24 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -595,3 +595,6 @@ contributors:
* Eero Vuojolahti: contributor
* Kian-Meng, Ang: contributor
+
+* Carli* Freudenberg (CarliJoy): contributor
+ - Improve non-ascii-name checker
diff --git a/ChangeLog b/ChangeLog
index df0a7313b..424d8b064 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -14,6 +14,15 @@ Release date: TBA
Closes #5588
+* Rewrote checker for ``non-ascii-name``.
+ It now ensures __all__ Python names are ASCII and also properly
+ checks the names of imports (``non-ascii-module-import``) as
+ well as file names (``non-ascii-file-name``) and emits their respective new warnings.
+
+ Non ASCII characters could be homoglyphs (look alike characters) and hard to
+ enter on a non specialized keyboard.
+ See `Confusable Characters in PEP 672 <https://www.python.org/dev/peps/pep-0672/#confusable-characters-in-identifiers>`_
+
* When run in parallel mode ``pylint`` now pickles the data passed to subprocesses with
the ``dill`` package. The ``dill`` package has therefore been added as a dependency.
diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst
index 10970a1dd..854fef618 100644
--- a/doc/whatsnew/2.13.rst
+++ b/doc/whatsnew/2.13.rst
@@ -14,6 +14,15 @@ New checkers
Closes #5460
+* Rewrote Checker of ``non-ascii-name``.
+ It now ensures __all__ Python names are ASCII and also properly
+ checks the names of imports (``non-ascii-module-import``) as
+ well as file names (``non-ascii-file-name``) and emits their respective new warnings.
+
+ Non ASCII characters could be homoglyphs (look alike characters) and hard to
+ enter on a non specialized keyboard.
+ See `Confusable Characters in PEP 672 <https://www.python.org/dev/peps/pep-0672/#confusable-characters-in-identifiers>`_
+
Removed checkers
================
diff --git a/pylint/checkers/__init__.py b/pylint/checkers/__init__.py
index 273b085a0..914425313 100644
--- a/pylint/checkers/__init__.py
+++ b/pylint/checkers/__init__.py
@@ -39,7 +39,14 @@ Base id of standard checkers (used in msg and report ids):
15: stdlib
16: python3
17: refactoring
-18-50: not yet used: reserved for future internal checkers.
+.
+.
+.
+24: non-ascii-names
+25-50: not yet used: reserved for future internal checkers.
+This file is not updated. Use
+ script/get_unused_message_id_category.py
+to get the next free checker id.
51-99: perhaps used: reserved for external checkers
The raw_metrics checker has no number associated since it doesn't emit any
diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py
index 2fe6123f1..2f79f852e 100644
--- a/pylint/checkers/base.py
+++ b/pylint/checkers/base.py
@@ -1737,11 +1737,6 @@ class NameChecker(_BasicChecker):
]
},
),
- "C0144": (
- '%s name "%s" contains a non-ASCII unicode character',
- "non-ascii-name",
- "Used when the name contains at least one non-ASCII unicode character.",
- ),
"W0111": (
"Name %s will become a keyword in Python %s",
"assign-to-new-keyword",
@@ -1838,7 +1833,6 @@ class NameChecker(_BasicChecker):
self._name_hints = {}
self._good_names_rgxs_compiled = []
self._bad_names_rgxs_compiled = []
- self._non_ascii_rgx_compiled = re.compile("[^\u0000-\u007F]")
def open(self):
self.linter.stats.reset_bad_names()
@@ -1878,7 +1872,7 @@ class NameChecker(_BasicChecker):
return regexps, hints
- @utils.check_messages("disallowed-name", "invalid-name", "non-ascii-name")
+ @utils.check_messages("disallowed-name", "invalid-name")
def visit_module(self, node: nodes.Module) -> None:
self._check_name("module", node.name.split(".")[-1], node)
self._bad_names = {}
@@ -1904,9 +1898,7 @@ class NameChecker(_BasicChecker):
for args in warnings:
self._raise_name_warning(prevalent_group, *args)
- @utils.check_messages(
- "disallowed-name", "invalid-name", "assign-to-new-keyword", "non-ascii-name"
- )
+ @utils.check_messages("disallowed-name", "invalid-name", "assign-to-new-keyword")
def visit_classdef(self, node: nodes.ClassDef) -> None:
self._check_assign_to_new_keyword_violation(node.name, node)
self._check_name("class", node.name, node)
@@ -1914,9 +1906,7 @@ class NameChecker(_BasicChecker):
if not any(node.instance_attr_ancestors(attr)):
self._check_name("attr", attr, anodes[0])
- @utils.check_messages(
- "disallowed-name", "invalid-name", "assign-to-new-keyword", "non-ascii-name"
- )
+ @utils.check_messages("disallowed-name", "invalid-name", "assign-to-new-keyword")
def visit_functiondef(self, node: nodes.FunctionDef) -> None:
# Do not emit any warnings if the method is just an implementation
# of a base class method.
@@ -1944,14 +1934,12 @@ class NameChecker(_BasicChecker):
visit_asyncfunctiondef = visit_functiondef
- @utils.check_messages("disallowed-name", "invalid-name", "non-ascii-name")
+ @utils.check_messages("disallowed-name", "invalid-name")
def visit_global(self, node: nodes.Global) -> None:
for name in node.names:
self._check_name("const", name, node)
- @utils.check_messages(
- "disallowed-name", "invalid-name", "assign-to-new-keyword", "non-ascii-name"
- )
+ @utils.check_messages("disallowed-name", "invalid-name", "assign-to-new-keyword")
def visit_assignname(self, node: nodes.AssignName) -> None:
"""check module level assigned names"""
self._check_assign_to_new_keyword_violation(node.name, node)
@@ -2041,11 +2029,6 @@ class NameChecker(_BasicChecker):
def _check_name(self, node_type, name, node, confidence=interfaces.HIGH):
"""check for a name using the type's regexp"""
- non_ascii_match = self._non_ascii_rgx_compiled.match(name)
- if non_ascii_match is not None:
- self._raise_name_warning(
- None, node, node_type, name, confidence, warning="non-ascii-name"
- )
def _should_exempt_from_invalid_name(node):
if node_type == "variable":
diff --git a/pylint/checkers/non_ascii_names.py b/pylint/checkers/non_ascii_names.py
new file mode 100644
index 000000000..6ad0df556
--- /dev/null
+++ b/pylint/checkers/non_ascii_names.py
@@ -0,0 +1,201 @@
+# Copyright (c) 2021-2022 Carli Freudenberg <carli.freudenberg@energymeteo.de>
+
+# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
+"""All alphanumeric unicode character are allowed in Python but due
+to similarities in how they look they can be confused.
+
+See: https://www.python.org/dev/peps/pep-0672/#confusable-characters-in-identifiers
+
+The following checkers are intended to make users are aware of these issues.
+"""
+
+import sys
+from typing import Optional, Union
+
+from astroid import nodes
+
+from pylint import constants, interfaces, lint
+from pylint.checkers import base_checker, utils
+
+if sys.version_info[:2] >= (3, 7):
+ # pylint: disable-next=fixme
+ # TODO: Remove after 3.6 has been deprecated
+ Py37Str = str
+else:
+
+ class Py37Str(str):
+ # Allow Python 3.6 compatibility
+ def isascii(self: str) -> bool:
+ return all("\u0000" <= x <= "\u007F" for x in self)
+
+
+NON_ASCII_HELP = (
+ "Used when the name contains at least one non-ASCII unicode character. "
+ "See https://www.python.org/dev/peps/pep-0672/#confusable-characters-in-identifiers"
+ " for a background why this could be bad. \n"
+ "If your programming guideline defines that you are programming in "
+ "English, then there should be no need for non ASCII characters in "
+ "Python Names. If not you can simply disable this check."
+)
+
+
+class NonAsciiNameChecker(base_checker.BaseChecker):
+ """A strict name checker only allowing ASCII
+
+ Note: This check only checks Names, so it ignores the content of
+ docstrings and comments!
+ """
+
+ __implements__ = interfaces.IAstroidChecker
+ priority = -1
+
+ msgs = {
+ "C2401": (
+ '%s name "%s" contains a non-ASCII character, consider renaming it.',
+ "non-ascii-name",
+ NON_ASCII_HELP,
+ {"old_names": [("C0144", "old-non-ascii-name")]},
+ ),
+ # First %s will always be "file"
+ "W2402": (
+ (
+ '%s name "%s" contains a non-ASCII character. PEP 3131 only allows '
+ "non-ascii identifiers, not file names."
+ ),
+ "non-ascii-file-name",
+ (
+ # Some = PyCharm at the time of writing didn't display the non_ascii_name_loł
+ # files and had big troubles with git.
+ # Probably only a bug shows the problem quite good.
+ # That's also why this is a warning and not only a convention!
+ "Some editors don't support non-ASCII file names properly. "
+ "Even though Python supports UTF-8 files since Python 3.5 this isn't "
+ "recommended for interoperability. Further reading:\n"
+ "- https://www.python.org/dev/peps/pep-0489/#export-hook-name\n"
+ "- https://www.python.org/dev/peps/pep-0672/#confusable-characters-in-identifiers\n"
+ "- https://bugs.python.org/issue20485\n"
+ ),
+ ),
+ # First %s will always be "module"
+ "C2403": (
+ '%s name "%s" contains a non-ASCII character, use an ASCII-only alias for import.',
+ "non-ascii-module-import",
+ NON_ASCII_HELP,
+ ),
+ }
+
+ name = "NonASCII-Checker"
+
+ def _check_name(
+ self, node_type: str, name: Optional[str], node: nodes.NodeNG
+ ) -> None:
+ """Check whether a name is using non-ASCII characters."""
+
+ if name is None:
+ # For some nodes i.e. *kwargs from a dict, the name will be empty
+ return
+
+ if not (Py37Str(name).isascii()):
+ type_label = constants.HUMAN_READABLE_TYPES[node_type]
+ args = (type_label.capitalize(), name)
+
+ msg = "non-ascii-name"
+
+ # Some node types have customized messages
+ if node_type == "file":
+ msg = "non-ascii-file-name"
+ elif node_type == "module":
+ msg = "non-ascii-module-import"
+
+ self.add_message(msg, node=node, args=args, confidence=interfaces.HIGH)
+
+ @utils.check_messages("non-ascii-name")
+ def visit_module(self, node: nodes.Module) -> None:
+ self._check_name("file", node.name.split(".")[-1], node)
+
+ @utils.check_messages("non-ascii-name")
+ def visit_functiondef(
+ self, node: Union[nodes.FunctionDef, nodes.AsyncFunctionDef]
+ ) -> None:
+ self._check_name("function", node.name, node)
+
+ # Check argument names
+ arguments = node.args
+
+ # Check position only arguments
+ if arguments.posonlyargs:
+ for pos_only_arg in arguments.posonlyargs:
+ self._check_name("argument", pos_only_arg.name, pos_only_arg)
+
+ # Check "normal" arguments
+ if arguments.args:
+ for arg in arguments.args:
+ self._check_name("argument", arg.name, arg)
+
+ # Check key word only arguments
+ if arguments.kwonlyargs:
+ for kwarg in arguments.kwonlyargs:
+ self._check_name("argument", kwarg.name, kwarg)
+
+ visit_asyncfunctiondef = visit_functiondef
+
+ @utils.check_messages("non-ascii-name")
+ def visit_global(self, node: nodes.Global) -> None:
+ for name in node.names:
+ self._check_name("const", name, node)
+
+ @utils.check_messages("non-ascii-name")
+ def visit_assignname(self, node: nodes.AssignName) -> None:
+ """check module level assigned names"""
+ # The NameChecker from which this Checker originates knows a lot of different
+ # versions of variables, i.e. constants, inline variables etc.
+ # To simplify we use only `variable` here, as we don't need to apply different
+ # rules to different types of variables.
+ frame = node.frame()
+
+ if isinstance(frame, nodes.FunctionDef):
+ if node.parent in frame.body:
+ # Only perform the check if the assignment was done in within the body
+ # of the function (and not the function parameter definition
+ # (will be handled in visit_functiondef)
+ # or within a decorator (handled in visit_call)
+ self._check_name("variable", node.name, node)
+ elif isinstance(frame, nodes.ClassDef):
+ self._check_name("attr", node.name, node)
+ else:
+ # Possibilities here:
+ # - isinstance(node.assign_type(), nodes.Comprehension) == inlinevar
+ # - isinstance(frame, nodes.Module) == variable (constant?)
+ # - some other kind of assigment missed but still most likely a variable
+ self._check_name("variable", node.name, node)
+
+ @utils.check_messages("non-ascii-name")
+ def visit_classdef(self, node: nodes.ClassDef) -> None:
+ self._check_name("class", node.name, node)
+ for attr, anodes in node.instance_attrs.items():
+ if not any(node.instance_attr_ancestors(attr)):
+ self._check_name("attr", attr, anodes[0])
+
+ def _check_module_import(self, node: Union[nodes.ImportFrom, nodes.Import]) -> None:
+ for module_name, alias in node.names:
+ name = alias or module_name
+ self._check_name("module", name, node)
+
+ @utils.check_messages("non-ascii-name")
+ def visit_import(self, node: nodes.Import) -> None:
+ self._check_module_import(node)
+
+ @utils.check_messages("non-ascii-name")
+ def visit_importfrom(self, node: nodes.ImportFrom) -> None:
+ self._check_module_import(node)
+
+ @utils.check_messages("non-ascii-name")
+ def visit_call(self, node: nodes.Call) -> None:
+ """Check if the used keyword args are correct."""
+ for keyword in node.keywords:
+ self._check_name("argument", keyword.arg, keyword)
+
+
+def register(linter: lint.PyLinter) -> None:
+ linter.register_checker(NonAsciiNameChecker(linter))
diff --git a/pylint/constants.py b/pylint/constants.py
index eb5b1118a..db54113ee 100644
--- a/pylint/constants.py
+++ b/pylint/constants.py
@@ -57,6 +57,7 @@ astroid {astroid.__version__}
Python {sys.version}"""
HUMAN_READABLE_TYPES = {
+ "file": "file",
"module": "module",
"const": "constant",
"class": "class",
diff --git a/pylint/testutils/checker_test_case.py b/pylint/testutils/checker_test_case.py
index af72136a9..5fabf97c5 100644
--- a/pylint/testutils/checker_test_case.py
+++ b/pylint/testutils/checker_test_case.py
@@ -32,12 +32,18 @@ class CheckerTestCase:
yield
@contextlib.contextmanager
- def assertAddsMessages(self, *messages: MessageTest) -> Generator[None, None, None]:
+ def assertAddsMessages(
+ self, *messages: MessageTest, ignore_position: bool = False
+ ) -> Generator[None, None, None]:
"""Assert that exactly the given method adds the given messages.
The list of messages must exactly match *all* the messages added by the
method. Additionally, we check to see whether the args in each message can
actually be substituted into the message string.
+
+ Using the keyword argument `ignore_position`, all checks for position
+ arguments (line, col_offset, ...) will be skipped. This can be used to
+ just test messages for the correct node.
"""
yield
got = self.linter.release_messages()
@@ -53,10 +59,15 @@ class CheckerTestCase:
for expected_msg, gotten_msg in zip(messages, got):
assert expected_msg.msg_id == gotten_msg.msg_id, msg
- assert expected_msg.line == gotten_msg.line, msg
assert expected_msg.node == gotten_msg.node, msg
assert expected_msg.args == gotten_msg.args, msg
assert expected_msg.confidence == gotten_msg.confidence, msg
+
+ if ignore_position:
+ # Do not check for line, col_offset etc...
+ continue
+
+ assert expected_msg.line == gotten_msg.line, msg
assert expected_msg.col_offset == gotten_msg.col_offset, msg
if PY38_PLUS:
# pylint: disable=fixme
diff --git a/tests/checkers/unittest_non_ascii_name.py b/tests/checkers/unittest_non_ascii_name.py
new file mode 100644
index 000000000..d0e3a85ab
--- /dev/null
+++ b/tests/checkers/unittest_non_ascii_name.py
@@ -0,0 +1,279 @@
+import sys
+from typing import Iterable, Optional
+
+import astroid
+import pytest
+from astroid import nodes
+
+import pylint.checkers.non_ascii_names
+import pylint.interfaces
+import pylint.testutils
+
+
+class TestNonAsciiChecker(pylint.testutils.CheckerTestCase):
+ CHECKER_CLASS = pylint.checkers.non_ascii_names.NonAsciiNameChecker
+ checker: pylint.checkers.non_ascii_names.NonAsciiNameChecker
+
+ @pytest.mark.skipif(
+ sys.version_info < (3, 8), reason="requires python3.8 or higher"
+ )
+ def test_kwargs_and_position_only(self):
+ """Even the new position only and keyword only should be found"""
+ node = astroid.extract_node(
+ """
+ def name(
+ ok,
+ not_økay,
+ not_okay_defaułt=None,
+ /,
+ p_or_kw_okay=None,
+ p_or_kw_not_økay=None,
+ *,
+ kw_arg_okay,
+ kw_arg_not_økay,
+ ):
+ ...
+ """
+ )
+ assert isinstance(node, nodes.FunctionDef)
+ arguments = node.args
+
+ posargs = list(arguments.posonlyargs)
+ args = list(arguments.args)
+ kwargs = list(arguments.kwonlyargs)
+
+ with self.assertAddsMessages(
+ pylint.testutils.MessageTest(
+ msg_id="non-ascii-name",
+ node=posargs[1],
+ args=("Argument", "not_økay"),
+ confidence=pylint.interfaces.HIGH,
+ ),
+ pylint.testutils.MessageTest(
+ msg_id="non-ascii-name",
+ node=posargs[2],
+ args=("Argument", "not_okay_defaułt"),
+ confidence=pylint.interfaces.HIGH,
+ ),
+ pylint.testutils.MessageTest(
+ msg_id="non-ascii-name",
+ node=args[1],
+ args=("Argument", "p_or_kw_not_økay"),
+ confidence=pylint.interfaces.HIGH,
+ ),
+ pylint.testutils.MessageTest(
+ msg_id="non-ascii-name",
+ node=kwargs[1],
+ args=("Argument", "kw_arg_not_økay"),
+ confidence=pylint.interfaces.HIGH,
+ ),
+ ignore_position=True,
+ ):
+ self.checker.visit_functiondef(node)
+
+ @pytest.mark.parametrize(
+ "code, assign_type",
+ [
+ pytest.param(
+ """
+ try:
+ ...
+ except ValueError as łol: #@
+ ...
+ """,
+ "Variable",
+ id="try-except",
+ ),
+ pytest.param(
+ """
+ class FooBar:
+ łol = "test" #@
+ """,
+ "Attribute",
+ id="class_attribute",
+ ),
+ pytest.param(
+ """
+ łol = "test" #@
+ """,
+ "Variable",
+ id="global_assign",
+ ),
+ pytest.param(
+ """
+ def foobar():
+ łol="test" #@
+ """,
+ "Variable",
+ id="function_variable",
+ ),
+ pytest.param(
+ """
+ for łol in os.listdir("."): #@
+ ...
+ """,
+ "Variable",
+ id="for_loop_variable",
+ ),
+ pytest.param(
+ """
+ [łoł
+ for łol in os.listdir(".") #@
+ ]
+ """,
+ "Variable",
+ id="inline_for_loop_variable",
+ ),
+ ],
+ )
+ def test_assignname(
+ self,
+ code: str,
+ assign_type: str,
+ ):
+ """Variables defined no matter where, should be checked for non ascii"""
+ assign_node = astroid.extract_node(code)
+
+ if not isinstance(assign_node, nodes.AssignName):
+ # For some elements we can't directly extract the assign
+ # node, so we have to manually look in the children for it
+ for child in assign_node.get_children():
+ if isinstance(child, nodes.AssignName):
+ assign_node = child
+ break
+
+ # Just to make sure we found the correct node
+ assert isinstance(assign_node, nodes.AssignName)
+
+ with self.assertAddsMessages(
+ pylint.testutils.MessageTest(
+ msg_id="non-ascii-name",
+ node=assign_node,
+ args=(assign_type, "łol"),
+ confidence=pylint.interfaces.HIGH,
+ ),
+ ignore_position=True,
+ ):
+ self.checker.visit_assignname(assign_node)
+
+ @pytest.mark.parametrize(
+ "import_statement, wrong_name",
+ [
+ pytest.param("import fürimma", "fürimma", id="bad_single_main_module"),
+ pytest.param(
+ "import fürimma as okay",
+ None,
+ id="bad_single_main_module_with_okay_alias",
+ ),
+ pytest.param(
+ "import fürimma, pathlib",
+ "fürimma",
+ id="bad_single_main_module_with_stdlib_import",
+ ),
+ pytest.param(
+ "import pathlib, os, foobar, fürimma",
+ "fürimma",
+ id="stdlib_with_bad_single_main_module",
+ ),
+ pytest.param(
+ "import pathlib, os, foobar, sys as systëm",
+ "systëm",
+ id="stdlib_with_bad_alias",
+ ),
+ pytest.param(
+ "import fürimma as okay, pathlib",
+ None,
+ id="bad_single_main_module_with_okay_alias_with_stdlib_import",
+ ),
+ pytest.param(
+ "import fürimma.submodule", "fürimma.submodule", id="bad_main_module"
+ ),
+ pytest.param(
+ "import fürimma.submodule as submodule",
+ None,
+ id="bad_main_module_with_okay_alias",
+ ),
+ pytest.param(
+ "import main_module.fürimma", "main_module.fürimma", id="bad_submodule"
+ ),
+ pytest.param(
+ "import main_module.fürimma as okay",
+ None,
+ id="bad_submodule_with_okay_alias",
+ ),
+ pytest.param(
+ "import main_module.fürimma as not_økay",
+ "not_økay",
+ id="bad_submodule_with_bad_alias",
+ ),
+ pytest.param(
+ "from foo.bar import function", None, id="from_okay_module_import_okay"
+ ),
+ pytest.param(
+ "from foo.bär import function", None, id="from_bad_module_import_okay"
+ ),
+ pytest.param(
+ "from foo.bar import functiøn",
+ "functiøn",
+ id="from_okay_module_import_bad",
+ ),
+ pytest.param(
+ "from foo.bar import functiøn as function",
+ None,
+ id="from_okay_module_import_bad_as_good",
+ ),
+ pytest.param(
+ "from foo.bär import functiøn as function",
+ None,
+ id="from_bad_module_import_bad_as_good",
+ ),
+ pytest.param(
+ "from foo.bar import functiøn as føl",
+ "føl",
+ id="from_okay_module_import_bad_as_bad",
+ ),
+ pytest.param(
+ "from foo.bar import functiøn as good, bäd",
+ "bäd",
+ id="from_okay_module_import_bad_as_good_and_bad",
+ ),
+ pytest.param(
+ "from foo.bar import functiøn as good, bäd",
+ "bäd",
+ id="from_okay_module_import_bad_as_good_and_bad",
+ ),
+ pytest.param(
+ "from foo.bar import functiøn as good, *",
+ # We still have functiøn within our namespace and could detect this
+ # But to do this properly we would need to check all `*` imports
+ # -> Too much effort!
+ "functiøn",
+ id="from_okay_module_import_bad_as_good_and_star",
+ marks=pytest.mark.xfail(
+ reason="We don't know what is imported when using star"
+ ),
+ ),
+ ],
+ )
+ def test_check_import(self, import_statement: str, wrong_name: Optional[str]):
+ """We expect that for everything that user can change there is a message"""
+ node = astroid.extract_node(f"{import_statement} #@")
+
+ expected_msgs: Iterable[pylint.testutils.MessageTest] = tuple()
+
+ if wrong_name:
+ expected_msgs = (
+ pylint.testutils.MessageTest(
+ msg_id="non-ascii-module-import",
+ node=node,
+ args=("Module", wrong_name),
+ confidence=pylint.interfaces.HIGH,
+ ),
+ )
+ with self.assertAddsMessages(*expected_msgs, ignore_position=True):
+ if import_statement.startswith("from"):
+ assert isinstance(node, nodes.ImportFrom)
+ self.checker.visit_importfrom(node)
+ else:
+ assert isinstance(node, nodes.Import)
+ self.checker.visit_import(node)
diff --git a/tests/functional/n/non/non_ascii_name.rc b/tests/functional/n/non/non_ascii_name.rc
deleted file mode 100644
index ff09ea1f6..000000000
--- a/tests/functional/n/non/non_ascii_name.rc
+++ /dev/null
@@ -1,3 +0,0 @@
-[testoptions]
-# This test cannot run on Windows due to Unicode error formatting.
-exclude_platforms=win32
diff --git a/tests/functional/n/non/non_ascii_name.txt b/tests/functional/n/non/non_ascii_name.txt
index ba5338bb9..2cda6962e 100644
--- a/tests/functional/n/non/non_ascii_name.txt
+++ b/tests/functional/n/non/non_ascii_name.txt
@@ -1,2 +1,2 @@
-non-ascii-name:3:0:3:10::"Constant name ""áéíóú"" contains a non-ASCII unicode character":HIGH
-non-ascii-name:5:0:6:12:úóíéá:"Function name ""úóíéá"" contains a non-ASCII unicode character":HIGH
+non-ascii-name:3:0:3:10::"Variable name ""áéíóú"" contains a non-ASCII character, consider renaming it.":HIGH
+non-ascii-name:5:0:6:12:úóíéá:"Function name ""úóíéá"" contains a non-ASCII character, consider renaming it.":HIGH
diff --git a/tests/functional/n/non/non_ascii_name_backward_test_code.py b/tests/functional/n/non/non_ascii_name_backward_test_code.py
new file mode 100644
index 000000000..36a22ca52
--- /dev/null
+++ b/tests/functional/n/non/non_ascii_name_backward_test_code.py
@@ -0,0 +1,7 @@
+""" Tests for non-ascii-name checker working as expected after a major refactor """
+# pylint: disable=C0144, use-symbolic-message-instead
+
+áéíóú = 4444
+
+def úóíéá():
+ """yo"""
diff --git a/tests/functional/n/non/non_ascii_name_backward_test_msg.py b/tests/functional/n/non/non_ascii_name_backward_test_msg.py
new file mode 100644
index 000000000..e63f3a4db
--- /dev/null
+++ b/tests/functional/n/non/non_ascii_name_backward_test_msg.py
@@ -0,0 +1,7 @@
+""" Tests for non-ascii-name checker working as expected after a major refactor """
+# pylint: disable=non-ascii-name
+
+áéíóú = 4444
+
+def úóíéá():
+ """yo"""
diff --git a/tests/functional/n/non_ascii_import/__init__.py b/tests/functional/n/non_ascii_import/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/functional/n/non_ascii_import/__init__.py
diff --git a/tests/functional/n/non_ascii_import/non_ascii_import.py b/tests/functional/n/non_ascii_import/non_ascii_import.py
new file mode 100644
index 000000000..76163b286
--- /dev/null
+++ b/tests/functional/n/non_ascii_import/non_ascii_import.py
@@ -0,0 +1,13 @@
+"""Test that invalid module name imports causes correct error"""
+# pylint: disable=import-error, wrong-import-position, unused-wildcard-import, wildcard-import, wrong-import-order
+import sys
+import os
+
+# allow module imports to test that this is indeed a valid python file
+SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
+sys.path.append(os.path.dirname(SCRIPT_DIR))
+
+import non_ascii_name_loł as ok
+from pathlib import * # test that star imports work correctly and give no error
+
+print(ok)
diff --git a/tests/functional/n/non_ascii_import/non_ascii_import_as_bad.py b/tests/functional/n/non_ascii_import/non_ascii_import_as_bad.py
new file mode 100644
index 000000000..df392961f
--- /dev/null
+++ b/tests/functional/n/non_ascii_import/non_ascii_import_as_bad.py
@@ -0,0 +1,6 @@
+"""import non ascii alias"""
+import os.path as łos # [non-ascii-module-import]
+
+
+# Usage should not raise a second error
+foo = łos.join("a", "b")
diff --git a/tests/functional/n/non_ascii_import/non_ascii_import_as_bad.txt b/tests/functional/n/non_ascii_import/non_ascii_import_as_bad.txt
new file mode 100644
index 000000000..595d387f7
--- /dev/null
+++ b/tests/functional/n/non_ascii_import/non_ascii_import_as_bad.txt
@@ -0,0 +1 @@
+non-ascii-module-import:2:0:2:22::"Module name ""łos"" contains a non-ASCII character, use an ASCII-only alias for import.":HIGH
diff --git a/tests/functional/n/non_ascii_import/non_ascii_import_as_okay.py b/tests/functional/n/non_ascii_import/non_ascii_import_as_okay.py
new file mode 100644
index 000000000..35f8513fa
--- /dev/null
+++ b/tests/functional/n/non_ascii_import/non_ascii_import_as_okay.py
@@ -0,0 +1,12 @@
+"""Test that invalid module name do not cause errors when using an alias"""
+# pylint: disable=import-error, wrong-import-position
+import sys
+import os
+
+# allow module imports to test that this is indeed a valid python file
+SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
+sys.path.append(os.path.dirname(SCRIPT_DIR))
+
+import non_ascii_name_loł as foobar
+
+print(foobar)
diff --git a/tests/functional/n/non_ascii_import/non_ascii_import_from_as.py b/tests/functional/n/non_ascii_import/non_ascii_import_from_as.py
new file mode 100644
index 000000000..68beba0ec
--- /dev/null
+++ b/tests/functional/n/non_ascii_import/non_ascii_import_from_as.py
@@ -0,0 +1,6 @@
+"""import as non ascii alias"""
+from os.path import join as łos # [non-ascii-module-import]
+
+
+# Usage should not raise a second error
+foo = łos("a", "b")
diff --git a/tests/functional/n/non_ascii_import/non_ascii_import_from_as.txt b/tests/functional/n/non_ascii_import/non_ascii_import_from_as.txt
new file mode 100644
index 000000000..58daa481b
--- /dev/null
+++ b/tests/functional/n/non_ascii_import/non_ascii_import_from_as.txt
@@ -0,0 +1 @@
+non-ascii-module-import:2:0:2:32::"Module name ""łos"" contains a non-ASCII character, use an ASCII-only alias for import.":HIGH
diff --git a/tests/functional/n/non_ascii_import/non_ascii_name_loł.py b/tests/functional/n/non_ascii_import/non_ascii_name_loł.py
new file mode 100644
index 000000000..bc59418e1
--- /dev/null
+++ b/tests/functional/n/non_ascii_import/non_ascii_name_loł.py
@@ -0,0 +1 @@
+# [non-ascii-file-name]
diff --git a/tests/functional/n/non_ascii_import/non_ascii_name_loł.txt b/tests/functional/n/non_ascii_import/non_ascii_name_loł.txt
new file mode 100644
index 000000000..eefaa197a
--- /dev/null
+++ b/tests/functional/n/non_ascii_import/non_ascii_name_loł.txt
@@ -0,0 +1 @@
+non-ascii-file-name:1:0:None:None::"File name ""non_ascii_name_loł"" contains a non-ASCII character. PEP 3131 only allows non-ascii identifiers, not file names.":HIGH
diff --git a/tests/functional/n/non_ascii_name/__init__.py b/tests/functional/n/non_ascii_name/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/__init__.py
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.py b/tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.py
new file mode 100644
index 000000000..ac51b9c4d
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.py
@@ -0,0 +1,4 @@
+"""Assigment Expression as defined in https://www.python.org/dev/peps/pep-0572/"""
+
+if (loł := __name__) == "__main__": # [non-ascii-name]
+ print(loł)
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.rc b/tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.rc
new file mode 100644
index 000000000..85fc502b3
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.rc
@@ -0,0 +1,2 @@
+[testoptions]
+min_pyver=3.8
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.txt b/tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.txt
new file mode 100644
index 000000000..8a5acceb8
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_assignment_expressions.txt
@@ -0,0 +1 @@
+non-ascii-name:3:4:3:8::"Variable name ""loł"" contains a non-ASCII character, consider renaming it.":HIGH
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_decorator.py b/tests/functional/n/non_ascii_name/non_ascii_name_decorator.py
new file mode 100644
index 000000000..06900ac7c
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_decorator.py
@@ -0,0 +1,19 @@
+"""
+Ensure that also decorator calls caught correctly
+Column and line not correctly detected with Python 3.8, so we
+skip it and only test versions after that.
+"""
+
+
+def decoractor_func(*args, **kwargs):
+ """A docstring"""
+ return lambda x: f"Foobar {args} {kwargs}"
+
+
+@decoractor_func(
+ aaaaaaaaaaaaaaaaaaalllllllooooooooooooonnngggggggggglllline=1,
+ normal=2,
+ fåling=3, # [non-ascii-name]
+)
+def a_function():
+ """A docstring"""
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_decorator.rc b/tests/functional/n/non_ascii_name/non_ascii_name_decorator.rc
new file mode 100644
index 000000000..16b75eea7
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_decorator.rc
@@ -0,0 +1,2 @@
+[testoptions]
+min_pyver=3.9
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_decorator.txt b/tests/functional/n/non_ascii_name/non_ascii_name_decorator.txt
new file mode 100644
index 000000000..6c4944009
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_decorator.txt
@@ -0,0 +1 @@
+non-ascii-name:16:4:16:13:a_function:"Argument name ""fåling"" contains a non-ASCII character, consider renaming it.":HIGH
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_dict_kwargs.py b/tests/functional/n/non_ascii_name/non_ascii_name_dict_kwargs.py
new file mode 100644
index 000000000..5d7b4c7f3
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_dict_kwargs.py
@@ -0,0 +1,13 @@
+"""
+We don't expect this to give any errors!
+"""
+
+
+def okay(**kwargs):
+ """print kwargs"""
+ print(kwargs)
+
+
+keyword_args = {"łol": "this would be hard to check against"}
+
+okay(**keyword_args)
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_for_loop.py b/tests/functional/n/non_ascii_name/non_ascii_name_for_loop.py
new file mode 100644
index 000000000..59585645a
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_for_loop.py
@@ -0,0 +1,14 @@
+"""invalid ascii char in a for loop"""
+
+import os
+
+
+def main():
+ """main func"""
+ # +2: [non-ascii-name]
+ a_variable = ""
+ for łol in os.listdir("."):
+ # Usage should not raise a second error
+ a_variable += (
+ f"{łol} "
+ )
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_for_loop.txt b/tests/functional/n/non_ascii_name/non_ascii_name_for_loop.txt
new file mode 100644
index 000000000..8995dc0eb
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_for_loop.txt
@@ -0,0 +1 @@
+non-ascii-name:10:8:10:12:main:"Variable name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_function.py b/tests/functional/n/non_ascii_name/non_ascii_name_function.py
new file mode 100644
index 000000000..e3c558ea6
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_function.py
@@ -0,0 +1,19 @@
+"""invalid ASCII char in a function definition"""
+# pylint: disable=invalid-name
+
+
+def sayHello():
+ """Greetings"""
+ print("Hello, World!")
+
+
+# +3: [non-ascii-name]
+
+
+def sayНello():
+ """From Russia with Love"""
+ print("Goodbye, World!")
+
+
+# Usage should not raise a second error
+sayНello()
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_function.txt b/tests/functional/n/non_ascii_name/non_ascii_name_function.txt
new file mode 100644
index 000000000..949059da9
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_function.txt
@@ -0,0 +1 @@
+non-ascii-name:13:0:15:28:sayНello:"Function name ""sayНello"" contains a non-ASCII character, consider renaming it.":HIGH
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.py b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.py
new file mode 100644
index 000000000..a75e1c6b7
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.py
@@ -0,0 +1,24 @@
+"""
+non ascii variable defined in a function
+
+This test is only for 3.8 as the starting column is incorrect
+"""
+
+
+def okay(
+ just_some_thing_long_again: str,
+ lol_very_long_argument: str,
+ łol: str, # [non-ascii-name]
+) -> bool:
+ """Be okay, yeah?"""
+ # Usage should not raise a second error
+ print(just_some_thing_long_again, lol_very_long_argument, łol)
+ return True
+
+
+# Usage should raise a second error
+okay(
+ "A VVVVVVVEEEERRRRRRRRRRYYYYYYYYYY LONG TIME ",
+ lol_very_long_argument="a",
+ łol="b", # [non-ascii-name]
+)
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.rc b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.rc
new file mode 100644
index 000000000..35b185fdb
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.rc
@@ -0,0 +1,3 @@
+[testoptions]
+min_pyver=3.8
+max_pyver=3.9
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.txt b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.txt
new file mode 100644
index 000000000..781336474
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py38.txt
@@ -0,0 +1,2 @@
+non-ascii-name:11:4:11:13:okay:"Argument name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH
+non-ascii-name:23:0:None:None::"Argument name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.py b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.py
new file mode 100644
index 000000000..a2d87ac4d
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.py
@@ -0,0 +1,25 @@
+"""
+non ascii variable defined in a function
+
+This test is 3.9+ and not using 'min_pyver_end_position'
+as the starting column is also incorrect on < 3.9
+"""
+
+
+def okay(
+ just_some_thing_long_again: str,
+ lol_very_long_argument: str,
+ łol: str, # [non-ascii-name]
+) -> bool:
+ """Be okay, yeah?"""
+ # Usage should not raise a second error
+ print(just_some_thing_long_again, lol_very_long_argument, łol)
+ return True
+
+
+# Usage should raise a second error
+okay(
+ "A VVVVVVVEEEERRRRRRRRRRYYYYYYYYYY LONG TIME ",
+ lol_very_long_argument="a",
+ łol="b", # [non-ascii-name]
+)
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.rc b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.rc
new file mode 100644
index 000000000..16b75eea7
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.rc
@@ -0,0 +1,2 @@
+[testoptions]
+min_pyver=3.9
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.txt b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.txt
new file mode 100644
index 000000000..0222308af
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_function_argument_py39plus.txt
@@ -0,0 +1,2 @@
+non-ascii-name:12:4:12:13:okay:"Argument name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH
+non-ascii-name:24:4:24:12::"Argument name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_inline_var.py b/tests/functional/n/non_ascii_name/non_ascii_name_inline_var.py
new file mode 100644
index 000000000..2d47e58c2
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_inline_var.py
@@ -0,0 +1,8 @@
+"""inline loop non ascii variable definition"""
+import os
+
+
+foo = [
+ f"{łol} "
+ for łol in os.listdir(".") # [non-ascii-name]
+]
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_inline_var.txt b/tests/functional/n/non_ascii_name/non_ascii_name_inline_var.txt
new file mode 100644
index 000000000..64515296b
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_inline_var.txt
@@ -0,0 +1 @@
+non-ascii-name:7:8:7:12::"Variable name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.py b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.py
new file mode 100644
index 000000000..3c7b8c683
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.py
@@ -0,0 +1,17 @@
+"""
+Defining non ASCII variables in a function call
+
+This test is only for 3.8 as the starting column is incorrect
+"""
+
+
+def okay(**kwargs):
+ """Print kwargs"""
+ print(kwargs)
+
+
+okay(
+ a_long_attribute_that_is_very_okay=1,
+ b_belongs_to_yet_another_okay_attributed=2,
+ łol=3, # [non-ascii-name]
+)
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.rc b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.rc
new file mode 100644
index 000000000..35b185fdb
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.rc
@@ -0,0 +1,3 @@
+[testoptions]
+min_pyver=3.8
+max_pyver=3.9
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.txt b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.txt
new file mode 100644
index 000000000..b43189658
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py38.txt
@@ -0,0 +1 @@
+non-ascii-name:16:0:None:None::"Argument name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.py b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.py
new file mode 100644
index 000000000..0dfceb38f
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.py
@@ -0,0 +1,18 @@
+"""
+Defining non ASCII variables in a function call
+
+This test is 3.9+ and not using 'min_pyver_end_position'
+as the starting column is also incorrect on < 3.9
+"""
+
+
+def okay(**kwargs):
+ """Print kwargs"""
+ print(kwargs)
+
+
+okay(
+ a_long_attribute_that_is_very_okay=1,
+ b_belongs_to_yet_another_okay_attributed=2,
+ łol=3, # [non-ascii-name]
+)
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.rc b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.rc
new file mode 100644
index 000000000..16b75eea7
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.rc
@@ -0,0 +1,2 @@
+[testoptions]
+min_pyver=3.9
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.txt b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.txt
new file mode 100644
index 000000000..7595a9d08
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_kwargs_py39plus.txt
@@ -0,0 +1 @@
+non-ascii-name:17:4:17:10::"Argument name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_local.py b/tests/functional/n/non_ascii_name/non_ascii_name_local.py
new file mode 100644
index 000000000..99423fdc8
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_local.py
@@ -0,0 +1,9 @@
+"""Using non ascii variables in local"""
+
+
+def okay():
+ """docstring"""
+ łol = "foo" # [non-ascii-name]
+ # Usage should not raise a second error
+ baring = łol
+ print(baring)
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_local.txt b/tests/functional/n/non_ascii_name/non_ascii_name_local.txt
new file mode 100644
index 000000000..8e5e684fa
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_local.txt
@@ -0,0 +1 @@
+non-ascii-name:6:4:6:8:okay:"Variable name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_pos_and_kwonly_function.py b/tests/functional/n/non_ascii_name/non_ascii_name_pos_and_kwonly_function.py
new file mode 100644
index 000000000..651d78f89
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_pos_and_kwonly_function.py
@@ -0,0 +1,24 @@
+"""
+Test for names within keyword and position only function
+
+This test is 3.8+ as the columns are not correctly identified
+by the ast parser < 3.8
+"""
+# pylint: disable=unused-argument
+
+
+def name(
+ some_thing_long_but_okay,
+ not_okay_łol, # [non-ascii-name]
+ not_okay_defaułt=None, # [non-ascii-name]
+ /,
+ p_or_kw_okay=None,
+ p_or_kw_not_økay=None, # [non-ascii-name]
+ *,
+ kw_arg_okay,
+ kw_arg_not_økay, # [non-ascii-name]
+):
+ """
+ Do something!
+ """
+ return "Foobar"
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_pos_and_kwonly_function.rc b/tests/functional/n/non_ascii_name/non_ascii_name_pos_and_kwonly_function.rc
new file mode 100644
index 000000000..85fc502b3
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_pos_and_kwonly_function.rc
@@ -0,0 +1,2 @@
+[testoptions]
+min_pyver=3.8
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_pos_and_kwonly_function.txt b/tests/functional/n/non_ascii_name/non_ascii_name_pos_and_kwonly_function.txt
new file mode 100644
index 000000000..b13a1af3b
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_pos_and_kwonly_function.txt
@@ -0,0 +1,4 @@
+non-ascii-name:12:4:12:17:name:"Argument name ""not_okay_łol"" contains a non-ASCII character, consider renaming it.":HIGH
+non-ascii-name:13:4:13:21:name:"Argument name ""not_okay_defaułt"" contains a non-ASCII character, consider renaming it.":HIGH
+non-ascii-name:16:4:16:21:name:"Argument name ""p_or_kw_not_økay"" contains a non-ASCII character, consider renaming it.":HIGH
+non-ascii-name:19:4:19:20:name:"Argument name ""kw_arg_not_økay"" contains a non-ASCII character, consider renaming it.":HIGH
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_staticmethod.py b/tests/functional/n/non_ascii_name/non_ascii_name_staticmethod.py
new file mode 100644
index 000000000..9b10f1cd4
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_staticmethod.py
@@ -0,0 +1,17 @@
+"""static method with non ascii characters"""
+
+
+class OkayClass:
+ """Class Docstring"""
+
+ def public(self):
+ """Say it load"""
+
+ @staticmethod
+ def umlaut_ä(): # [non-ascii-name]
+ """Say ä"""
+ return "ä"
+
+
+# Usage should not raise a second error
+OkayClass.umlaut_ä()
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_staticmethod.txt b/tests/functional/n/non_ascii_name/non_ascii_name_staticmethod.txt
new file mode 100644
index 000000000..8bc3e8ce6
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_staticmethod.txt
@@ -0,0 +1 @@
+non-ascii-name:11:4:13:19:OkayClass.umlaut_ä:"Function name ""umlaut_ä"" contains a non-ASCII character, consider renaming it.":HIGH
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_try_except.py b/tests/functional/n/non_ascii_name/non_ascii_name_try_except.py
new file mode 100644
index 000000000..beccf4b9a
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_try_except.py
@@ -0,0 +1,11 @@
+"""
+Also variables defined in except can't contain non ascii chars
+"""
+
+
+try:
+ raise AttributeError("Test")
+ # +1: [non-ascii-name]
+except AttributeError as łol:
+ # Usage should not raise a second error
+ foo = łol
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_try_except.txt b/tests/functional/n/non_ascii_name/non_ascii_name_try_except.txt
new file mode 100644
index 000000000..f6b0d6c6c
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_try_except.txt
@@ -0,0 +1 @@
+non-ascii-name:9:0:11:14::"Variable name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_variable.py b/tests/functional/n/non_ascii_name/non_ascii_name_variable.py
new file mode 100644
index 000000000..2994dfa47
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_variable.py
@@ -0,0 +1,9 @@
+"""
+Simply variable test
+"""
+# pylint: disable=invalid-name
+
+# Test invalid variable name
+łol = "Foobar" # [non-ascii-name]
+# Usage should not raise a second error
+łol += "-" # [non-ascii-name]
diff --git a/tests/functional/n/non_ascii_name/non_ascii_name_variable.txt b/tests/functional/n/non_ascii_name/non_ascii_name_variable.txt
new file mode 100644
index 000000000..5c142ee7e
--- /dev/null
+++ b/tests/functional/n/non_ascii_name/non_ascii_name_variable.txt
@@ -0,0 +1,2 @@
+non-ascii-name:7:0:7:4::"Variable name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH
+non-ascii-name:9:0:9:4::"Variable name ""łol"" contains a non-ASCII character, consider renaming it.":HIGH
diff --git a/tests/functional/n/non_ascii_name_class/__init__.py b/tests/functional/n/non_ascii_name_class/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/functional/n/non_ascii_name_class/__init__.py
diff --git a/tests/functional/n/non_ascii_name_class/non_ascii_name_class.py b/tests/functional/n/non_ascii_name_class/non_ascii_name_class.py
new file mode 100644
index 000000000..edcdae643
--- /dev/null
+++ b/tests/functional/n/non_ascii_name_class/non_ascii_name_class.py
@@ -0,0 +1,23 @@
+""" Non ASCII char in class name
+
+Note that the end line parameter here seems to be off.
+We would expect it to be the same as the start, as we only refer
+to the class Name and not the complete class definition.
+But this is not possible atm with pylint.
+"""
+# pylint: disable=too-few-public-methods
+
+
+class НoldIt: # [non-ascii-name]
+ """nice classs"""
+
+ def public(self):
+ """do something"""
+ print(self)
+
+
+def main():
+ """Main function"""
+ # Usage should not raise a second error
+ foobar = НoldIt()
+ print(foobar)
diff --git a/tests/functional/n/non_ascii_name_class/non_ascii_name_class.txt b/tests/functional/n/non_ascii_name_class/non_ascii_name_class.txt
new file mode 100644
index 000000000..92c4430b3
--- /dev/null
+++ b/tests/functional/n/non_ascii_name_class/non_ascii_name_class.txt
@@ -0,0 +1 @@
+non-ascii-name:11:0:16:19:НoldIt:"Class name ""НoldIt"" contains a non-ASCII character, consider renaming it.":HIGH
diff --git a/tests/functional/n/non_ascii_name_class/non_ascii_name_class_attribute.py b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_attribute.py
new file mode 100644
index 000000000..07d95bba5
--- /dev/null
+++ b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_attribute.py
@@ -0,0 +1,25 @@
+"""Non ASCII name in class variable"""
+
+
+class OkayIsh:
+ """Class docstring"""
+
+ def public(self):
+ """Be public!"""
+ print(self)
+
+ def __init__(self):
+ self.łoopback = "invalid" # [non-ascii-name]
+
+ def foobar(self):
+ """do something"""
+ # Usage should not raise a second error
+ return self.łoopback
+
+def main():
+ """main function"""
+ # Usage should not raise a second error
+ barrrr = OkayIsh()
+ barrrr.foobar()
+ test = barrrr.łoopback
+ print(test)
diff --git a/tests/functional/n/non_ascii_name_class/non_ascii_name_class_attribute.txt b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_attribute.txt
new file mode 100644
index 000000000..7e3f5327c
--- /dev/null
+++ b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_attribute.txt
@@ -0,0 +1 @@
+non-ascii-name:12:8:12:22:OkayIsh.__init__:"Attribute name ""łoopback"" contains a non-ASCII character, consider renaming it.":HIGH
diff --git a/tests/functional/n/non_ascii_name_class/non_ascii_name_class_constant.py b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_constant.py
new file mode 100644
index 000000000..c3f96cadd
--- /dev/null
+++ b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_constant.py
@@ -0,0 +1,23 @@
+"""non ASCII name in global class variable/class constant"""
+
+
+class OkayIsh:
+ """Class docstring"""
+ ŁOOPBACK = "invalid" # [non-ascii-name]
+
+
+ def more_public(self):
+ """yet another public method"""
+ print(self)
+
+ def public(self):
+ """something public"""
+ print(self)
+
+
+
+def main():
+ """Main func"""
+ # Usage should not raise a second error
+ foobar = OkayIsh.ŁOOPBACK
+ print(foobar)
diff --git a/tests/functional/n/non_ascii_name_class/non_ascii_name_class_constant.txt b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_constant.txt
new file mode 100644
index 000000000..26cf27ef7
--- /dev/null
+++ b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_constant.txt
@@ -0,0 +1 @@
+non-ascii-name:6:4:6:13:OkayIsh:"Attribute name ""ŁOOPBACK"" contains a non-ASCII character, consider renaming it.":HIGH
diff --git a/tests/functional/n/non_ascii_name_class/non_ascii_name_class_method.py b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_method.py
new file mode 100644
index 000000000..ec05b4cf9
--- /dev/null
+++ b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_method.py
@@ -0,0 +1,18 @@
+"""Non ASCII char in classmethod"""
+
+
+class OkayClass:
+ """We need a class docstring?"""
+
+ def public(self):
+ """Say something"""
+ print(self)
+
+ @classmethod
+ def umlaut_ä(cls): # [non-ascii-name]
+ """do something"""
+ return "ä"
+
+
+# Usage should not raise a second error
+OkayClass.umlaut_ä()
diff --git a/tests/functional/n/non_ascii_name_class/non_ascii_name_class_method.txt b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_method.txt
new file mode 100644
index 000000000..8d7606e67
--- /dev/null
+++ b/tests/functional/n/non_ascii_name_class/non_ascii_name_class_method.txt
@@ -0,0 +1 @@
+non-ascii-name:12:4:14:19:OkayClass.umlaut_ä:"Function name ""umlaut_ä"" contains a non-ASCII character, consider renaming it.":HIGH