diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index f1d54dbd5c..c0865d0c6d 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -521,3 +521,5 @@ contributors: * Yilei Yang: contributor * Marcin Kurczewski (rr-): contributor + +* Daniel van Noord (DanielNoord): contributor diff --git a/ChangeLog b/ChangeLog index 778e863939..52ce722f3d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,10 @@ Release date: TBA .. Put new features here and also in 'doc/whatsnew/2.10.rst' +* Added ``unspecified-encoding``: Emitted when open() is called without specifying an encoding + + Closes #3826 + What's New in Pylint 2.9.6? =========================== diff --git a/doc/whatsnew/2.10.rst b/doc/whatsnew/2.10.rst index 3e17dc7817..a2c4e1fbfe 100644 --- a/doc/whatsnew/2.10.rst +++ b/doc/whatsnew/2.10.rst @@ -12,6 +12,9 @@ Summary -- Release highlights New checkers ============ +* Added ``unspecified-encoding``: Emitted when open() is called without specifying an encoding + + Closes #3826 Other Changes diff --git a/pylint/checkers/similar.py b/pylint/checkers/similar.py index 8ddbf68573..8d3155c9cd 100644 --- a/pylint/checkers/similar.py +++ b/pylint/checkers/similar.py @@ -546,7 +546,7 @@ def Run(argv=None): min_lines, ignore_comments, ignore_docstrings, ignore_imports, ignore_signatures ) for filename in args: - with open(filename) as stream: + with open(filename, encoding="utf-8") as stream: sim.append_stream(filename, stream) sim.run() sys.exit(0) diff --git a/pylint/checkers/spelling.py b/pylint/checkers/spelling.py index e2b6df1898..1b0a7f653b 100644 --- a/pylint/checkers/spelling.py +++ b/pylint/checkers/spelling.py @@ -318,7 +318,7 @@ def open(self): dict_name, self.config.spelling_private_dict_file ) self.private_dict_file = open( # pylint: disable=consider-using-with - self.config.spelling_private_dict_file, "a" + self.config.spelling_private_dict_file, "a", encoding="utf-8" ) else: self.spelling_dict = enchant.Dict(dict_name) diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py index c0e789ad87..1fba67afd4 100644 --- a/pylint/checkers/stdlib.py +++ b/pylint/checkers/stdlib.py @@ -30,6 +30,7 @@ # Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com> # Copyright (c) 2021 Matus Valo # Copyright (c) 2021 victor <16359131+jiajunsu@users.noreply.github.com> +# Copyright (c) 2021 Daniel van Noord <13665637+DanielNoord@users.noreply.github.com> # 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 @@ -44,12 +45,13 @@ from pylint.checkers import BaseChecker, DeprecatedMixin, utils from pylint.interfaces import IAstroidChecker -OPEN_FILES = {"open", "file"} +OPEN_FILES_MODE = ("open", "file") +OPEN_FILES_ENCODING = ("open",) UNITTEST_CASE = "unittest.case" THREADING_THREAD = "threading.Thread" COPY_COPY = "copy.copy" OS_ENVIRON = "os._Environ" -ENV_GETTERS = {"os.getenv"} +ENV_GETTERS = ("os.getenv",) SUBPROCESS_POPEN = "subprocess.Popen" SUBPROCESS_RUN = "subprocess.run" OPEN_MODULE = "_io" @@ -425,6 +427,13 @@ class StdlibChecker(DeprecatedMixin, BaseChecker): "deprecated-decorator", "The decorator is marked as deprecated and will be removed in the future.", ), + "W1514": ( + "Using open without explicitly specifying an encoding", + "unspecified-encoding", + "It is better to specify an encoding when opening documents. " + "Using the system default implicitly can create problems on other operating systems. " + "See https://www.python.org/dev/peps/pep-0597/", + ), } def __init__(self, linter=None): @@ -485,6 +494,7 @@ def _check_shallow_copy_environ(self, node): "subprocess-popen-preexec-fn", "subprocess-run-check", "deprecated-class", + "unspecified-encoding", ) def visit_call(self, node): """Visit a Call node.""" @@ -494,8 +504,18 @@ def visit_call(self, node): if inferred is astroid.Uninferable: continue if inferred.root().name == OPEN_MODULE: - if getattr(node.func, "name", None) in OPEN_FILES: + if ( + isinstance(node.func, astroid.Name) + and node.func.name in OPEN_FILES_MODE + ): self._check_open_mode(node) + if ( + isinstance(node.func, astroid.Name) + and node.func.name in OPEN_FILES_ENCODING + or isinstance(node.func, astroid.Attribute) + and node.func.attrname in OPEN_FILES_ENCODING + ): + self._check_open_encoded(node) elif inferred.root().name == UNITTEST_CASE: self._check_redundant_assert(node, inferred) elif isinstance(inferred, astroid.ClassDef): @@ -573,6 +593,34 @@ def _check_open_mode(self, node): ): self.add_message("bad-open-mode", node=node, args=mode_arg.value) + def _check_open_encoded(self, node: astroid.Call) -> None: + """Check that the encoded argument of an open call is valid.""" + mode_arg = None + try: + mode_arg = utils.get_argument_from_call(node, position=1, keyword="mode") + except utils.NoSuchArgumentError: + pass + + if mode_arg: + mode_arg = utils.safe_infer(mode_arg) + if not mode_arg or "b" not in mode_arg.value: + encoding_arg = None + try: + encoding_arg = utils.get_argument_from_call( + node, position=None, keyword="encoding" + ) + except utils.NoSuchArgumentError: + self.add_message("unspecified-encoding", node=node) + + if encoding_arg: + encoding_arg = utils.safe_infer(encoding_arg) + + if ( + isinstance(encoding_arg, astroid.Const) + and encoding_arg.value is None + ): + self.add_message("unspecified-encoding", node=node) + def _check_env_function(self, node, infer): env_name_kwarg = "key" env_value_kwarg = "default" diff --git a/pylint/config/find_default_config_files.py b/pylint/config/find_default_config_files.py index d4faa259e5..2a8937e527 100644 --- a/pylint/config/find_default_config_files.py +++ b/pylint/config/find_default_config_files.py @@ -9,7 +9,7 @@ def _toml_has_config(path): - with open(path) as toml_handle: + with open(path, encoding="utf-8") as toml_handle: try: content = toml.load(toml_handle) except TomlDecodeError as error: diff --git a/pylint/config/option_manager_mixin.py b/pylint/config/option_manager_mixin.py index 86785f36f5..5aa2323f0c 100644 --- a/pylint/config/option_manager_mixin.py +++ b/pylint/config/option_manager_mixin.py @@ -267,7 +267,7 @@ def read_config_file(self, config_file=None, verbose=None): parser = self.cfgfile_parser if config_file.endswith(".toml"): - with open(config_file) as fp: + with open(config_file, encoding="utf-8") as fp: content = toml.load(fp) try: diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index c2d5362117..25ae29c02a 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -571,7 +571,9 @@ def _load_reporters(self) -> None: (reporter_output,) = reporter_output # pylint: disable=consider-using-with - output_file = stack.enter_context(open(reporter_output, "w")) + output_file = stack.enter_context( + open(reporter_output, "w", encoding="utf-8") + ) reporter.set_output(output_file) output_files.append(output_file) @@ -617,8 +619,9 @@ def set_option(self, optname, value, action=None, optdict=None): except KeyError: meth = self._bw_options_methods[optname] warnings.warn( - "%s is deprecated, replace it by %s" - % (optname, optname.split("-")[0]), + "{} is deprecated, replace it by {}".format( + optname, optname.split("-")[0] + ), DeprecationWarning, ) value = utils._check_csv(value) diff --git a/pylint/lint/run.py b/pylint/lint/run.py index 08806e6c83..66c9b36225 100644 --- a/pylint/lint/run.py +++ b/pylint/lint/run.py @@ -373,7 +373,7 @@ def __init__( if self._output: try: - with open(self._output, "w") as output: + with open(self._output, "w", encoding="utf-8") as output: linter.reporter.set_output(output) linter.check(args) score_value = linter.generate_reports() diff --git a/pylint/pyreverse/utils.py b/pylint/pyreverse/utils.py index fea9bda792..06f8d3b7e2 100644 --- a/pylint/pyreverse/utils.py +++ b/pylint/pyreverse/utils.py @@ -35,7 +35,7 @@ def get_default_options(): if home: rcfile = os.path.join(home, RCFILE) try: - with open(rcfile) as file_handle: + with open(rcfile, encoding="utf-8") as file_handle: options = file_handle.read().split() except OSError: pass # ignore if no config file found diff --git a/pylint/pyreverse/writer.py b/pylint/pyreverse/writer.py index db54c3f0bd..f45783c498 100644 --- a/pylint/pyreverse/writer.py +++ b/pylint/pyreverse/writer.py @@ -187,7 +187,9 @@ def __init__(self, config): def set_printer(self, file_name, basename): """initialize VCGWriter for a UML graph""" - self.graph_file = open(file_name, "w+") # pylint: disable=consider-using-with + self.graph_file = open( # pylint: disable=consider-using-with + file_name, "w+", encoding="utf-8" + ) self.printer = VCGPrinter(self.graph_file) self.printer.open_graph( title=basename, diff --git a/pylint/testutils/lint_module_test.py b/pylint/testutils/lint_module_test.py index c88463821a..8f24daf864 100644 --- a/pylint/testutils/lint_module_test.py +++ b/pylint/testutils/lint_module_test.py @@ -141,14 +141,14 @@ def multiset_difference( # pylint: disable=consider-using-with def _open_expected_file(self): try: - return open(self._test_file.expected_output) + return open(self._test_file.expected_output, encoding="utf-8") except FileNotFoundError: return StringIO("") # pylint: disable=consider-using-with def _open_source_file(self): if self._test_file.base == "invalid_encoded_data": - return open(self._test_file.source) + return open(self._test_file.source, encoding="utf-8") if "latin1" in self._test_file.base: return open(self._test_file.source, encoding="latin1") return open(self._test_file.source, encoding="utf8") diff --git a/script/bump_changelog.py b/script/bump_changelog.py index 278b627dad..41c8c3c8a9 100644 --- a/script/bump_changelog.py +++ b/script/bump_changelog.py @@ -33,10 +33,10 @@ def main() -> None: logging.debug(f"Launching bump_changelog with args: {args}") if "dev" in args.version: return - with open(DEFAULT_CHANGELOG_PATH) as f: + with open(DEFAULT_CHANGELOG_PATH, encoding="utf-8") as f: content = f.read() content = transform_content(content, args.version) - with open(DEFAULT_CHANGELOG_PATH, "w") as f: + with open(DEFAULT_CHANGELOG_PATH, "w", encoding="utf-8") as f: f.write(content) diff --git a/script/fix_documentation.py b/script/fix_documentation.py index 2422253812..b03f70ead4 100644 --- a/script/fix_documentation.py +++ b/script/fix_documentation.py @@ -82,7 +82,7 @@ def main(argv: Union[List[str], None] = None) -> int: return_value: int = 0 for file_name in args.filenames: - with open(file_name) as fp: + with open(file_name, encoding="utf-8") as fp: orignal_content = fp.read() content = orignal_content # Modify files @@ -91,7 +91,7 @@ def main(argv: Union[List[str], None] = None) -> int: content = changelog_insert_empty_lines(content, args.subtitle_prefix) # If modified, write changes and eventually return 1 if orignal_content != content: - with open(file_name, "w") as fp: + with open(file_name, "w", encoding="utf-8") as fp: fp.write(content) return_value |= 1 return return_value diff --git a/tests/checkers/unittest_similar.py b/tests/checkers/unittest_similar.py index 8edc87bca8..ca64bdba30 100644 --- a/tests/checkers/unittest_similar.py +++ b/tests/checkers/unittest_similar.py @@ -449,7 +449,7 @@ def test_get_map_data(): # Manually perform a 'map' type function for source_fname in source_streams: sim = similar.SimilarChecker(linter) - with open(source_fname) as stream: + with open(source_fname, encoding="utf-8") as stream: sim.append_stream(source_fname, stream) # The map bit, can you tell? ;) data.extend(sim.get_map_data()) diff --git a/tests/functional/b/bad_open_mode_py3.py b/tests/functional/b/bad_open_mode_py3.py index 27aa2e6a21..b68238013a 100644 --- a/tests/functional/b/bad_open_mode_py3.py +++ b/tests/functional/b/bad_open_mode_py3.py @@ -3,22 +3,22 @@ NAME = "foo.bar" open(NAME, "wb") -open(NAME, "w") +open(NAME, "w", encoding="utf-8") open(NAME, "rb") -open(NAME, "x") +open(NAME, "x", encoding="utf-8") open(NAME, "br") -open(NAME, "+r") +open(NAME, "+r", encoding="utf-8") open(NAME, "xb") -open(NAME, "rwx") # [bad-open-mode] -open(NAME, "rr") # [bad-open-mode] -open(NAME, "+") # [bad-open-mode] -open(NAME, "xw") # [bad-open-mode] +open(NAME, "rwx", encoding="utf-8") # [bad-open-mode] +open(NAME, "rr", encoding="utf-8") # [bad-open-mode] +open(NAME, "+", encoding="utf-8") # [bad-open-mode] +open(NAME, "xw", encoding="utf-8") # [bad-open-mode] open(NAME, "ab+") open(NAME, "a+b") open(NAME, "+ab") open(NAME, "+rUb") open(NAME, "x+b") -open(NAME, "Ua") # [bad-open-mode] -open(NAME, "Ur++") # [bad-open-mode] -open(NAME, "Ut") +open(NAME, "Ua", encoding="utf-8") # [bad-open-mode] +open(NAME, "Ur++", encoding="utf-8") # [bad-open-mode] +open(NAME, "Ut", encoding="utf-8") open(NAME, "Ubr") diff --git a/tests/functional/c/consider/consider_using_with.py b/tests/functional/c/consider/consider_using_with.py index fc2804455b..d05866aaab 100644 --- a/tests/functional/c/consider/consider_using_with.py +++ b/tests/functional/c/consider/consider_using_with.py @@ -151,7 +151,7 @@ def test_suppress_in_exit_stack(): """Regression test for issue #4654 (false positive)""" with contextlib.ExitStack() as stack: _ = stack.enter_context( - open("/sys/firmware/devicetree/base/hwid,location", "r") + open("/sys/firmware/devicetree/base/hwid,location", "r", encoding="utf-8") ) # must not trigger diff --git a/tests/functional/c/consider/consider_using_with_open.py b/tests/functional/c/consider/consider_using_with_open.py index e93b619b78..30be92def5 100644 --- a/tests/functional/c/consider/consider_using_with_open.py +++ b/tests/functional/c/consider/consider_using_with_open.py @@ -1,5 +1,5 @@ # pylint: disable=missing-function-docstring, missing-module-docstring, invalid-name, import-outside-toplevel -# pylint: disable=missing-class-docstring, too-few-public-methods, unused-variable, multiple-statements +# pylint: disable=missing-class-docstring, too-few-public-methods, unused-variable, multiple-statements, line-too-long """ The functional test for the standard ``open()`` function has to be moved in a separate file, because PyPy has to be excluded for the tests as the ``open()`` function is uninferable in PyPy. @@ -8,26 +8,26 @@ """ from contextlib import contextmanager - -myfile = open("test.txt") # [consider-using-with] +myfile = open("test.txt", encoding="utf-8") # [consider-using-with] def test_open(): - fh = open("test.txt") # [consider-using-with] + fh = open("test.txt", encoding="utf-8") # [consider-using-with] fh.close() - with open("test.txt") as fh: # must not trigger + with open("test.txt", encoding="utf-8") as fh: # must not trigger fh.read() def test_open_in_enter(): """Message must not trigger if the resource is allocated in a context manager.""" + class MyContextManager: def __init__(self): self.file_handle = None def __enter__(self): - self.file_handle = open("foo.txt", "w") # must not trigger + self.file_handle = open("foo.txt", "w", encoding="utf-8") # must not trigger def __exit__(self, exc_type, exc_value, traceback): self.file_handle.close() @@ -36,31 +36,31 @@ def __exit__(self, exc_type, exc_value, traceback): @contextmanager def test_open_in_with_contextlib(): """Message must not trigger if the resource is allocated in a context manager.""" - file_handle = open("foo.txt", "w") # must not trigger + file_handle = open("foo.txt", "w", encoding="utf-8") # must not trigger yield file_handle file_handle.close() def test_open_outside_assignment(): - open("foo").read() # [consider-using-with] - content = open("foo").read() # [consider-using-with] + open("foo", encoding="utf-8").read() # [consider-using-with] + content = open("foo", encoding="utf-8").read() # [consider-using-with] def test_open_inside_with_block(): - with open("foo") as fh: - open("bar") # [consider-using-with] + with open("foo", encoding="utf-8") as fh: + open("bar", encoding="utf-8") # [consider-using-with] def test_ternary_if_in_with_block(file1, file2, which): """Regression test for issue #4676 (false positive)""" - with (open(file1) if which else open(file2)) as input_file: # must not trigger + with (open(file1, encoding="utf-8") if which else open(file2, encoding="utf-8")) as input_file: # must not trigger return input_file.read() def test_single_line_with(file1): - with open(file1): return file1.read() # must not trigger + with open(file1, encoding="utf-8"): return file1.read() # must not trigger def test_multiline_with_items(file1, file2, which): - with (open(file1) if which - else open(file2)) as input_file: return input_file.read() + with (open(file1, encoding="utf-8") if which + else open(file2, encoding="utf-8")) as input_file: return input_file.read() diff --git a/tests/functional/c/consider/consider_using_with_open.txt b/tests/functional/c/consider/consider_using_with_open.txt index 63cd08b7c9..ea3503123d 100644 --- a/tests/functional/c/consider/consider_using_with_open.txt +++ b/tests/functional/c/consider/consider_using_with_open.txt @@ -1,5 +1,5 @@ -consider-using-with:12:9::Consider using 'with' for resource-allocating operations -consider-using-with:16:9:test_open:Consider using 'with' for resource-allocating operations +consider-using-with:11:9::Consider using 'with' for resource-allocating operations +consider-using-with:15:9:test_open:Consider using 'with' for resource-allocating operations consider-using-with:45:4:test_open_outside_assignment:Consider using 'with' for resource-allocating operations consider-using-with:46:14:test_open_outside_assignment:Consider using 'with' for resource-allocating operations consider-using-with:51:8:test_open_inside_with_block:Consider using 'with' for resource-allocating operations diff --git a/tests/functional/d/defined_and_used_on_same_line.py b/tests/functional/d/defined_and_used_on_same_line.py index a9b9eb7d58..d5bdb4db0a 100644 --- a/tests/functional/d/defined_and_used_on_same_line.py +++ b/tests/functional/d/defined_and_used_on_same_line.py @@ -1,5 +1,5 @@ """Check for definitions and usage happening on the same line.""" -#pylint: disable=missing-docstring,multiple-statements,no-absolute-import,parameter-unpacking,wrong-import-position,unnecessary-comprehension +#pylint: disable=missing-docstring,multiple-statements,no-absolute-import,parameter-unpacking,wrong-import-position,unnecessary-comprehension,unspecified-encoding from __future__ import print_function print([index for index in range(10)]) diff --git a/tests/functional/d/disabled_msgid_in_pylintrc.py b/tests/functional/d/disabled_msgid_in_pylintrc.py index 09ddf11474..91cb155e13 100644 --- a/tests/functional/d/disabled_msgid_in_pylintrc.py +++ b/tests/functional/d/disabled_msgid_in_pylintrc.py @@ -1,6 +1,6 @@ """https://github.com/PyCQA/pylint/issues/4265""" try: - f = open('test') + f = open('test', encoding="utf-8") except Exception: pass diff --git a/tests/functional/n/non/non_iterator_returned.py b/tests/functional/n/non/non_iterator_returned.py index 94c3601f59..4d0dd2a393 100644 --- a/tests/functional/n/non/non_iterator_returned.py +++ b/tests/functional/n/non/non_iterator_returned.py @@ -67,7 +67,7 @@ def __init__(self, path): def __iter__(self): if self.file is not None: self.file.close() - self.file = open(self.path) + self.file = open(self.path, encoding="utf-8") # self file has two infered values: None and # we don't want to emit error in this case return self.file diff --git a/tests/functional/r/redefined_argument_from_local.py b/tests/functional/r/redefined_argument_from_local.py index 8202527815..7ccff1d184 100644 --- a/tests/functional/r/redefined_argument_from_local.py +++ b/tests/functional/r/redefined_argument_from_local.py @@ -1,31 +1,33 @@ # pylint: disable=missing-docstring, unused-variable, unused-argument # pylint: disable=redefined-outer-name, invalid-name + def test_redefined_in_with(name): - with open('something') as name: # [redefined-argument-from-local] + with open("something", encoding="utf-8") as name: # [redefined-argument-from-local] pass - with open('something') as (second, name): # [redefined-argument-from-local] + with open("something", encoding="utf-8") as (second, name): # [redefined-argument-from-local] pass - with open('something') as (second, (name, third)): # [redefined-argument-from-local] + with open("something", encoding="utf-8") as ( + second, + (name, third), # [redefined-argument-from-local] + ): pass other = None - with open('something') as other: + with open("something", encoding="utf-8") as other: pass - def test_not_redefined_in_with(name): - with open('something') as test_redefined_in_with: + with open("something", encoding="utf-8") as test_redefined_in_with: pass - def test_redefined_in_for(name): - for name in []: # [redefined-argument-from-local] + for name in []: # [redefined-argument-from-local] pass - for (name, is_) in []: # [redefined-argument-from-local] + for (name, is_) in []: # [redefined-argument-from-local] pass - for (is_, (name, _)) in []: # [redefined-argument-from-local] + for (is_, (name, _)) in []: # [redefined-argument-from-local] pass for _ in []: pass @@ -45,7 +47,7 @@ def test_not_redefined_in_for(name): def test_redefined_in_except_handler(name): try: 1 / 0 - except ZeroDivisionError as name: # [redefined-argument-from-local] + except ZeroDivisionError as name: # [redefined-argument-from-local] pass @@ -58,7 +60,7 @@ def test_not_redefined_in_except_handler(name): def test_not_redefined(name): if not name: - name = '' + name = "" def apply_filter(objects, filt=lambda obj: True): diff --git a/tests/functional/r/redefined_argument_from_local.txt b/tests/functional/r/redefined_argument_from_local.txt index 28ec50da11..b811971c6a 100644 --- a/tests/functional/r/redefined_argument_from_local.txt +++ b/tests/functional/r/redefined_argument_from_local.txt @@ -1,7 +1,7 @@ -redefined-argument-from-local:5:30:test_redefined_in_with:Redefining argument with the local name 'name' -redefined-argument-from-local:7:39:test_redefined_in_with:Redefining argument with the local name 'name' -redefined-argument-from-local:9:40:test_redefined_in_with:Redefining argument with the local name 'name' -redefined-argument-from-local:24:8:test_redefined_in_for:Redefining argument with the local name 'name' -redefined-argument-from-local:26:9:test_redefined_in_for:Redefining argument with the local name 'name' -redefined-argument-from-local:28:15:test_redefined_in_for:Redefining argument with the local name 'name' -redefined-argument-from-local:48:4:test_redefined_in_except_handler:Redefining argument with the local name 'name' +redefined-argument-from-local:6:48:test_redefined_in_with:Redefining argument with the local name 'name' +redefined-argument-from-local:8:57:test_redefined_in_with:Redefining argument with the local name 'name' +redefined-argument-from-local:12:9:test_redefined_in_with:Redefining argument with the local name 'name' +redefined-argument-from-local:26:8:test_redefined_in_for:Redefining argument with the local name 'name' +redefined-argument-from-local:28:9:test_redefined_in_for:Redefining argument with the local name 'name' +redefined-argument-from-local:30:15:test_redefined_in_for:Redefining argument with the local name 'name' +redefined-argument-from-local:50:4:test_redefined_in_except_handler:Redefining argument with the local name 'name' diff --git a/tests/functional/r/regression/regression_4612_crash_pytest_fixture.py b/tests/functional/r/regression/regression_4612_crash_pytest_fixture.py index f0aa0f5d1d..f7680f1fb7 100644 --- a/tests/functional/r/regression/regression_4612_crash_pytest_fixture.py +++ b/tests/functional/r/regression/regression_4612_crash_pytest_fixture.py @@ -5,5 +5,5 @@ @pytest.fixture def qm_file(): - qm_file = open("src/test/resources/example_qm_file.csv").read() + qm_file = open("src/test/resources/example_qm_file.csv", encoding="utf-8").read() return qm_file diff --git a/tests/functional/u/unspecified_encoding_py38.py b/tests/functional/u/unspecified_encoding_py38.py new file mode 100644 index 0000000000..20d2d7be1c --- /dev/null +++ b/tests/functional/u/unspecified_encoding_py38.py @@ -0,0 +1,55 @@ +"""Warnings for using open() without specifying an encoding""" +# pylint: disable=consider-using-with +import io +import locale + +FILENAME = "foo.bar" +open(FILENAME, "w", encoding="utf-8") +open(FILENAME, "wb") +open(FILENAME, "w+b") +open(FILENAME) # [unspecified-encoding] +open(FILENAME, "wt") # [unspecified-encoding] +open(FILENAME, "w+") # [unspecified-encoding] +open(FILENAME, "w", encoding=None) # [unspecified-encoding] +open(FILENAME, "r") # [unspecified-encoding] + +with open(FILENAME, encoding="utf8", errors="surrogateescape") as f: + pass + +LOCALE_ENCODING = locale.getlocale()[1] +with open(FILENAME, encoding=LOCALE_ENCODING) as f: + pass + +with open(FILENAME) as f: # [unspecified-encoding] + pass + +with open(FILENAME, encoding=None) as f: # [unspecified-encoding] + pass + +LOCALE_ENCODING = None +with open(FILENAME, encoding=LOCALE_ENCODING) as f: # [unspecified-encoding] + pass + +io.open(FILENAME, "w+b") +io.open_code(FILENAME) +io.open(FILENAME) # [unspecified-encoding] +io.open(FILENAME, "wt") # [unspecified-encoding] +io.open(FILENAME, "w+") # [unspecified-encoding] +io.open(FILENAME, "w", encoding=None) # [unspecified-encoding] + +with io.open(FILENAME, encoding="utf8", errors="surrogateescape") as f: + pass + +LOCALE_ENCODING = locale.getlocale()[1] +with io.open(FILENAME, encoding=LOCALE_ENCODING) as f: + pass + +with io.open(FILENAME) as f: # [unspecified-encoding] + pass + +with io.open(FILENAME, encoding=None) as f: # [unspecified-encoding] + pass + +LOCALE_ENCODING = None +with io.open(FILENAME, encoding=LOCALE_ENCODING) as f: # [unspecified-encoding] + pass diff --git a/tests/functional/u/unspecified_encoding_py38.rc b/tests/functional/u/unspecified_encoding_py38.rc new file mode 100644 index 0000000000..85fc502b37 --- /dev/null +++ b/tests/functional/u/unspecified_encoding_py38.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.8 diff --git a/tests/functional/u/unspecified_encoding_py38.txt b/tests/functional/u/unspecified_encoding_py38.txt new file mode 100644 index 0000000000..cde7f22383 --- /dev/null +++ b/tests/functional/u/unspecified_encoding_py38.txt @@ -0,0 +1,15 @@ +unspecified-encoding:10:0::"Using open without explicitly specifying an encoding" +unspecified-encoding:11:0::"Using open without explicitly specifying an encoding" +unspecified-encoding:12:0::"Using open without explicitly specifying an encoding" +unspecified-encoding:13:0::"Using open without explicitly specifying an encoding" +unspecified-encoding:14:0::"Using open without explicitly specifying an encoding" +unspecified-encoding:23:5::"Using open without explicitly specifying an encoding" +unspecified-encoding:26:5::"Using open without explicitly specifying an encoding" +unspecified-encoding:30:5::"Using open without explicitly specifying an encoding" +unspecified-encoding:35:0::"Using open without explicitly specifying an encoding" +unspecified-encoding:36:0::"Using open without explicitly specifying an encoding" +unspecified-encoding:37:0::"Using open without explicitly specifying an encoding" +unspecified-encoding:38:0::"Using open without explicitly specifying an encoding" +unspecified-encoding:47:5::"Using open without explicitly specifying an encoding" +unspecified-encoding:50:5::"Using open without explicitly specifying an encoding" +unspecified-encoding:54:5::"Using open without explicitly specifying an encoding" diff --git a/tests/functional/w/with_used_before_assign.py b/tests/functional/w/with_used_before_assign.py index 64a475af10..ebe7d3093c 100644 --- a/tests/functional/w/with_used_before_assign.py +++ b/tests/functional/w/with_used_before_assign.py @@ -1,11 +1,12 @@ -''' +""" Regression test for https://bitbucket.org/logilab/pylint/issue/128/attributeerror-when-parsing -''' +""" from __future__ import with_statement + def do_nothing(): """ empty """ - with open("") as ctx.obj: # [undefined-variable] + with open("", encoding="utf-8") as ctx.obj: # [undefined-variable] context.do() # [used-before-assignment] context = None diff --git a/tests/functional/w/with_used_before_assign.txt b/tests/functional/w/with_used_before_assign.txt index 4a98cec607..8e1fe1be82 100644 --- a/tests/functional/w/with_used_before_assign.txt +++ b/tests/functional/w/with_used_before_assign.txt @@ -1,2 +1,2 @@ -undefined-variable:9:21:do_nothing:Undefined variable 'ctx' -used-before-assignment:10:8:do_nothing:Using variable 'context' before assignment +undefined-variable:10:39:do_nothing:Undefined variable 'ctx' +used-before-assignment:11:8:do_nothing:Using variable 'context' before assignment diff --git a/tests/lint/unittest_lint.py b/tests/lint/unittest_lint.py index 984e9759dd..004c29281c 100644 --- a/tests/lint/unittest_lint.py +++ b/tests/lint/unittest_lint.py @@ -156,7 +156,7 @@ def create_files(paths, chroot="."): if not isdir(dirpath): os.makedirs(dirpath) for filepath in files: - with open(filepath, "w"): + with open(filepath, "w", encoding="utf-8"): pass diff --git a/tests/test_epylint.py b/tests/test_epylint.py index a53c2b0456..5256271667 100644 --- a/tests/test_epylint.py +++ b/tests/test_epylint.py @@ -13,7 +13,7 @@ def run(self): self.hassan() """ path = tmp_path / "my_app.py" - with open(path, "w") as f: + with open(path, "w", encoding="utf-8") as f: f.write(content) return path diff --git a/tests/test_func.py b/tests/test_func.py index e15d3b0f8d..8742b42f96 100644 --- a/tests/test_func.py +++ b/tests/test_func.py @@ -88,7 +88,7 @@ def _has_output(self): def _get_expected(self): if self._has_output() and self.output: - with open(self.output) as fobj: + with open(self.output, encoding="utf-8") as fobj: return fobj.read().strip() + "\n" else: return "" @@ -103,7 +103,7 @@ def _check_result(self, got): except OSError: expected = "" if got != expected: - with open(self.output, "w") as f: + with open(self.output, "w", encoding="utf-8") as f: f.write(got) diff --git a/tests/test_functional.py b/tests/test_functional.py index f3959b8e32..4ab7617388 100644 --- a/tests/test_functional.py +++ b/tests/test_functional.py @@ -59,7 +59,7 @@ def _check_output_text(self, _, expected_output, actual_output): if os.path.exists(self._test_file.expected_output): os.remove(self._test_file.expected_output) return - with open(self._test_file.expected_output, "w") as f: + with open(self._test_file.expected_output, "w", encoding="utf-8") as f: writer = csv.writer(f, dialect="test") for line in actual_output: writer.writerow(line.to_csv()) diff --git a/tests/test_import_graph.py b/tests/test_import_graph.py index 92ca4a5104..07a0937591 100644 --- a/tests/test_import_graph.py +++ b/tests/test_import_graph.py @@ -46,7 +46,7 @@ def dest(request): def test_dependencies_graph(dest): """DOC files are correctly generated, and the graphname is the basename""" imports._dependencies_graph(dest, {"labas": ["hoho", "yep"], "hoho": ["yep"]}) - with open(dest) as stream: + with open(dest, encoding="utf-8") as stream: assert ( stream.read().strip() == """ diff --git a/tests/test_self.py b/tests/test_self.py index 937ee03943..de63ebd6fb 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -553,7 +553,7 @@ def test_parseable_file_path(self): # create module under directories which have the same name as reporter.path_strip_prefix # e.g. /src/some/path/src/test_target.py when reporter.path_strip_prefix = /src/ os.makedirs(fake_path) - with open(module, "w") as test_target: + with open(module, "w", encoding="utf-8") as test_target: test_target.write("a,b = object()") self._test_output( diff --git a/tests/unittest_reporting.py b/tests/unittest_reporting.py index 6b238b4844..3946ec61d3 100644 --- a/tests/unittest_reporting.py +++ b/tests/unittest_reporting.py @@ -128,7 +128,7 @@ def test_multi_format_output(tmp_path): linter.reporter.close_output_files() del linter.reporter - with open(json) as f: + with open(json, encoding="utf-8") as f: assert ( f.read() == "[\n" " {\n"