diff options
84 files changed, 525 insertions, 384 deletions
@@ -10,6 +10,10 @@ Release date: TBA .. Put new features here and also in 'doc/whatsnew/2.11.rst' +* Added ``consider-using-f-string``: Emitted when .format() or '%' is being used to format a string. + + Closes #3592 + What's New in Pylint 2.10.3? ============================ @@ -193,7 +197,6 @@ Release date: 2021-08-20 * Allow ``true`` and ``false`` values in ``pylintrc`` for better compatibility with ``toml`` config. - * Class methods' signatures are ignored the same way as functions' with similarities "ignore-signatures" option enabled Closes #4653 diff --git a/doc/exts/pylint_extensions.py b/doc/exts/pylint_extensions.py index 02e9be5bc..4dc6f184f 100755 --- a/doc/exts/pylint_extensions.py +++ b/doc/exts/pylint_extensions.py @@ -37,7 +37,7 @@ def builder_inited(app): if name[0] == "_" or name in DEPRECATED_MODULES: continue if ext == ".py": - modules.append("pylint.extensions.%s" % name) + modules.append(f"pylint.extensions.{name}") elif ext == ".rst": doc_files["pylint.extensions." + name] = os.path.join(ext_path, filename) modules.sort() diff --git a/doc/whatsnew/2.11.rst b/doc/whatsnew/2.11.rst index fc370ecd4..e9c05b909 100644 --- a/doc/whatsnew/2.11.rst +++ b/doc/whatsnew/2.11.rst @@ -12,6 +12,10 @@ Summary -- Release highlights New checkers ============ +* Added ``consider-using-f-string``: Emitted when .format() or '%' is being used to format a string. + + Closes #3592 + Extensions ========== diff --git a/pylint/checkers/__init__.py b/pylint/checkers/__init__.py index 4bc586a1c..ffc940223 100644 --- a/pylint/checkers/__init__.py +++ b/pylint/checkers/__init__.py @@ -65,8 +65,8 @@ def table_lines_from_stats(stats, old_stats, columns): diff_str = diff_string(old, new) else: old, diff_str = "NC", "NC" - new = "%.3f" % new if isinstance(new, float) else str(new) - old = "%.3f" % old if isinstance(old, float) else str(old) + new = f"{new:.3f}" if isinstance(new, float) else str(new) + old = f"{old:.3f}" if isinstance(old, float) else str(old) lines += (m_type.replace("_", " "), new, old, diff_str) return lines diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index 4422ab336..6aba62d79 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -193,7 +193,7 @@ DEFAULT_ARGUMENT_SYMBOLS = dict( ["set()", "{}", "[]"], ), **{ - x: "%s()" % x + x: f"{x}()" for x in ( "collections.deque", "collections.ChainMap", @@ -404,12 +404,12 @@ def report_by_type_stats(sect, stats, old_stats): try: documented = total - stats["undocumented_" + node_type] percent = (documented * 100.0) / total - nice_stats[node_type]["percent_documented"] = "%.2f" % percent + nice_stats[node_type]["percent_documented"] = f"{percent:.2f}" except KeyError: nice_stats[node_type]["percent_documented"] = "NC" try: percent = (stats["badname_" + node_type] * 100.0) / total - nice_stats[node_type]["percent_badname"] = "%.2f" % percent + nice_stats[node_type]["percent_badname"] = f"{percent:.2f}" except KeyError: nice_stats[node_type]["percent_badname"] = "NC" lines = ("type", "number", "old number", "difference", "%documented", "%badname") @@ -1703,8 +1703,7 @@ def _create_naming_options(): "type": "choice", "choices": list(NAMING_STYLES.keys()), "metavar": "<style>", - "help": "Naming style matching correct %s names." - % (human_readable_name,), + "help": f"Naming style matching correct {human_readable_name} names.", }, ) ) @@ -1715,8 +1714,7 @@ def _create_naming_options(): "default": None, "type": "regexp", "metavar": "<regexp>", - "help": "Regular expression matching correct %s names. Overrides %s-naming-style." - % (human_readable_name, name_type), + "help": f"Regular expression matching correct {human_readable_name} names. Overrides {name_type}-naming-style.", }, ) ) @@ -1888,9 +1886,9 @@ class NameChecker(_BasicChecker): regexps[name_type] = custom_regex if custom_regex is not None: - hints[name_type] = "%r pattern" % custom_regex.pattern + hints[name_type] = f"{custom_regex.pattern!r} pattern" else: - hints[name_type] = "%s naming style" % naming_style_name + hints[name_type] = f"{naming_style_name} naming style" return regexps, hints @@ -2023,7 +2021,7 @@ class NameChecker(_BasicChecker): type_label = HUMAN_READABLE_TYPES[node_type] hint = self._name_hints[node_type] if self.config.include_naming_hint: - hint += " (%r pattern)" % self._name_regexps[node_type].pattern + hint += f" ({self._name_regexps[node_type].pattern!r} pattern)" args = ( (type_label.capitalize(), name, hint) if warning == "invalid-name" diff --git a/pylint/checkers/base_checker.py b/pylint/checkers/base_checker.py index 4b220f28d..5dfd7ea78 100644 --- a/pylint/checkers/base_checker.py +++ b/pylint/checkers/base_checker.py @@ -70,35 +70,37 @@ class BaseChecker(OptionsProviderMixIn): def get_full_documentation(self, msgs, options, reports, doc=None, module=None): result = "" - checker_title = "%s checker" % (self.name.replace("_", " ").title()) + checker_title = f"{self.name.replace('_', ' ').title()} checker" if module: # Provide anchor to link against - result += ".. _%s:\n\n" % module - result += "%s\n" % get_rst_title(checker_title, "~") + result += f".. _{module}:\n\n" + result += f"{get_rst_title(checker_title, '~')}\n" if module: - result += "This checker is provided by ``%s``.\n" % module - result += "Verbatim name of the checker is ``%s``.\n\n" % self.name + result += f"This checker is provided by ``{module}``.\n" + result += f"Verbatim name of the checker is ``{self.name}``.\n\n" if doc: # Provide anchor to link against result += get_rst_title(f"{checker_title} Documentation", "^") - result += "%s\n\n" % cleandoc(doc) + result += f"{cleandoc(doc)}\n\n" # options might be an empty generator and not be False when casted to boolean options = list(options) if options: result += get_rst_title(f"{checker_title} Options", "^") - result += "%s\n" % get_rst_section(None, options) + result += f"{get_rst_section(None, options)}\n" if msgs: result += get_rst_title(f"{checker_title} Messages", "^") for msgid, msg in sorted( msgs.items(), key=lambda kv: (_MSG_ORDER.index(kv[0][0]), kv[1]) ): msg = self.create_message_definition_from_tuple(msgid, msg) - result += "%s\n" % msg.format_help(checkerref=False) + result += f"{msg.format_help(checkerref=False)}\n" result += "\n" if reports: result += get_rst_title(f"{checker_title} Reports", "^") for report in reports: - result += ":%s: %s\n" % report[:2] + result += ( + ":%s: %s\n" % report[:2] # pylint: disable=consider-using-f-string + ) result += "\n" result += "\n" return result diff --git a/pylint/checkers/classes.py b/pylint/checkers/classes.py index 92395114b..3b18b7e00 100644 --- a/pylint/checkers/classes.py +++ b/pylint/checkers/classes.py @@ -2039,7 +2039,7 @@ class SpecialMethodsChecker(BaseChecker): "__iter__ returns non-iterator", "non-iterator-returned", "Used when an __iter__ method returns something which is not an " - "iterable (i.e. has no `%s` method)" % NEXT_METHOD, + f"iterable (i.e. has no `{NEXT_METHOD}` method)", { "old_names": [ ("W0234", "old-non-iterator-returned-1"), @@ -2189,6 +2189,7 @@ class SpecialMethodsChecker(BaseChecker): # tuple, although the user should implement the method # to take all of them in consideration. emit = mandatory not in expected_params + # pylint: disable-next=consider-using-f-string expected_params = "between %d or %d" % expected_params else: # If the number of mandatory parameters doesn't diff --git a/pylint/checkers/exceptions.py b/pylint/checkers/exceptions.py index 7c173a5ab..fd6c4d482 100644 --- a/pylint/checkers/exceptions.py +++ b/pylint/checkers/exceptions.py @@ -271,7 +271,7 @@ class ExceptionsChecker(checkers.BaseChecker): "default": OVERGENERAL_EXCEPTIONS, "type": "csv", "metavar": "<comma-separated class names>", - "help": "Exceptions that will emit a warning " + "help": "Exceptions that will emit a warning " # pylint: disable=consider-using-f-string 'when being caught. Defaults to "%s".' % (", ".join(OVERGENERAL_EXCEPTIONS),), }, @@ -488,20 +488,14 @@ class ExceptionsChecker(checkers.BaseChecker): def visit_binop(self, node): if isinstance(node.parent, nodes.ExceptHandler): # except (V | A) - suggestion = "Did you mean '({}, {})' instead?".format( - node.left.as_string(), - node.right.as_string(), - ) + suggestion = f"Did you mean '({node.left.as_string()}, {node.right.as_string()})' instead?" self.add_message("wrong-exception-operation", node=node, args=(suggestion,)) @utils.check_messages("wrong-exception-operation") def visit_compare(self, node): if isinstance(node.parent, nodes.ExceptHandler): # except (V < A) - suggestion = "Did you mean '({}, {})' instead?".format( - node.left.as_string(), - ", ".join(operand.as_string() for _, operand in node.ops), - ) + suggestion = f"Did you mean '({node.left.as_string()}, {', '.join(operand.as_string() for _, operand in node.ops)})' instead?" self.add_message("wrong-exception-operation", node=node, args=(suggestion,)) @utils.check_messages( @@ -561,10 +555,7 @@ class ExceptionsChecker(checkers.BaseChecker): for previous_exc in exceptions_classes: if previous_exc in exc_ancestors: - msg = "{} is an ancestor class of {}".format( - previous_exc.name, - exc.name, - ) + msg = f"{previous_exc.name} is an ancestor class of {exc.name}" self.add_message( "bad-except-order", node=handler.type, args=msg ) diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py index e67456722..d4d813812 100644 --- a/pylint/checkers/imports.py +++ b/pylint/checkers/imports.py @@ -151,16 +151,16 @@ def _repr_tree_defs(data, indent_str=None): lines = [] nodes_items = data.items() for i, (mod, (sub, files)) in enumerate(sorted(nodes_items, key=lambda x: x[0])): - files = "" if not files else "(%s)" % ",".join(sorted(files)) + files = "" if not files else f"({','.join(sorted(files))})" if indent_str is None: lines.append(f"{mod} {files}") sub_indent_str = " " else: lines.append(fr"{indent_str}\-{mod} {files}") if i == len(nodes_items) - 1: - sub_indent_str = "%s " % indent_str + sub_indent_str = f"{indent_str} " else: - sub_indent_str = "%s| " % indent_str + sub_indent_str = f"{indent_str}| " if sub: lines.append(_repr_tree_defs(sub, sub_indent_str)) return "\n".join(lines) @@ -739,8 +739,8 @@ class ImportsChecker(DeprecatedMixin, BaseChecker): "wrong-import-order", node=node, args=( - 'standard import "%s"' % node.as_string(), - '"%s"' % wrong_import[0][0].as_string(), + f'standard import "{node.as_string()}"', + f'"{wrong_import[0][0].as_string()}"', ), ) elif import_category == "THIRDPARTY": @@ -754,8 +754,8 @@ class ImportsChecker(DeprecatedMixin, BaseChecker): "wrong-import-order", node=node, args=( - 'third party import "%s"' % node.as_string(), - '"%s"' % wrong_import[0][0].as_string(), + f'third party import "{node.as_string()}"', + f'"{wrong_import[0][0].as_string()}"', ), ) elif import_category == "FIRSTPARTY": @@ -769,8 +769,8 @@ class ImportsChecker(DeprecatedMixin, BaseChecker): "wrong-import-order", node=node, args=( - 'first party import "%s"' % node.as_string(), - '"%s"' % wrong_import[0][0].as_string(), + f'first party import "{node.as_string()}"', + f'"{wrong_import[0][0].as_string()}"', ), ) elif import_category == "LOCALFOLDER": @@ -787,9 +787,7 @@ class ImportsChecker(DeprecatedMixin, BaseChecker): return None self.add_message("relative-beyond-top-level", node=importnode) except astroid.AstroidSyntaxError as exc: - message = "Cannot import {!r} due to syntax error {!r}".format( - modname, str(exc.error) # pylint: disable=no-member; false positive - ) + message = f"Cannot import {modname!r} due to syntax error {str(exc.error)!r}" # pylint: disable=no-member; false positive self.add_message("syntax-error", line=importnode.lineno, args=message) except astroid.AstroidBuildingException: diff --git a/pylint/checkers/misc.py b/pylint/checkers/misc.py index 4fd9ffd9c..e53935adf 100644 --- a/pylint/checkers/misc.py +++ b/pylint/checkers/misc.py @@ -110,7 +110,7 @@ class EncodingChecker(BaseChecker): if self.config.notes_rgx: regex_string = fr"#\s*({notes}|{self.config.notes_rgx})\b" else: - regex_string = r"#\s*(%s)\b" % (notes) + regex_string = fr"#\s*({notes})\b" self._fixme_pattern = re.compile(regex_string, re.I) diff --git a/pylint/checkers/raw_metrics.py b/pylint/checkers/raw_metrics.py index 22bd096cb..eb3f717ff 100644 --- a/pylint/checkers/raw_metrics.py +++ b/pylint/checkers/raw_metrics.py @@ -29,7 +29,7 @@ def report_raw_stats(sect, stats, old_stats): total_lines = stats["total_lines"] if not total_lines: raise EmptyReportError() - sect.description = "%s lines have been analyzed" % total_lines + sect.description = f"{total_lines} lines have been analyzed" lines = ("type", "number", "%", "previous", "difference") for node_type in ("code", "docstring", "comment", "empty"): key = node_type + "_lines" @@ -40,7 +40,7 @@ def report_raw_stats(sect, stats, old_stats): diff_str = diff_string(old, total) else: old, diff_str = "NC", "NC" - lines += (node_type, str(total), "%.2f" % percent, str(old), diff_str) + lines += (node_type, str(total), f"{percent:.2f}", str(old), diff_str) sect.append(Table(children=lines, cols=5, rheaders=1)) diff --git a/pylint/checkers/refactoring/not_checker.py b/pylint/checkers/refactoring/not_checker.py index a14818ff3..ebe2c0ebe 100644 --- a/pylint/checkers/refactoring/not_checker.py +++ b/pylint/checkers/refactoring/not_checker.py @@ -75,10 +75,8 @@ class NotChecker(checkers.BaseChecker): and _type.qname() in self.skipped_classnames ): return - suggestion = "{} {} {}".format( - left.as_string(), - self.reverse_op[operator], - right.as_string(), + suggestion = ( + f"{left.as_string()} {self.reverse_op[operator]} {right.as_string()}" ) self.add_message( "unneeded-not", node=node, args=(node.as_string(), suggestion) diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index 773839961..7a3048ab4 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -1406,11 +1406,7 @@ class RefactoringChecker(checkers.BaseTokenChecker): suggestion = false_value.as_string() else: message = "consider-using-ternary" - suggestion = "{truth} if {cond} else {false}".format( - truth=truth_value.as_string(), - cond=cond.as_string(), - false=false_value.as_string(), - ) + suggestion = f"{truth_value.as_string()} if {cond.as_string()} else {false_value.as_string()}" self.add_message(message, node=node, args=(suggestion,)) def _append_context_managers_to_stack(self, node: nodes.Assign) -> None: diff --git a/pylint/checkers/similar.py b/pylint/checkers/similar.py index 215b816b2..c3c4ed080 100644 --- a/pylint/checkers/similar.py +++ b/pylint/checkers/similar.py @@ -457,11 +457,7 @@ class Similar: report += f" {line.rstrip()}\n" if line.rstrip() else "\n" duplicated_line_number += number * (len(couples_l) - 1) total_line_number: int = sum(len(lineset) for lineset in self.linesets) - report += "TOTAL lines={} duplicates={} percent={:.2f}\n".format( - total_line_number, - duplicated_line_number, - duplicated_line_number * 100.0 / total_line_number, - ) + report += f"TOTAL lines={total_line_number} duplicates={duplicated_line_number} percent={duplicated_line_number * 100.0 / total_line_number:.2f}\n" return report def _find_common( @@ -676,7 +672,7 @@ class LineSet: ) def __str__(self): - return "<Lineset for %s>" % self.name + return f"<Lineset for {self.name}>" def __len__(self): return len(self._real_lines) diff --git a/pylint/checkers/spelling.py b/pylint/checkers/spelling.py index 16685272f..d5ab5c1ca 100644 --- a/pylint/checkers/spelling.py +++ b/pylint/checkers/spelling.py @@ -233,7 +233,7 @@ class SpellingChecker(BaseTokenChecker): "metavar": "<dict name>", "choices": dict_choices, "help": "Spelling dictionary name. " - "Available dictionaries: %s.%s" % (dicts, instr), + f"Available dictionaries: {dicts}.{instr}", }, ), ( @@ -400,14 +400,14 @@ class SpellingChecker(BaseTokenChecker): # Store word to private dict or raise a message. if self.config.spelling_store_unknown_words: if lower_cased_word not in self.unknown_words: - self.private_dict_file.write("%s\n" % lower_cased_word) + self.private_dict_file.write(f"{lower_cased_word}\n") self.unknown_words.add(lower_cased_word) else: # Present up to N suggestions. suggestions = self.spelling_dict.suggest(word) del suggestions[self.config.max_spelling_suggestions :] line_segment = line[word_start_at:] - match = re.search(r"(\W|^)(%s)(\W|$)" % word, line_segment) + match = re.search(fr"(\W|^)({word})(\W|$)", line_segment) if match: # Start position of second group in regex. col = match.regs[2][0] diff --git a/pylint/checkers/strings.py b/pylint/checkers/strings.py index 666ffb3c6..b45b7bfdc 100644 --- a/pylint/checkers/strings.py +++ b/pylint/checkers/strings.py @@ -80,9 +80,9 @@ _PREFIXES = { "Rb", "RB", } -SINGLE_QUOTED_REGEX = re.compile("(%s)?'''" % "|".join(_PREFIXES)) -DOUBLE_QUOTED_REGEX = re.compile('(%s)?"""' % "|".join(_PREFIXES)) -QUOTE_DELIMITER_REGEX = re.compile("(%s)?(\"|')" % "|".join(_PREFIXES), re.DOTALL) +SINGLE_QUOTED_REGEX = re.compile(f"({'|'.join(_PREFIXES)})?'''") +DOUBLE_QUOTED_REGEX = re.compile(f"({'|'.join(_PREFIXES)})?\"\"\"") +QUOTE_DELIMITER_REGEX = re.compile(f"({'|'.join(_PREFIXES)})?(\"|')", re.DOTALL) MSGS = { # pylint: disable=consider-using-namedtuple-or-dataclass "E1300": ( @@ -673,6 +673,12 @@ class StringConstantChecker(BaseTokenChecker): "in Python 2 to indicate a string was Unicode, but since Python 3.0 strings " "are Unicode by default.", ), + "C1407": ( + "Formatting a regular string which could be a f-string", + "consider-using-f-string", + "Used when we detect a string that is being formatted with format() or % " + "which could potentially be a f-string. The use of f-strings is preferred.", + ), } options = ( ( @@ -910,11 +916,13 @@ class StringConstantChecker(BaseTokenChecker): index += 2 @check_messages("redundant-u-string-prefix") + @check_messages("consider-using-f-string") def visit_const(self, node: nodes.Const): if node.pytype() == "builtins.str" and not isinstance( node.parent, nodes.JoinedStr ): self._detect_u_string_prefix(node) + self._detect_replacable_format_call(node) def _detect_u_string_prefix(self, node: nodes.Const): """Check whether strings include a 'u' prefix like u'String'""" @@ -925,6 +933,69 @@ class StringConstantChecker(BaseTokenChecker): col_offset=node.col_offset, ) + def _detect_replacable_format_call(self, node: nodes.Const) -> None: + """Check whether a string is used in a call to format() or '%' and whether it + can be replaced by a f-string""" + if ( + isinstance(node.parent, nodes.Attribute) + and node.parent.attrname == "format" + ): + # Allow assigning .format to a variable + if isinstance(node.parent.parent, nodes.Assign): + return + + if node.parent.parent.args: + for arg in node.parent.parent.args: + # If star expressions with more than 1 element are being used + if isinstance(arg, nodes.Starred): + inferred = utils.safe_infer(arg.value) + if ( + isinstance(inferred, astroid.List) + and len(inferred.elts) > 1 + ): + return + + elif node.parent.parent.keywords: + keyword_args = [ + i[0] for i in utils.parse_format_method_string(node.value)[0] + ] + for keyword in node.parent.parent.keywords: + # If keyword is used multiple times + if keyword_args.count(keyword.arg) > 1: + return + + keyword = utils.safe_infer(keyword.value) + + # If lists of more than one element are being unpacked + if isinstance(keyword, nodes.Dict): + if len(keyword.items) > 1 and len(keyword_args) > 1: + return + + # If all tests pass, then raise message + self.add_message( + "consider-using-f-string", + line=node.lineno, + col_offset=node.col_offset, + ) + + elif isinstance(node.parent, nodes.BinOp) and node.parent.op == "%": + inferred_right = utils.safe_infer(node.parent.right) + + # If dicts or lists of length > 1 are used + if isinstance(inferred_right, nodes.Dict): + if len(inferred_right.items) > 1: + return + elif isinstance(inferred_right, nodes.List): + if len(inferred_right.elts) > 1: + return + + # If all tests pass, then raise message + self.add_message( + "consider-using-f-string", + line=node.lineno, + col_offset=node.col_offset, + ) + def register(linter): """required method to auto register this checker""" @@ -989,7 +1060,7 @@ def _get_quote_delimiter(string_token: str) -> str: """ match = QUOTE_DELIMITER_REGEX.match(string_token) if not match: - raise ValueError("string token %s is not a well-formed string" % string_token) + raise ValueError(f"string token {string_token} is not a well-formed string") return match.group(2) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 749428376..54a47057a 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -248,7 +248,7 @@ def _missing_member_hint(owner, attrname, distance_threshold, max_choices): if len(names) == 1: names = ", ".join(names) else: - names = "one of {} or {}".format(", ".join(names[:-1]), names[-1]) + names = f"one of {', '.join(names[:-1])} or {names[-1]}" return f"; maybe {names}?" diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index ab5d04d7c..922cc0370 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -185,9 +185,9 @@ def _get_unpacking_extra_info(node, inferred): inferred_module = inferred.root().name if node.root().name == inferred_module: if node.lineno == inferred.lineno: - more = " %s" % inferred.as_string() + more = f" {inferred.as_string()}" elif inferred.lineno: - more = " defined at line %s" % inferred.lineno + more = f" defined at line {inferred.lineno}" elif inferred.lineno: more = f" defined at line {inferred.lineno} of {inferred_module}" return more @@ -1711,7 +1711,7 @@ class VariablesChecker(BaseChecker): if asname is not None: msg = f"{qname} imported as {asname}" else: - msg = "import %s" % name + msg = f"import {name}" self.add_message("unused-import", args=msg, node=stmt) return if isinstance(stmt, nodes.ImportFrom): @@ -2102,7 +2102,7 @@ class VariablesChecker(BaseChecker): if as_name == "_": continue if as_name is None: - msg = "import %s" % imported_name + msg = f"import {imported_name}" else: msg = f"{imported_name} imported as {as_name}" if not _is_type_checking_import(stmt): @@ -2128,8 +2128,7 @@ class VariablesChecker(BaseChecker): if as_name is None: msg = f"{imported_name} imported from {stmt.modname}" else: - fields = (imported_name, stmt.modname, as_name) - msg = "%s imported from %s as %s" % fields + msg = f"{imported_name} imported from {stmt.modname} as {as_name}" if not _is_type_checking_import(stmt): self.add_message("unused-import", args=msg, node=stmt) del self._to_consume diff --git a/pylint/config/__init__.py b/pylint/config/__init__.py index 8c85cc7fa..a3e538124 100644 --- a/pylint/config/__init__.py +++ b/pylint/config/__init__.py @@ -127,7 +127,7 @@ def save_results(results, base): try: os.mkdir(PYLINT_HOME) except OSError: - print("Unable to create directory %s" % PYLINT_HOME, file=sys.stderr) + print(f"Unable to create directory {PYLINT_HOME}", file=sys.stderr) data_file = _get_pdata_path(base, 1) try: with open(data_file, "wb") as stream: diff --git a/pylint/config/man_help_formatter.py b/pylint/config/man_help_formatter.py index d2c6feca2..edef771a4 100644 --- a/pylint/config/man_help_formatter.py +++ b/pylint/config/man_help_formatter.py @@ -16,7 +16,7 @@ class _ManHelpFormatter(optparse.HelpFormatter): ) def format_heading(self, heading): - return ".SH %s\n" % heading.upper() + return f".SH {heading.upper()}\n" def format_description(self, description): return description @@ -54,7 +54,10 @@ class _ManHelpFormatter(optparse.HelpFormatter): @staticmethod def format_title(pgm, section): - date = "%d-%02d-%02d" % time.localtime()[:3] + date = ( + "%d-%02d-%02d" # pylint: disable=consider-using-f-string + % time.localtime()[:3] + ) return f'.TH {pgm} {section} "{date}" {pgm}' @staticmethod diff --git a/pylint/config/option.py b/pylint/config/option.py index 911287ec6..eb5890896 100644 --- a/pylint/config/option.py +++ b/pylint/config/option.py @@ -91,7 +91,7 @@ VALIDATORS = { def _call_validator(opttype, optdict, option, value): if opttype not in VALIDATORS: - raise Exception('Unsupported type "%s"' % opttype) + raise Exception(f'Unsupported type "{opttype}"') try: return VALIDATORS[opttype](optdict, option, value) except TypeError: @@ -149,13 +149,14 @@ class Option(optparse.Option): ) if not isinstance(self.choices, (tuple, list)): raise optparse.OptionError( + # pylint: disable-next=consider-using-f-string "choices must be a list of strings ('%s' supplied)" % str(type(self.choices)).split("'")[1], self, ) elif self.choices is not None: raise optparse.OptionError( - "must not supply choices for type %r" % self.type, self + f"must not supply choices for type {self.type!r}", self ) # pylint: disable=unsupported-assignment-operation diff --git a/pylint/config/option_manager_mixin.py b/pylint/config/option_manager_mixin.py index 97aaea773..106ec47cc 100644 --- a/pylint/config/option_manager_mixin.py +++ b/pylint/config/option_manager_mixin.py @@ -245,7 +245,7 @@ class OptionsManagerMixIn: if opt in self._all_options: break # already processed help_function = functools.partial(self.helpfunc, level=help_level) - help_msg = "%s verbose help." % " ".join(["more"] * help_level) + help_msg = f"{' '.join(['more'] * help_level)} verbose help." optdict = { "action": "callback", "callback": help_function, diff --git a/pylint/epylint.py b/pylint/epylint.py index 59028864f..e3e8b73d1 100755 --- a/pylint/epylint.py +++ b/pylint/epylint.py @@ -186,10 +186,10 @@ def py_run(command_options="", return_std=False, stdout=None, stderr=None): def Run(): if len(sys.argv) == 1: - print("Usage: %s <filename> [options]" % sys.argv[0]) + print(f"Usage: {sys.argv[0]} <filename> [options]") sys.exit(1) elif not os.path.exists(sys.argv[1]): - print("%s does not exist" % sys.argv[1]) + print(f"{sys.argv[1]} does not exist") sys.exit(1) else: sys.exit(lint(sys.argv[1], sys.argv[2:])) diff --git a/pylint/extensions/_check_docs_utils.py b/pylint/extensions/_check_docs_utils.py index 659ef675b..3e29e487f 100644 --- a/pylint/extensions/_check_docs_utils.py +++ b/pylint/extensions/_check_docs_utils.py @@ -247,12 +247,10 @@ class SphinxDocstring(Docstring): \w(?:\w|\.[^\.])* # Valid python name """ - re_simple_container_type = r""" - {type} # a container type + re_simple_container_type = fr""" + {re_type} # a container type [\(\[] [^\n\s]+ [\)\]] # with the contents of the container - """.format( - type=re_type - ) + """ re_multiple_simple_type = r""" (?:{container_type}|{type}) @@ -261,14 +259,12 @@ class SphinxDocstring(Docstring): type=re_type, container_type=re_simple_container_type ) - re_xref = r""" + re_xref = fr""" (?::\w+:)? # optional tag - `{}` # what to reference - """.format( - re_type - ) + `{re_type}` # what to reference + """ - re_param_raw = r""" + re_param_raw = fr""" : # initial colon (?: # Sphinx keywords param|parameter| @@ -278,51 +274,43 @@ class SphinxDocstring(Docstring): \s+ # whitespace (?: # optional type declaration - ({type}|{container_type}) + ({re_type}|{re_simple_container_type}) \s+ )? (\w+) # Parameter name \s* # whitespace : # final colon - """.format( - type=re_type, container_type=re_simple_container_type - ) + """ re_param_in_docstring = re.compile(re_param_raw, re.X | re.S) - re_type_raw = r""" - :type # Sphinx keyword - \s+ # whitespace - ({type}) # Parameter name - \s* # whitespace - : # final colon - """.format( - type=re_multiple_simple_type - ) + re_type_raw = fr""" + :type # Sphinx keyword + \s+ # whitespace + ({re_multiple_simple_type}) # Parameter name + \s* # whitespace + : # final colon + """ re_type_in_docstring = re.compile(re_type_raw, re.X | re.S) - re_property_type_raw = r""" - :type: # Sphinx keyword - \s+ # whitespace - {type} # type declaration - """.format( - type=re_multiple_simple_type - ) + re_property_type_raw = fr""" + :type: # Sphinx keyword + \s+ # whitespace + {re_multiple_simple_type} # type declaration + """ re_property_type_in_docstring = re.compile(re_property_type_raw, re.X | re.S) - re_raise_raw = r""" - : # initial colon - (?: # Sphinx keyword + re_raise_raw = fr""" + : # initial colon + (?: # Sphinx keyword raises?| except|exception ) - \s+ # whitespace - ({type}) # exception type - \s* # whitespace - : # final colon - """.format( - type=re_multiple_simple_type - ) + \s+ # whitespace + ({re_multiple_simple_type}) # exception type + \s* # whitespace + : # final colon + """ re_raise_in_docstring = re.compile(re_raise_raw, re.X | re.S) re_rtype_in_docstring = re.compile(r":rtype:") @@ -454,12 +442,10 @@ class GoogleDocstring(Docstring): re_xref = SphinxDocstring.re_xref - re_container_type = r""" - (?:{type}|{xref}) # a container type + re_container_type = fr""" + (?:{re_type}|{re_xref}) # a container type [\(\[] [^\n]+ [\)\]] # with the contents of the container - """.format( - type=re_type, xref=re_xref - ) + """ re_multiple_type = r""" (?:{container_type}|{type}|{xref}) @@ -484,16 +470,14 @@ class GoogleDocstring(Docstring): ) re_param_line = re.compile( - r""" + fr""" \s* \*{{0,2}}(\w+) # identifier potentially with asterisks \s* ( [(] - {type} + {re_multiple_type} (?:,\s+optional)? [)] )? \s* : # optional type declaration \s* (.*) # beginning of optional description - """.format( - type=re_multiple_type - ), + """, re.X | re.S | re.M, ) @@ -502,12 +486,10 @@ class GoogleDocstring(Docstring): ) re_raise_line = re.compile( - r""" - \s* ({type}) \s* : # identifier + fr""" + \s* ({re_multiple_type}) \s* : # identifier \s* (.*) # beginning of optional description - """.format( - type=re_multiple_type - ), + """, re.X | re.S | re.M, ) @@ -516,22 +498,18 @@ class GoogleDocstring(Docstring): ) re_returns_line = re.compile( - r""" - \s* ({type}:)? # identifier + fr""" + \s* ({re_multiple_type}:)? # identifier \s* (.*) # beginning of description - """.format( - type=re_multiple_type - ), + """, re.X | re.S | re.M, ) re_property_returns_line = re.compile( - r""" - ^{type}: # indentifier + fr""" + ^{re_multiple_type}: # indentifier \s* (.*) # Summary line / description - """.format( - type=re_multiple_type - ), + """, re.X | re.S | re.M, ) @@ -743,15 +721,13 @@ class NumpyDocstring(GoogleDocstring): ) re_param_line = re.compile( - r""" - \s* (\w+) # identifier + fr""" + \s* (\w+) # identifier \s* : - \s* (?:({type})(?:,\s+optional)?)? # optional type declaration - \n # description starts on a new line - \s* (.*) # description - """.format( - type=GoogleDocstring.re_multiple_type - ), + \s* (?:({GoogleDocstring.re_multiple_type})(?:,\s+optional)?)? # optional type declaration + \n # description starts on a new line + \s* (.*) # description + """, re.X | re.S, ) @@ -760,12 +736,10 @@ class NumpyDocstring(GoogleDocstring): ) re_raise_line = re.compile( - r""" - \s* ({type})$ # type declaration - \s* (.*) # optional description - """.format( - type=GoogleDocstring.re_type - ), + fr""" + \s* ({GoogleDocstring.re_type})$ # type declaration + \s* (.*) # optional description + """, re.X | re.S | re.M, ) @@ -774,13 +748,11 @@ class NumpyDocstring(GoogleDocstring): ) re_returns_line = re.compile( - r""" + fr""" \s* (?:\w+\s+:\s+)? # optional name - ({type})$ # type declaration - \s* (.*) # optional description - """.format( - type=GoogleDocstring.re_multiple_type - ), + ({GoogleDocstring.re_multiple_type})$ # type declaration + \s* (.*) # optional description + """, re.X | re.S | re.M, ) diff --git a/pylint/extensions/broad_try_clause.py b/pylint/extensions/broad_try_clause.py index dc38a1c89..b7d2fc974 100644 --- a/pylint/extensions/broad_try_clause.py +++ b/pylint/extensions/broad_try_clause.py @@ -61,9 +61,7 @@ class BroadTryClauseChecker(checkers.BaseChecker): def visit_tryexcept(self, node): try_clause_statements = self._count_statements(node) if try_clause_statements > self.config.max_try_statements: - msg = "try clause contains {} statements, expected at most {}".format( - try_clause_statements, self.config.max_try_statements - ) + msg = f"try clause contains {try_clause_statements} statements, expected at most {self.config.max_try_statements}" self.add_message( "too-many-try-statements", node.lineno, node=node, args=msg ) diff --git a/pylint/extensions/mccabe.py b/pylint/extensions/mccabe.py index 87e9b8375..c85588ca2 100644 --- a/pylint/extensions/mccabe.py +++ b/pylint/extensions/mccabe.py @@ -50,7 +50,7 @@ class PathGraphingAstVisitor(Mccabe_PathGraphingAstVisitor): pathnode = self._append_node(node) self.tail = pathnode self.dispatch_list(node.body) - bottom = "%s" % self._bottom_counter + bottom = f"{self._bottom_counter}" self._bottom_counter += 1 self.graph.connect(self.tail, bottom) self.graph.connect(node, bottom) @@ -135,7 +135,7 @@ class PathGraphingAstVisitor(Mccabe_PathGraphingAstVisitor): else: loose_ends.append(node) if node: - bottom = "%s" % self._bottom_counter + bottom = f"{self._bottom_counter}" self._bottom_counter += 1 for end in loose_ends: self.graph.connect(end, bottom) @@ -181,9 +181,9 @@ class McCabeMethodChecker(checkers.BaseChecker): complexity = graph.complexity() node = graph.root if hasattr(node, "name"): - node_name = "'%s'" % node.name + node_name = f"'{node.name}'" else: - node_name = "This '%s'" % node.__class__.__name__.lower() + node_name = f"This '{node.__class__.__name__.lower()}'" if complexity <= self.config.max_complexity: continue self.add_message( diff --git a/pylint/extensions/overlapping_exceptions.py b/pylint/extensions/overlapping_exceptions.py index aba70b559..891dde59f 100644 --- a/pylint/extensions/overlapping_exceptions.py +++ b/pylint/extensions/overlapping_exceptions.py @@ -66,8 +66,7 @@ class OverlappingExceptionsChecker(checkers.BaseChecker): self.add_message( "overlapping-except", node=handler.type, - args="%s and %s are the same" - % (prev_part.as_string(), part.as_string()), + args=f"{prev_part.as_string()} and {part.as_string()} are the same", ) elif prev_exc in exc_ancestors or exc in prev_exc_ancestors: ancestor = part if exc in prev_exc_ancestors else prev_part @@ -75,8 +74,7 @@ class OverlappingExceptionsChecker(checkers.BaseChecker): self.add_message( "overlapping-except", node=handler.type, - args="%s is an ancestor class of %s" - % (ancestor.as_string(), descendant.as_string()), + args=f"{ancestor.as_string()} is an ancestor class of {descendant.as_string()}", ) handled_in_clause += [(part, exc)] diff --git a/pylint/graph.py b/pylint/graph.py index 7cb357c2d..927fac47e 100644 --- a/pylint/graph.py +++ b/pylint/graph.py @@ -54,18 +54,20 @@ class DotBackend: self.renderer = renderer self.lines = [] self._source = None - self.emit("digraph %s {" % normalize_node_id(graphname)) + self.emit(f"digraph {normalize_node_id(graphname)} {{") if rankdir: - self.emit("rankdir=%s" % rankdir) + self.emit(f"rankdir={rankdir}") if ratio: - self.emit("ratio=%s" % ratio) + self.emit(f"ratio={ratio}") if size: - self.emit('size="%s"' % size) + self.emit(f'size="{size}"') if charset: - assert charset.lower() in ("utf-8", "iso-8859-1", "latin1"), ( - "unsupported charset %s" % charset - ) - self.emit('charset="%s"' % charset) + assert charset.lower() in ( + "utf-8", + "iso-8859-1", + "latin1", + ), f"unsupported charset {charset}" + self.emit(f'charset="{charset}"') for param in additional_param.items(): self.emit("=".join(param)) @@ -150,19 +152,19 @@ class DotBackend: """ attrs = [f'{prop}="{value}"' for prop, value in props.items()] n_from, n_to = normalize_node_id(name1), normalize_node_id(name2) - self.emit("{} -> {} [{}];".format(n_from, n_to, ", ".join(sorted(attrs)))) + self.emit(f"{n_from} -> {n_to} [{', '.join(sorted(attrs))}];") def emit_node(self, name, **props): """emit a node with given properties. node properties: see https://www.graphviz.org/doc/info/attrs.html """ attrs = [f'{prop}="{value}"' for prop, value in props.items()] - self.emit("{} [{}];".format(normalize_node_id(name), ", ".join(sorted(attrs)))) + self.emit(f"{normalize_node_id(name)} [{', '.join(sorted(attrs))}];") def normalize_node_id(nid): """Returns a suitable DOT node id for `nid`.""" - return '"%s"' % nid + return f'"{nid}"' def get_cycles(graph_dict, vertices=None): diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index e4ed47683..d09f4e39e 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -315,8 +315,7 @@ class PyLinter( "choices": [c.name for c in interfaces.CONFIDENCE_LEVELS], "group": "Messages control", "help": "Only show warnings with the listed confidence levels." - " Leave empty to show all. Valid levels: %s." - % (", ".join(c.name for c in interfaces.CONFIDENCE_LEVELS),), + f" Leave empty to show all. Valid levels: {', '.join(c.name for c in interfaces.CONFIDENCE_LEVELS)}.", }, ), ( @@ -630,9 +629,7 @@ class PyLinter( except KeyError: meth = self._bw_options_methods[optname] warnings.warn( - "{} is deprecated, replace it by {}".format( - optname, optname.split("-")[0] - ), + f"{optname} is deprecated, replace it by {optname.split('-')[0]}", DeprecationWarning, ) value = utils._check_csv(value) @@ -652,7 +649,7 @@ class PyLinter( try: checkers.BaseTokenChecker.set_option(self, optname, value, action, optdict) except config.UnsupportedAction: - print("option %s can't be read from config file" % optname, file=sys.stderr) + print(f"option {optname} can't be read from config file", file=sys.stderr) def register_reporter(self, reporter_class): self._reporters[reporter_class.name] = reporter_class @@ -1302,10 +1299,10 @@ class PyLinter( try: note = eval(evaluation, {}, self.stats) # pylint: disable=eval-used except Exception as ex: # pylint: disable=broad-except - msg = "An exception occurred while rating: %s" % ex + msg = f"An exception occurred while rating: {ex}" else: self.stats["global_note"] = note - msg = "Your code has been rated at %.2f/10" % note + msg = f"Your code has been rated at {note:.2f}/10" pnote = previous_stats.get("global_note") if pnote is not None: msg += f" (previous run: {pnote:.2f}/10, {note - pnote:+.2f})" diff --git a/pylint/lint/report_functions.py b/pylint/lint/report_functions.py index 15ca05d62..fd316c611 100644 --- a/pylint/lint/report_functions.py +++ b/pylint/lint/report_functions.py @@ -65,7 +65,7 @@ def report_messages_by_module_stats(sect, stats, _): continue lines.append(line[-1]) for val in line[:-1]: - lines.append("%.2f" % val) + lines.append(f"{val:.2f}") if len(lines) == 5: raise exceptions.EmptyReportError() sect.append(report_nodes.Table(children=lines, cols=5, rheaders=1)) diff --git a/pylint/lint/run.py b/pylint/lint/run.py index c84567cc2..ac4f586f4 100644 --- a/pylint/lint/run.py +++ b/pylint/lint/run.py @@ -40,7 +40,7 @@ def cb_list_extensions(option, optname, value, parser): def cb_list_confidence_levels(option, optname, value, parser): for level in interfaces.CONFIDENCE_LEVELS: - print("%-18s: %s" % level) + print(f"%-18s: {level}") sys.exit(0) @@ -350,8 +350,7 @@ group are mutually exclusive.", if linter.config.jobs < 0: print( - "Jobs number (%d) should be greater than or equal to 0" - % linter.config.jobs, + f"Jobs number ({linter.config.jobs}) should be greater than or equal to 0", file=sys.stderr, ) sys.exit(32) diff --git a/pylint/lint/utils.py b/pylint/lint/utils.py index 38784a9bc..b1f8edfff 100644 --- a/pylint/lint/utils.py +++ b/pylint/lint/utils.py @@ -96,12 +96,12 @@ def preprocess_options(args, search_for): del args[i] if takearg and val is None: if i >= len(args) or args[i].startswith("-"): - msg = "Option %s expects a value" % option + msg = f"Option {option} expects a value" raise ArgumentPreprocessingError(msg) val = args[i] del args[i] elif not takearg and val is not None: - msg = "Option %s doesn't expects a value" % option + msg = f"Option {option} doesn't expects a value" raise ArgumentPreprocessingError(msg) cb(option, val) diff --git a/pylint/message/message_definition.py b/pylint/message/message_definition.py index a48526e9b..f7f680590 100644 --- a/pylint/message/message_definition.py +++ b/pylint/message/message_definition.py @@ -64,24 +64,24 @@ class MessageDefinition: """return the help string for the given message id""" desc = self.description if checkerref: - desc += " This message belongs to the %s checker." % self.checker_name + desc += f" This message belongs to the {self.checker_name} checker." title = self.msg if self.minversion or self.maxversion: restr = [] if self.minversion: - restr.append("< %s" % ".".join(str(n) for n in self.minversion)) + restr.append(f"< {'.'.join(str(n) for n in self.minversion)}") if self.maxversion: - restr.append(">= %s" % ".".join(str(n) for n in self.maxversion)) + restr.append(f">= {'.'.join(str(n) for n in self.maxversion)}") restriction = " or ".join(restr) if checkerref: - desc += " It can't be emitted when using Python %s." % restriction + desc += f" It can't be emitted when using Python {restriction}." else: desc += ( - " This message can't be emitted when using Python %s." % restriction + f" This message can't be emitted when using Python {restriction}." ) msg_help = normalize_text(" ".join(desc.split()), indent=" ") message_id = f"{self.symbol} ({self.msgid})" if title != "%s": title = title.splitlines()[0] - return ":{}: *{}*\n{}".format(message_id, title.rstrip(" "), msg_help) + return f":{message_id}: *{title.rstrip(' ')}*\n{msg_help}" return f":{message_id}:\n{msg_help}" diff --git a/pylint/message/message_handler_mix_in.py b/pylint/message/message_handler_mix_in.py index 495599757..c2dd4d0cb 100644 --- a/pylint/message/message_handler_mix_in.py +++ b/pylint/message/message_handler_mix_in.py @@ -255,20 +255,18 @@ class MessagesHandlerMixIn: if message_definition.scope == WarningScope.LINE: if line is None: raise InvalidMessageError( - "Message %s must provide line, got None" - % message_definition.msgid + f"Message {message_definition.msgid} must provide line, got None" ) if node is not None: raise InvalidMessageError( - "Message %s must only provide line, " - "got line=%s, node=%s" % (message_definition.msgid, line, node) + f"Message {message_definition.msgid} must only provide line, " + f"got line={line}, node={node}" ) elif message_definition.scope == WarningScope.NODE: # Node-based warnings may provide an override line. if node is None: raise InvalidMessageError( - "Message %s must provide Node, got None" - % message_definition.msgid + f"Message {message_definition.msgid} must provide Node, got None" ) def add_one_message( @@ -372,9 +370,9 @@ Pylint provides global options and switches. if section is None: title = "General options" else: - title = "%s options" % section.capitalize() + title = f"{section.capitalize()} options" result += get_rst_title(title, "~") - result += "%s\n" % get_rst_section(None, options) + result += f"{get_rst_section(None, options)}\n" result += get_rst_title("Pylint checkers' options and switches", "-") result += """\ diff --git a/pylint/pyreverse/diadefslib.py b/pylint/pyreverse/diadefslib.py index b6f6110e8..df9a7f6ad 100644 --- a/pylint/pyreverse/diadefslib.py +++ b/pylint/pyreverse/diadefslib.py @@ -139,10 +139,10 @@ class DefaultDiadefGenerator(LocalsVisitor, DiaDefGenerator): """ mode = self.config.mode if len(node.modules) > 1: - self.pkgdiagram = PackageDiagram("packages %s" % node.name, mode) + self.pkgdiagram = PackageDiagram(f"packages {node.name}", mode) else: self.pkgdiagram = None - self.classdiagram = ClassDiagram("classes %s" % node.name, mode) + self.classdiagram = ClassDiagram(f"classes {node.name}", mode) def leave_project(self, node): # pylint: disable=unused-argument """leave the pyreverse.utils.Project node diff --git a/pylint/pyreverse/diagrams.py b/pylint/pyreverse/diagrams.py index 6160fb48c..abb456ade 100644 --- a/pylint/pyreverse/diagrams.py +++ b/pylint/pyreverse/diagrams.py @@ -110,7 +110,7 @@ class ClassDiagram(Figure, FilterMixIn): continue names = self.class_names(associated_nodes) if names: - node_name = "{} : {}".format(node_name, ", ".join(names)) + node_name = f"{node_name} : {', '.join(names)}" attrs.append(node_name) return sorted(attrs) @@ -240,7 +240,7 @@ class PackageDiagram(ClassDiagram): package = node.root().name if mod_name == f"{package}.{name}": return mod - if mod_name == "{}.{}".format(package.rsplit(".", 1)[0], name): + if mod_name == f"{package.rsplit('.', 1)[0]}.{name}": return mod raise KeyError(name) diff --git a/pylint/pyreverse/inspector.py b/pylint/pyreverse/inspector.py index 7a0749243..6a97982df 100644 --- a/pylint/pyreverse/inspector.py +++ b/pylint/pyreverse/inspector.py @@ -33,7 +33,7 @@ def _iface_hdlr(_): def _astroid_wrapper(func, modname): - print("parsing %s..." % modname) + print(f"parsing {modname}...") try: return func(modname) except astroid.exceptions.AstroidBuildingException as exc: @@ -282,7 +282,7 @@ class Linker(IdGeneratorMixIn, utils.LocalsVisitor): module = node.root() context_name = module.name if relative: - mod_path = "{}.{}".format(".".join(context_name.split(".")[:-1]), mod_path) + mod_path = f"{'.'.join(context_name.split('.')[:-1])}.{mod_path}" if self.compute_module(context_name, mod_path): # handle dependencies if not hasattr(module, "depends"): diff --git a/pylint/pyreverse/utils.py b/pylint/pyreverse/utils.py index b68073a26..71702febf 100644 --- a/pylint/pyreverse/utils.py +++ b/pylint/pyreverse/utils.py @@ -131,7 +131,7 @@ class FilterMixIn: try: __mode += MODES[nummod] except KeyError as ex: - print("Unknown filter mode %s" % ex, file=sys.stderr) + print(f"Unknown filter mode {ex}", file=sys.stderr) self.__mode = __mode def show_attr(self, node): @@ -176,10 +176,10 @@ class ASTWalker: handler = self.handler kid = klass.__name__.lower() e_method = getattr( - handler, "visit_%s" % kid, getattr(handler, "visit_default", None) + handler, f"visit_{kid}", getattr(handler, "visit_default", None) ) l_method = getattr( - handler, "leave_%s" % kid, getattr(handler, "leave_default", None) + handler, f"leave_{kid}", getattr(handler, "leave_default", None) ) self._cache[klass] = (e_method, l_method) else: @@ -257,7 +257,7 @@ def get_annotation( label = get_annotation_label(ann) if ann: label = ( - rf"Optional[{label}]" + fr"Optional[{label}]" if getattr(default, "value", "value") is None and not label.startswith("Optional") else label diff --git a/pylint/pyreverse/vcg_printer.py b/pylint/pyreverse/vcg_printer.py index ca8bf6d72..238bfb8bb 100644 --- a/pylint/pyreverse/vcg_printer.py +++ b/pylint/pyreverse/vcg_printer.py @@ -227,7 +227,7 @@ class VCGPrinter(Printer): @staticmethod def _build_label_for_node(properties: NodeProperties) -> str: fontcolor = "\f09" if properties.fontcolor == "red" else "" - label = rf"\fb{fontcolor}{properties.label}\fn" + label = fr"\fb{fontcolor}{properties.label}\fn" if properties.attrs is None and properties.methods is None: # return a compact form which only displays the classname in a box return label diff --git a/pylint/reporters/base_reporter.py b/pylint/reporters/base_reporter.py index 067a32c77..fda99f4ef 100644 --- a/pylint/reporters/base_reporter.py +++ b/pylint/reporters/base_reporter.py @@ -42,7 +42,7 @@ class BaseReporter: """display results encapsulated in the layout tree""" self.section = 0 if hasattr(layout, "report_id"): - layout.children[0].children[0].data += " (%s)" % layout.report_id + layout.children[0].children[0].data += f" ({layout.report_id})" self._display(layout) def _display(self, layout): diff --git a/pylint/reporters/reports_handler_mix_in.py b/pylint/reporters/reports_handler_mix_in.py index f42824559..914556ef4 100644 --- a/pylint/reporters/reports_handler_mix_in.py +++ b/pylint/reporters/reports_handler_mix_in.py @@ -51,7 +51,7 @@ class ReportsHandlerMixIn: def make_reports(self, stats, old_stats): """render registered reports""" - sect = Section("Report", "%s statements analysed." % (self.stats["statement"])) + sect = Section("Report", f"{self.stats['statement']} statements analysed.") for checker in self.report_order(): for reportid, r_title, r_cb in self._reports[checker]: if not self.report_is_enabled(reportid): diff --git a/pylint/reporters/text.py b/pylint/reporters/text.py index 865b3c84d..6d9a05f78 100644 --- a/pylint/reporters/text.py +++ b/pylint/reporters/text.py @@ -147,7 +147,7 @@ class TextReporter(BaseReporter): """manage message of different type and in the context of path""" if msg.module not in self._modules: if msg.module: - self.writeln("************* Module %s" % msg.module) + self.writeln(f"************* Module {msg.module}") self._modules.add(msg.module) else: self.writeln("************* ") @@ -171,8 +171,7 @@ class ParseableTextReporter(TextReporter): def __init__(self, output=None): warnings.warn( - "%s output format is deprecated. This is equivalent " - "to --msg-template=%s" % (self.name, self.line_format), + f"{self.name} output format is deprecated. This is equivalent to --msg-template={self.line_format}", DeprecationWarning, ) TextReporter.__init__(self, output) @@ -227,10 +226,10 @@ class ColorizedTextReporter(TextReporter): color, style = self._get_decoration("S") if msg.module: modsep = colorize_ansi( - "************* Module %s" % msg.module, color, style + f"************* Module {msg.module}", color, style ) else: - modsep = colorize_ansi("************* %s" % msg.module, color, style) + modsep = colorize_ansi(f"************* {msg.module}", color, style) self.writeln(modsep) self._modules.add(msg.module) color, style = self._get_decoration(msg.C) diff --git a/pylint/reporters/ureports/nodes.py b/pylint/reporters/ureports/nodes.py index 76d75959c..d359093b8 100644 --- a/pylint/reporters/ureports/nodes.py +++ b/pylint/reporters/ureports/nodes.py @@ -50,11 +50,11 @@ class VNode: return self.__class__.__name__.lower() def accept(self, visitor, *args, **kwargs): - func = getattr(visitor, "visit_%s" % self._get_visit_name()) + func = getattr(visitor, f"visit_{self._get_visit_name()}") return func(self, *args, **kwargs) def leave(self, visitor, *args, **kwargs): - func = getattr(visitor, "leave_%s" % self._get_visit_name()) + func = getattr(visitor, f"leave_{self._get_visit_name()}") return func(self, *args, **kwargs) diff --git a/pylint/reporters/ureports/text_writer.py b/pylint/reporters/ureports/text_writer.py index 42b03f0ae..a48d73aac 100644 --- a/pylint/reporters/ureports/text_writer.py +++ b/pylint/reporters/ureports/text_writer.py @@ -94,4 +94,4 @@ class TextWriter(BaseWriter): def visit_text(self, layout): """add some text""" - self.write("%s" % layout.data) + self.write(f"{layout.data}") diff --git a/pylint/testutils/constants.py b/pylint/testutils/constants.py index fa70d3b57..fc04d927d 100644 --- a/pylint/testutils/constants.py +++ b/pylint/testutils/constants.py @@ -7,7 +7,9 @@ import sys from os.path import abspath, dirname from pathlib import Path -SYS_VERS_STR = "%d%d%d" % sys.version_info[:3] +SYS_VERS_STR = ( + "%d%d%d" % sys.version_info[:3] # pylint: disable=consider-using-f-string +) TITLE_UNDERLINES = ["", "=", "-", "."] PREFIX = abspath(dirname(__file__)) UPDATE_OPTION = "--update-functional-output" @@ -20,7 +22,7 @@ _MESSAGE = {"msg": r"[a-z][a-z\-]+"} # - followed by a list of bracketed message symbols. # Used to extract expected messages from testdata files. _EXPECTED_RE = re.compile( - r"\s*#\s*(?:(?P<line>[+-]?[0-9]+):)?" + r"\s*#\s*(?:(?P<line>[+-]?[0-9]+):)?" # pylint: disable=consider-using-f-string r"(?:(?P<op>[><=]+) *(?P<version>[0-9.]+):)?" r"\s*\[(?P<msgs>%(msg)s(?:,\s*%(msg)s)*)]" % _MESSAGE ) diff --git a/pylint/testutils/lint_module_test.py b/pylint/testutils/lint_module_test.py index 8f24daf86..2b5ad2dcb 100644 --- a/pylint/testutils/lint_module_test.py +++ b/pylint/testutils/lint_module_test.py @@ -53,8 +53,7 @@ class LintModuleTest: def setUp(self): if self._should_be_skipped_due_to_version(): pytest.skip( - "Test cannot run with Python %s." - % sys.version.split(" ", maxsplit=1)[0] + f"Test cannot run with Python {sys.version.split(' ', maxsplit=1)[0]}." ) missing = [] for requirement in self._test_file.options["requires"]: @@ -63,7 +62,7 @@ class LintModuleTest: except ImportError: missing.append(requirement) if missing: - pytest.skip("Requires %s to be present." % ",".join(missing)) + pytest.skip(f"Requires {','.join(missing)} to be present.") except_implementations = self._test_file.options["except_implementations"] if except_implementations: implementations = [i.strip() for i in except_implementations.split(",")] @@ -74,7 +73,7 @@ class LintModuleTest: if excluded_platforms: platforms = [p.strip() for p in excluded_platforms.split(",")] if sys.platform.lower() in platforms: - pytest.skip("Test cannot run on platform %r" % sys.platform) + pytest.skip(f"Test cannot run on platform {sys.platform!r}") def runTest(self): self._runTest() @@ -193,16 +192,19 @@ class LintModuleTest: def error_msg_for_unequal_messages( self, actual_messages, expected_messages, actual_output: List[OutputLine] ): - msg = ['Wrong results for file "%s":' % (self._test_file.base)] + msg = [f'Wrong results for file "{self._test_file.base}":'] missing, unexpected = self.multiset_difference( expected_messages, actual_messages ) if missing: msg.append("\nExpected in testdata:") - msg.extend(" %3d: %s" % msg for msg in sorted(missing)) + msg.extend( + " %3d: %s" % msg # pylint: disable=consider-using-f-string + for msg in sorted(missing) + ) if unexpected: msg.append("\nUnexpected in testdata:") - msg.extend(" %3d: %s" % msg for msg in sorted(unexpected)) # type: ignore + msg.extend(" %3d: %s" % msg for msg in sorted(unexpected)) # type: ignore #pylint: disable=consider-using-f-string error_msg = "\n".join(msg) if self._config and self._config.getoption("verbose") > 0: error_msg += "\n\nActual pylint output for this file:\n" diff --git a/pylint/testutils/reporter_for_tests.py b/pylint/testutils/reporter_for_tests.py index 45aa63577..b1b7af7e4 100644 --- a/pylint/testutils/reporter_for_tests.py +++ b/pylint/testutils/reporter_for_tests.py @@ -32,7 +32,7 @@ class GenericTestReporter(BaseReporter): str_message: str = msg.msg self.message_ids[msg_id] = 1 if obj: - obj = ":%s" % obj + obj = f":{obj}" sigle = msg_id[0] if linesep != "\n": # 2to3 writes os.linesep instead of using diff --git a/pylint/utils/utils.py b/pylint/utils/utils.py index 1a8f7957d..e0241ce0b 100644 --- a/pylint/utils/utils.py +++ b/pylint/utils/utils.py @@ -47,7 +47,7 @@ def diff_string(old, new): difference """ diff = abs(old - new) - diff_str = "{}{}".format(CMPS[cmp(old, new)], diff and ("%.2f" % diff) or "") + diff_str = f"{CMPS[cmp(old, new)]}{diff and f'{diff:.2f}' or ''}" return diff_str @@ -80,16 +80,16 @@ def get_rst_section(section, options, doc=None): result += get_rst_title(section, "'") if doc: formatted_doc = normalize_text(doc) - result += "%s\n\n" % formatted_doc + result += f"{formatted_doc}\n\n" for optname, optdict, value in options: help_opt = optdict.get("help") - result += ":%s:\n" % optname + result += f":{optname}:\n" if help_opt: formatted_help = normalize_text(help_opt, indent=" ") - result += "%s\n" % formatted_help + result += f"{formatted_help}\n" if value: value = str(_format_option_value(optdict, value)) - result += "\n Default: ``%s``\n" % value.replace("`` ", "```` ``") + result += f"\n Default: ``{value.replace('`` ', '```` ``')}``\n" return result @@ -240,7 +240,7 @@ def _check_csv(value): def _comment(string): """return string as a comment""" lines = [line.strip() for line in string.splitlines()] - return "# " + ("%s# " % os.linesep).join(lines) + return "# " + f"{os.linesep}# ".join(lines) def _format_option_value(optdict, value): @@ -257,7 +257,7 @@ def _format_option_value(optdict, value): elif optdict.get("type") == "yn": value = "yes" if value else "no" elif isinstance(value, str) and value.isspace(): - value = "'%s'" % value + value = f"'{value}'" return value @@ -265,7 +265,7 @@ def format_section(stream, section, options, doc=None): """format an options section using the INI format""" if doc: print(_comment(doc), file=stream) - print("[%s]" % section, file=stream) + print(f"[{section}]", file=stream) _ini_format(stream, options) @@ -281,7 +281,7 @@ def _ini_format(stream, options): else: print(file=stream) if value is None: - print("#%s=" % optname, file=stream) + print(f"#{optname}=", file=stream) else: value = str(value).strip() if re.match(r"^([\w-]+,)+[\w-]+$", str(value)): diff --git a/script/bump_changelog.py b/script/bump_changelog.py index 8d8225379..6b8e4abbf 100644 --- a/script/bump_changelog.py +++ b/script/bump_changelog.py @@ -18,7 +18,7 @@ RELEASE_DATE_TEXT = "Release date: TBA" WHATS_NEW_TEXT = "What's New in Pylint" TODAY = datetime.now() FULL_WHATS_NEW_TEXT = WHATS_NEW_TEXT + " {version}?" -NEW_RELEASE_DATE_MESSAGE = "Release date: {}".format(TODAY.strftime("%Y-%m-%d")) +NEW_RELEASE_DATE_MESSAGE = f"Release date: {TODAY.strftime('%Y-%m-%d')}" def main() -> None: @@ -128,10 +128,14 @@ def transform_content(content: str, version: str) -> str: def do_checks(content, next_version, version, version_type): err = "in the changelog, fix that first!" NEW_VERSION_ERROR_MSG = ( - "The text for this version '{version}' did not exists %s" % err + # pylint: disable-next=consider-using-f-string + "The text for this version '{version}' did not exists %s" + % err ) NEXT_VERSION_ERROR_MSG = ( - "The text for the next version '{version}' already exists %s" % err + # pylint: disable-next=consider-using-f-string + "The text for the next version '{version}' already exists %s" + % err ) wn_next_version = get_whats_new(next_version) wn_this_version = get_whats_new(version) diff --git a/tests/benchmark/test_baseline_benchmarks.py b/tests/benchmark/test_baseline_benchmarks.py index e7713daff..693fe4d68 100644 --- a/tests/benchmark/test_baseline_benchmarks.py +++ b/tests/benchmark/test_baseline_benchmarks.py @@ -131,9 +131,7 @@ class TestEstablishBaselineBenchmarks: benchmark(linter.check, fileinfos) assert ( linter.msg_status == 0 - ), "Expected no errors to be thrown: %s" % pprint.pformat( - linter.reporter.messages - ) + ), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}" def test_baseline_benchmark_j10(self, benchmark): """Establish a baseline of pylint performance with no work across threads @@ -155,9 +153,7 @@ class TestEstablishBaselineBenchmarks: benchmark(linter.check, fileinfos) assert ( linter.msg_status == 0 - ), "Expected no errors to be thrown: %s" % pprint.pformat( - linter.reporter.messages - ) + ), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}" def test_baseline_benchmark_check_parallel_j10(self, benchmark): """Should demonstrate times very close to `test_baseline_benchmark_j10`""" @@ -170,9 +166,7 @@ class TestEstablishBaselineBenchmarks: benchmark(check_parallel, linter, jobs=10, files=fileinfos) assert ( linter.msg_status == 0 - ), "Expected no errors to be thrown: %s" % pprint.pformat( - linter.reporter.messages - ) + ), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}" def test_baseline_lots_of_files_j1(self, benchmark): """Establish a baseline with only 'master' checker being run in -j1 @@ -190,9 +184,7 @@ class TestEstablishBaselineBenchmarks: benchmark(linter.check, fileinfos) assert ( linter.msg_status == 0 - ), "Expected no errors to be thrown: %s" % pprint.pformat( - linter.reporter.messages - ) + ), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}" def test_baseline_lots_of_files_j10(self, benchmark): """Establish a baseline with only 'master' checker being run in -j10 @@ -211,9 +203,7 @@ class TestEstablishBaselineBenchmarks: benchmark(linter.check, fileinfos) assert ( linter.msg_status == 0 - ), "Expected no errors to be thrown: %s" % pprint.pformat( - linter.reporter.messages - ) + ), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}" def test_baseline_lots_of_files_j1_empty_checker(self, benchmark): """Baselines pylint for a single extra checker being run in -j1, for N-files @@ -232,9 +222,7 @@ class TestEstablishBaselineBenchmarks: benchmark(linter.check, fileinfos) assert ( linter.msg_status == 0 - ), "Expected no errors to be thrown: %s" % pprint.pformat( - linter.reporter.messages - ) + ), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}" def test_baseline_lots_of_files_j10_empty_checker(self, benchmark): """Baselines pylint for a single extra checker being run in -j10, for N-files @@ -253,9 +241,7 @@ class TestEstablishBaselineBenchmarks: benchmark(linter.check, fileinfos) assert ( linter.msg_status == 0 - ), "Expected no errors to be thrown: %s" % pprint.pformat( - linter.reporter.messages - ) + ), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}" def test_baseline_benchmark_j1_single_working_checker(self, benchmark): """Establish a baseline of single-worker performance for PyLinter @@ -280,9 +266,7 @@ class TestEstablishBaselineBenchmarks: benchmark(linter.check, fileinfos) assert ( linter.msg_status == 0 - ), "Expected no errors to be thrown: %s" % pprint.pformat( - linter.reporter.messages - ) + ), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}" def test_baseline_benchmark_j10_single_working_checker(self, benchmark): """Establishes baseline of multi-worker performance for PyLinter/check_parallel @@ -308,9 +292,7 @@ class TestEstablishBaselineBenchmarks: benchmark(linter.check, fileinfos) assert ( linter.msg_status == 0 - ), "Expected no errors to be thrown: %s" % pprint.pformat( - linter.reporter.messages - ) + ), f"Expected no errors to be thrown: {pprint.pformat(linter.reporter.messages)}" def test_baseline_benchmark_j1_all_checks_single_file(self, benchmark): """Runs a single file, with -j1, against all plug-ins @@ -327,9 +309,7 @@ class TestEstablishBaselineBenchmarks: assert ( runner.linter.msg_status == 0 - ), "Expected no errors to be thrown: %s" % pprint.pformat( - runner.linter.reporter.messages - ) + ), f"Expected no errors to be thrown: {pprint.pformat(runner.linter.reporter.messages)}" def test_baseline_benchmark_j1_all_checks_lots_of_files(self, benchmark): """Runs lots of files, with -j1, against all plug-ins diff --git a/tests/checkers/unittest_python3.py b/tests/checkers/unittest_python3.py index d8f87d14f..6dd78cd25 100644 --- a/tests/checkers/unittest_python3.py +++ b/tests/checkers/unittest_python3.py @@ -500,7 +500,7 @@ class TestPython3Checker(testutils.CheckerTestCase): def test_dict_iter_method(self): for meth in ("keys", "values", "items"): - node = astroid.extract_node("x.iter%s() #@" % meth) + node = astroid.extract_node(f"x.iter{meth}() #@") message = testutils.Message("dict-iter-method", node=node) with self.assertAddsMessages(message): self.checker.visit_call(node) @@ -537,7 +537,7 @@ class TestPython3Checker(testutils.CheckerTestCase): def test_dict_view_method(self): for meth in ("keys", "values", "items"): - node = astroid.extract_node("x.view%s() #@" % meth) + node = astroid.extract_node(f"x.view{meth}() #@") message = testutils.Message("dict-view-method", node=node) with self.assertAddsMessages(message): self.checker.visit_call(node) diff --git a/tests/checkers/unittest_similar.py b/tests/checkers/unittest_similar.py index 467033e62..f27df9272 100644 --- a/tests/checkers/unittest_similar.py +++ b/tests/checkers/unittest_similar.py @@ -53,10 +53,10 @@ def test_ignore_comments(): assert ( output.getvalue().strip() == ( - """ + f""" 10 similar lines in 2 files -==%s:[0:11] -==%s:[0:11] +=={SIMILAR1}:[0:11] +=={SIMILAR2}:[0:11] import one from two import two three @@ -70,7 +70,6 @@ def test_ignore_comments(): ''' ten TOTAL lines=62 duplicates=10 percent=16.13 """ - % (SIMILAR1, SIMILAR2) ).strip() ) @@ -83,10 +82,10 @@ def test_ignore_docstrings(): assert ( output.getvalue().strip() == ( - """ + f""" 5 similar lines in 2 files -==%s:[7:15] -==%s:[7:15] +=={SIMILAR1}:[7:15] +=={SIMILAR2}:[7:15] seven eight nine @@ -97,8 +96,8 @@ def test_ignore_docstrings(): fourteen 5 similar lines in 2 files -==%s:[0:5] -==%s:[0:5] +=={SIMILAR1}:[0:5] +=={SIMILAR2}:[0:5] import one from two import two three @@ -106,7 +105,6 @@ def test_ignore_docstrings(): five TOTAL lines=62 duplicates=10 percent=16.13 """ - % ((SIMILAR1, SIMILAR2) * 2) ).strip() ) @@ -132,10 +130,10 @@ def test_multiline_imports(): assert ( output.getvalue().strip() == ( - """ + f""" 8 similar lines in 2 files -==%s:[0:8] -==%s:[0:8] +=={MULTILINE}:[0:8] +=={MULTILINE}:[0:8] from foo import ( bar, baz, @@ -146,7 +144,6 @@ def test_multiline_imports(): ) TOTAL lines=16 duplicates=8 percent=50.00 """ - % (MULTILINE, MULTILINE) ).strip() ) @@ -172,10 +169,10 @@ def test_ignore_signatures_fail(): assert ( output.getvalue().strip() == ( - ''' + f''' 9 similar lines in 2 files -==%s:[7:17] -==%s:[8:18] +=={SIMILAR5}:[7:17] +=={SIMILAR6}:[8:18] arg1: int = 3, arg2: Class1 = val1, arg3: Class2 = func3(val2), @@ -188,8 +185,8 @@ def test_ignore_signatures_fail(): """Valid function definition with docstring only.""" 6 similar lines in 2 files -==%s:[0:6] -==%s:[1:7] +=={SIMILAR5}:[0:6] +=={SIMILAR6}:[1:7] @deco1(dval1) @deco2(dval2) @deco3( @@ -198,7 +195,6 @@ def test_ignore_signatures_fail(): ) TOTAL lines=35 duplicates=15 percent=42.86 ''' - % (SIMILAR5, SIMILAR6, SIMILAR5, SIMILAR6) ).strip() ) @@ -224,10 +220,10 @@ def test_ignore_signatures_class_methods_fail(): assert ( output.getvalue().strip() == ( - ''' + f''' 15 similar lines in 2 files -==%s:[1:18] -==%s:[1:18] +=={SIMILAR_CLS_A}:[1:18] +=={SIMILAR_CLS_B}:[1:18] def parent_method( self, *, @@ -247,8 +243,8 @@ def test_ignore_signatures_class_methods_fail(): 7 similar lines in 2 files -==%s:[20:27] -==%s:[20:27] +=={SIMILAR_CLS_A}:[20:27] +=={SIMILAR_CLS_B}:[20:27] self, *, a=None, @@ -258,7 +254,6 @@ def test_ignore_signatures_class_methods_fail(): pass TOTAL lines=54 duplicates=22 percent=40.74 ''' - % (SIMILAR_CLS_A, SIMILAR_CLS_B, SIMILAR_CLS_A, SIMILAR_CLS_B) ).strip() ) @@ -284,10 +279,10 @@ def test_ignore_signatures_empty_functions_fail(): assert ( output.getvalue().strip() == ( - ''' + f''' 6 similar lines in 2 files -==%s:[1:7] -==%s:[1:7] +=={EMPTY_FUNCTION_1}:[1:7] +=={EMPTY_FUNCTION_2}:[1:7] arg1: int = 1, arg2: str = "2", arg3: int = 3, @@ -296,7 +291,6 @@ def test_ignore_signatures_empty_functions_fail(): """Valid function definition with docstring only.""" TOTAL lines=14 duplicates=6 percent=42.86 ''' - % (EMPTY_FUNCTION_1, EMPTY_FUNCTION_2) ).strip() ) @@ -330,10 +324,10 @@ def test_ignore_nothing(): assert ( output.getvalue().strip() == ( - """ + f""" 5 similar lines in 2 files -==%s:[0:5] -==%s:[0:5] +=={SIMILAR1}:[0:5] +=={SIMILAR2}:[0:5] import one from two import two three @@ -341,7 +335,6 @@ def test_ignore_nothing(): five TOTAL lines=62 duplicates=5 percent=8.06 """ - % (SIMILAR1, SIMILAR2) ).strip() ) @@ -354,10 +347,10 @@ def test_lines_without_meaningful_content_do_not_trigger_similarity(): assert ( output.getvalue().strip() == ( - """ + f""" 14 similar lines in 2 files -==%s:[11:25] -==%s:[11:25] +=={SIMILAR3}:[11:25] +=={SIMILAR4}:[11:25] b = ( ( [ @@ -374,7 +367,6 @@ def test_lines_without_meaningful_content_do_not_trigger_similarity(): ) TOTAL lines=50 duplicates=14 percent=28.00 """ - % (SIMILAR3, SIMILAR4) ).strip() ) diff --git a/tests/functional/a/arguments.py b/tests/functional/a/arguments.py index 7d0c74f83..e1a8005aa 100644 --- a/tests/functional/a/arguments.py +++ b/tests/functional/a/arguments.py @@ -1,5 +1,5 @@ # pylint: disable=too-few-public-methods, no-absolute-import,missing-docstring,import-error,wrong-import-position -# pylint: disable=wrong-import-order, useless-object-inheritance,unnecessary-lambda +# pylint: disable=wrong-import-order, useless-object-inheritance,unnecessary-lambda, consider-using-f-string def decorator(fun): """Decorator""" diff --git a/tests/functional/c/consider/consider_using_f_string.py b/tests/functional/c/consider/consider_using_f_string.py new file mode 100644 index 000000000..825f3517c --- /dev/null +++ b/tests/functional/c/consider/consider_using_f_string.py @@ -0,0 +1,107 @@ +"""Test to see if a f-string would be possible and consider-using-f-string should be raised""" +# pylint: disable=unused-variable, invalid-name, missing-function-docstring, pointless-statement +# pylint: disable=expression-not-assigned, repeated-keyword + +PARAM_1 = PARAM_2 = PARAM_3 = 1 +PARAM_LIST = [PARAM_1, PARAM_2, PARAM_3] +PARAM_LIST_SINGLE = [PARAM_1] +PARAM_DICT = {"Param_1": PARAM_1, "Param_2": PARAM_2, "Param_3": PARAM_3} +PARAM_DICT_SINGLE = {"Param_1": PARAM_1} + + +def return_parameter(): + return PARAM_1 + + +def return_list(): + return PARAM_LIST + + +def return_dict(): + return PARAM_DICT + + +def print_good(): + print("String {}, {} or {}".format(*PARAM_LIST)) + print("String {}, {}, {} or {}".format(*PARAM_LIST_SINGLE, *PARAM_LIST)) + print("String {Param}, {}, {} or {}".format(Param=PARAM_1, *PARAM_LIST)) + print("String {Param} {Param}".format(Param=PARAM_1)) + print("{Param_1} {Param_2}".format(**PARAM_DICT)) + print("{Param_1} {Param_2} {Param_3}".format(**PARAM_DICT_SINGLE, **PARAM_DICT)) + print("{Param_1} {Param_2} {Param_3}".format(Param_1=PARAM_1, **PARAM_DICT)) + print("{Param_1} {Param_2}".format(**PARAM_DICT)) + print("{Param_1} {Param_2}".format(**return_dict())) + print("%(Param_1)s %(Param_2)s" % PARAM_LIST) + print("%(Param_1)s %(Param_2)s" % PARAM_DICT) + print("%(Param_1)s %(Param_2)s" % return_dict()) + print("{a[Param_1]}{a[Param_2]}".format(a=PARAM_DICT)) + +def print_bad(): + print("String %f" % PARAM_1) # [consider-using-f-string] + print("String {}".format(PARAM_1)) # [consider-using-f-string] + print("String {Param_1}".format(Param_1=PARAM_1)) # [consider-using-f-string] + print("{} {}".format(PARAM_1, PARAM_2)) # [consider-using-f-string] + print("{Par_1}{Par_2}".format(Par_1=PARAM_1, Par_2=PARAM_2)) # [consider-using-f-string] + print("{Param_1}".format(*PARAM_LIST_SINGLE)) # [consider-using-f-string] + print("{Param_1}".format(**PARAM_DICT_SINGLE)) # [consider-using-f-string] + print("String %s" % (PARAM_1)) # [consider-using-f-string] + print("String %s %s" % (PARAM_1, PARAM_2)) # [consider-using-f-string] + print("String %s" % (PARAM_LIST_SINGLE)) # [consider-using-f-string] + + +def statement_good(): + "String {}, {} or {}".format(*PARAM_LIST) + "String {}, {}, {} or {}".format(*PARAM_LIST_SINGLE, *PARAM_LIST) + "String {Param}, {}, {} or {}".format(Param=PARAM_1, *PARAM_LIST) + "String {Param} {Param}".format(Param=PARAM_1) + "{Param_1} {Param_2}".format(**PARAM_DICT) + "{Param_1} {Param_2} {Param_3}".format(**PARAM_DICT_SINGLE, **PARAM_DICT) + "{Param_1} {Param_2} {Param_3}".format(Param_1=PARAM_1, **PARAM_DICT) + "{Param_1} {Param_2}".format(**PARAM_DICT) + "{Param_1} {Param_2}".format(**return_dict()) + "%(Param_1)s %(Param_2)s" % PARAM_LIST + "%(Param_1)s %(Param_2)s" % PARAM_DICT + "%(Param_1)s %(Param_2)s" % return_dict() + "{a[Param_1]}{a[Param_2]}".format(a=PARAM_DICT) + +def statement_bad(): + "String %f" % PARAM_1 # [consider-using-f-string] + "String {}".format(PARAM_1) # [consider-using-f-string] + "String {Param_1}".format(Param_1=PARAM_1) # [consider-using-f-string] + "{} {}".format(PARAM_1, PARAM_2) # [consider-using-f-string] + "{Par_1}{Par_2}".format(Par_1=PARAM_1, Par_2=PARAM_2) # [consider-using-f-string] + "{Param_1}".format(*PARAM_LIST_SINGLE) # [consider-using-f-string] + "{Param_1}".format(**PARAM_DICT_SINGLE) # [consider-using-f-string] + "String %s" % (PARAM_1) # [consider-using-f-string] + "String %s %s" % (PARAM_1, PARAM_2) # [consider-using-f-string] + "String %s" % (PARAM_LIST_SINGLE) # [consider-using-f-string] + + +def assignment_good(): + A = "String {}, {} or {}".format(*PARAM_LIST) + B = "String {}, {}, {} or {}".format(*PARAM_LIST_SINGLE, *PARAM_LIST) + C = "String {Param}, {}, {} or {}".format(Param=PARAM_1, *PARAM_LIST) + D = "String {Param} {Param}".format(Param=PARAM_1) + E = "{Param_1} {Param_2}".format(**PARAM_DICT) + F = "{Param_1} {Param_2} {Param_3}".format(**PARAM_DICT_SINGLE, **PARAM_DICT) + G = "{Param_1} {Param_2} {Param_3}".format(Param_1=PARAM_1, **PARAM_DICT) + H = "{Param_1} {Param_2}".format(**PARAM_DICT) + I = "{Param_1} {Param_2}".format(**return_dict()) + J = "%(Param_1)s %(Param_2)s" % PARAM_LIST + K = "%(Param_1)s %(Param_2)s" % PARAM_DICT + L = "%(Param_1)s %(Param_2)s" % return_dict() + M = "{a[Param_1]}{a[Param_2]}".format(a=PARAM_DICT) + N = "{Param}".format + + +def assignment_bad(): + a = "String %f" % PARAM_1 # [consider-using-f-string] + b = "String {}".format(PARAM_1) # [consider-using-f-string] + c = "String {Param_1}".format(Param_1=PARAM_1) # [consider-using-f-string] + d = "{} {}".format(PARAM_1, PARAM_2) # [consider-using-f-string] + e = "{Par_1}{Par_2}".format(Par_1=PARAM_1, Par_2=PARAM_2) # [consider-using-f-string] + f = "{Param_1}".format(*PARAM_LIST_SINGLE) # [consider-using-f-string] + g = "{Param_1}".format(**PARAM_DICT_SINGLE) # [consider-using-f-string] + h = "String %s" % (PARAM_1) # [consider-using-f-string] + i = "String %s %s" % (PARAM_1, PARAM_2) # [consider-using-f-string] + j = "String %s" % (PARAM_LIST_SINGLE) # [consider-using-f-string] diff --git a/tests/functional/c/consider/consider_using_f_string.txt b/tests/functional/c/consider/consider_using_f_string.txt new file mode 100644 index 000000000..05288cde0 --- /dev/null +++ b/tests/functional/c/consider/consider_using_f_string.txt @@ -0,0 +1,30 @@ +consider-using-f-string:40:10::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:41:10::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:42:10::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:43:10::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:44:10::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:45:10::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:46:10::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:47:10::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:48:10::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:49:10::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:68:4::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:69:4::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:70:4::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:71:4::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:72:4::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:73:4::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:74:4::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:75:4::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:76:4::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:77:4::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:98:8::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:99:8::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:100:8::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:101:8::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:102:8::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:103:8::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:104:8::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:105:8::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:106:8::"Formatting a regular string which could be a f-string":HIGH +consider-using-f-string:107:8::"Formatting a regular string which could be a f-string":HIGH diff --git a/tests/functional/d/docstrings.py b/tests/functional/d/docstrings.py index e466a000f..e9d137a7f 100644 --- a/tests/functional/d/docstrings.py +++ b/tests/functional/d/docstrings.py @@ -1,4 +1,4 @@ -# pylint: disable=no-self-use, useless-object-inheritance, unnecessary-pass
+# pylint: disable=no-self-use, useless-object-inheritance, unnecessary-pass, consider-using-f-string
# -1: [missing-module-docstring]
from __future__ import print_function
diff --git a/tests/functional/d/duplicate_string_formatting_argument.py b/tests/functional/d/duplicate_string_formatting_argument.py index b012f98e0..572f20eca 100644 --- a/tests/functional/d/duplicate_string_formatting_argument.py +++ b/tests/functional/d/duplicate_string_formatting_argument.py @@ -1,4 +1,4 @@ -# pylint: disable=missing-docstring +# pylint: disable=missing-docstring, consider-using-f-string NAME = 42 OTHER_NAME = 24 diff --git a/tests/functional/l/logging_format_interpolation.py b/tests/functional/l/logging_format_interpolation.py index 8defa940b..782e3834b 100644 --- a/tests/functional/l/logging_format_interpolation.py +++ b/tests/functional/l/logging_format_interpolation.py @@ -1,5 +1,5 @@ # pylint: disable=no-member, no-absolute-import, import-error,line-too-long -# pylint: disable=invalid-name,missing-docstring,wrong-import-order,wrong-import-position +# pylint: disable=invalid-name,missing-docstring,wrong-import-order,wrong-import-position, consider-using-f-string try: import __builtin__ as builtins except ImportError: diff --git a/tests/functional/l/logging_not_lazy.py b/tests/functional/l/logging_not_lazy.py index c0634327f..28aa3b783 100644 --- a/tests/functional/l/logging_not_lazy.py +++ b/tests/functional/l/logging_not_lazy.py @@ -1,4 +1,4 @@ -# pylint: disable=missing-docstring,no-member,deprecated-method,invalid-name +# pylint: disable=missing-docstring,no-member,deprecated-method,invalid-name, consider-using-f-string # Muck up the names in an effort to confuse... import logging as renamed_logging @@ -14,7 +14,6 @@ renamed_logging.log(renamed_logging.INFO, "Var: " + var) # [logging-not-lazy] renamed_logging.warn('%s' + ' the rest of a single string') # [logging-not-lazy] renamed_logging.log(renamed_logging.INFO, var_name + var) # [logging-not-lazy] -var_name = 'Var:' # Statements that should not be flagged: renamed_logging.warn('%s, %s', 4, 5) renamed_logging.log(renamed_logging.INFO, 'msg: %s', 'Run!') diff --git a/tests/functional/l/logging_not_lazy_with_logger.py b/tests/functional/l/logging_not_lazy_with_logger.py index ef2221f23..69d0e9bd4 100644 --- a/tests/functional/l/logging_not_lazy_with_logger.py +++ b/tests/functional/l/logging_not_lazy_with_logger.py @@ -1,4 +1,5 @@ """Logging warnings using a logger class.""" +# pylint: disable=consider-using-f-string from __future__ import absolute_import import logging diff --git a/tests/functional/l/logging_not_lazy_with_logger.txt b/tests/functional/l/logging_not_lazy_with_logger.txt index 31fe45c6d..43d9c1620 100644 --- a/tests/functional/l/logging_not_lazy_with_logger.txt +++ b/tests/functional/l/logging_not_lazy_with_logger.txt @@ -1,4 +1,4 @@ -logging-not-lazy:8:0::Use lazy % formatting in logging functions logging-not-lazy:9:0::Use lazy % formatting in logging functions -logging-not-lazy:11:0::Use lazy % formatting in logging functions -logging-not-lazy:13:0::Use lazy % formatting in logging functions +logging-not-lazy:10:0::Use lazy % formatting in logging functions +logging-not-lazy:12:0::Use lazy % formatting in logging functions +logging-not-lazy:14:0::Use lazy % formatting in logging functions diff --git a/tests/functional/m/misplaced_format_function.py b/tests/functional/m/misplaced_format_function.py index 7bcabfc03..679f59858 100644 --- a/tests/functional/m/misplaced_format_function.py +++ b/tests/functional/m/misplaced_format_function.py @@ -1,7 +1,7 @@ """Test that format function is used only with string.""" # pylint: disable=invalid-name, pointless-string-statement, line-too-long, no-member, disallowed-name, undefined-variable, missing-docstring, too-few-public-methods - +# pylint: disable=consider-using-f-string print('value: {}').format(123) # [misplaced-format-function] print("value: {}").format(123) # [misplaced-format-function] print('value: {}'.format(123)) diff --git a/tests/functional/n/new_style_class_py_30.py b/tests/functional/n/new_style_class_py_30.py index fd78c5590..7a2a59a17 100644 --- a/tests/functional/n/new_style_class_py_30.py +++ b/tests/functional/n/new_style_class_py_30.py @@ -14,8 +14,7 @@ class File(file): # pylint: disable=file-builtin,undefined-variable self.verbose = verbose super(File, self).__init__(name, mode, buffering) # [super-with-arguments] if self.verbose: - print("File %s is opened. The mode is: %s" % (self.name, - self.mode)) + print(f"File {self.name} is opened. The mode is: {self.mode}") def write(self, a_string): """ Write a string to the file.""" @@ -30,6 +29,6 @@ class File(file): # pylint: disable=file-builtin,undefined-variable def close(self): """Close the file.""" if self.verbose: - print("Closing file %s" % self.name) + print(f"Closing file {self.name}") super(File, self).close() # [super-with-arguments] self.was_modified = False diff --git a/tests/functional/n/new_style_class_py_30.txt b/tests/functional/n/new_style_class_py_30.txt index 1f3434378..b86cc2b54 100644 --- a/tests/functional/n/new_style_class_py_30.txt +++ b/tests/functional/n/new_style_class_py_30.txt @@ -1,4 +1,4 @@ super-with-arguments:15:8:File.__init__:Consider using Python 3 style super() without arguments -super-with-arguments:22:8:File.write:Consider using Python 3 style super() without arguments -super-with-arguments:27:8:File.writelines:Consider using Python 3 style super() without arguments -super-with-arguments:34:8:File.close:Consider using Python 3 style super() without arguments +super-with-arguments:21:8:File.write:Consider using Python 3 style super() without arguments +super-with-arguments:26:8:File.writelines:Consider using Python 3 style super() without arguments +super-with-arguments:33:8:File.close:Consider using Python 3 style super() without arguments diff --git a/tests/functional/r/raise/raising_format_tuple.py b/tests/functional/r/raise/raising_format_tuple.py index 12e763d47..268d834f4 100644 --- a/tests/functional/r/raise/raising_format_tuple.py +++ b/tests/functional/r/raise/raising_format_tuple.py @@ -4,7 +4,7 @@ contains a percent sign, thus suggesting a % string formatting was intended instead. The same holds for a string containing {...} suggesting str.format() was intended. ''' -# pylint: disable=redundant-u-string-prefix +# pylint: disable=redundant-u-string-prefix, consider-using-f-string def bad_percent(arg): '''Raising a percent-formatted string and an argument''' diff --git a/tests/functional/r/renamed_import_logging_not_lazy.py b/tests/functional/r/renamed_import_logging_not_lazy.py index e4ca1c338..5eb23bf95 100644 --- a/tests/functional/r/renamed_import_logging_not_lazy.py +++ b/tests/functional/r/renamed_import_logging_not_lazy.py @@ -1,4 +1,4 @@ -# pylint: disable=missing-docstring, too-few-public-methods, no-member +# pylint: disable=missing-docstring, too-few-public-methods, no-member, consider-using-f-string from __future__ import absolute_import # Muck up the names in an effort to confuse... diff --git a/tests/functional/s/slots_checks.py b/tests/functional/s/slots_checks.py index 9cdcf97d1..c8cfd04e0 100644 --- a/tests/functional/s/slots_checks.py +++ b/tests/functional/s/slots_checks.py @@ -25,7 +25,7 @@ class ThirdGood(object): __slots__ = ['a'] class FourthGood(object): - __slots__ = ('a%s' % i for i in range(10)) + __slots__ = (f'a{i}' for i in range(10)) class FifthGood(object): __slots__ = deque(["a", "b", "c"]) diff --git a/tests/functional/s/string/string_formatting.py b/tests/functional/s/string/string_formatting.py index 23067601a..6d2b18665 100644 --- a/tests/functional/s/string/string_formatting.py +++ b/tests/functional/s/string/string_formatting.py @@ -1,7 +1,7 @@ """Test for Python 3 string formatting error"""
# pylint: disable=too-few-public-methods, import-error, unused-argument, line-too-long, no-absolute-import,
-# pylint: disable=useless-object-inheritance
+# pylint: disable=useless-object-inheritance, consider-using-f-string
import os
import sys
import logging
diff --git a/tests/functional/s/string/string_formatting_error.py b/tests/functional/s/string/string_formatting_error.py index 1f9347a15..d48b47d3f 100644 --- a/tests/functional/s/string/string_formatting_error.py +++ b/tests/functional/s/string/string_formatting_error.py @@ -1,5 +1,5 @@ """test string format error""" -# pylint: disable=print-statement,unsupported-binary-operation,line-too-long +# pylint: disable=print-statement,unsupported-binary-operation,line-too-long, consider-using-f-string from __future__ import print_function PARG_1 = PARG_2 = PARG_3 = 1 diff --git a/tests/functional/s/string/string_formatting_failed_inference.py b/tests/functional/s/string/string_formatting_failed_inference.py index c5add58c8..e47ca5baa 100644 --- a/tests/functional/s/string/string_formatting_failed_inference.py +++ b/tests/functional/s/string/string_formatting_failed_inference.py @@ -1,4 +1,4 @@ """ Testing string format with a failed inference. This should not crash. """ -# pylint: disable=using-constant-test +# pylint: disable=using-constant-test, consider-using-f-string import collections "{dict[0]}".format(dict=collections.defaultdict(int)) diff --git a/tests/functional/s/string/string_formatting_failed_inference_py35.py b/tests/functional/s/string/string_formatting_failed_inference_py35.py index a511ff80e..f4d3ef34e 100644 --- a/tests/functional/s/string/string_formatting_failed_inference_py35.py +++ b/tests/functional/s/string/string_formatting_failed_inference_py35.py @@ -1,5 +1,5 @@ """ Testing string format with a failed inference. This should not crash. """ -# pylint: disable=using-constant-test +# pylint: disable=using-constant-test, consider-using-f-string import collections "{dict[0]}".format(dict=collections.defaultdict(int)) diff --git a/tests/functional/s/string/string_formatting_py3.py b/tests/functional/s/string/string_formatting_py3.py index 3cdd60eb2..6ab4d8c91 100644 --- a/tests/functional/s/string/string_formatting_py3.py +++ b/tests/functional/s/string/string_formatting_py3.py @@ -1,4 +1,4 @@ -# pylint: disable=missing-docstring,import-error +# pylint: disable=missing-docstring,import-error, consider-using-f-string def issue_957_good(): diff --git a/tests/functional/t/too/too_many_return_statements.py b/tests/functional/t/too/too_many_return_statements.py index 66a85da40..b9f35a2ad 100644 --- a/tests/functional/t/too/too_many_return_statements.py +++ b/tests/functional/t/too/too_many_return_statements.py @@ -27,7 +27,7 @@ def stupid_function(arg): # [too-many-return-statements] def many_yield(text): """Not a problem""" if text: - yield " line 1: %s\n" % text + yield f" line 1: {text}\n" yield " line 2\n" yield " line 3\n" yield " line 4\n" diff --git a/tests/functional/u/undefined/undefined_loop_variable.py b/tests/functional/u/undefined/undefined_loop_variable.py index 3df17f7d1..956773e31 100644 --- a/tests/functional/u/undefined/undefined_loop_variable.py +++ b/tests/functional/u/undefined/undefined_loop_variable.py @@ -1,4 +1,4 @@ -# pylint: disable=missing-docstring,redefined-builtin +# pylint: disable=missing-docstring,redefined-builtin, consider-using-f-string def do_stuff(some_random_list): for var in some_random_list: diff --git a/tests/functional/u/unused/unused_argument.py b/tests/functional/u/unused/unused_argument.py index 707eb25af..c5f534259 100644 --- a/tests/functional/u/unused/unused_argument.py +++ b/tests/functional/u/unused/unused_argument.py @@ -72,7 +72,7 @@ class AAAA(object): def using_inner_function(self, etype, size=1): """return a fake result set for a particular entity type""" - rset = AAAA([('A',)]*size, '%s X' % etype, + rset = AAAA([('A',)]*size, f'{etype} X', description=[(etype,)]*size) def inner(row, col=0, etype=etype, req=self, rset=rset): """inner using all its argument""" diff --git a/tests/functional/u/use/used_before_assignement.py b/tests/functional/u/use/used_before_assignement.py index c975b1dc2..285f3d180 100644 --- a/tests/functional/u/use/used_before_assignement.py +++ b/tests/functional/u/use/used_before_assignement.py @@ -1,5 +1,5 @@ """pylint doesn't see the NameError in this module""" - +#pylint: disable=consider-using-f-string __revision__ = None MSG = "hello %s" % MSG # [used-before-assignment] diff --git a/tests/functional/u/use/used_before_assignment_issue853.py b/tests/functional/u/use/used_before_assignment_issue853.py index f8b412252..7da9fdd50 100644 --- a/tests/functional/u/use/used_before_assignment_issue853.py +++ b/tests/functional/u/use/used_before_assignment_issue853.py @@ -1,4 +1,4 @@ -# pylint: disable=missing-docstring,bare-except,pointless-statement,superfluous-parens +# pylint: disable=missing-docstring,bare-except,pointless-statement,superfluous-parens, consider-using-f-string def strangeproblem(): try: for _ in range(0, 4): diff --git a/tests/input/func_w0401_package/thing2.py b/tests/input/func_w0401_package/thing2.py index 66d331677..80bec1dd8 100644 --- a/tests/input/func_w0401_package/thing2.py +++ b/tests/input/func_w0401_package/thing2.py @@ -4,4 +4,4 @@ from .all_the_things import THING1 __revision__ = None THING2 = "I am thing2" -THING1_PLUS_THING2 = "%s, plus %s" % (THING1, THING2) +THING1_PLUS_THING2 = f"{THING1}, plus {THING2}" diff --git a/tests/profile/test_profile_against_externals.py b/tests/profile/test_profile_against_externals.py index dd3e72708..20fa55d4a 100644 --- a/tests/profile/test_profile_against_externals.py +++ b/tests/profile/test_profile_against_externals.py @@ -18,7 +18,7 @@ from pylint.testutils import GenericTestReporter as Reporter def _get_py_files(scanpath): - assert os.path.exists(scanpath), "Dir not found %s" % scanpath + assert os.path.exists(scanpath), f"Dir not found {scanpath}" filepaths = [] for dirpath, dirnames, filenames in os.walk(scanpath): @@ -46,12 +46,11 @@ def test_run(tmp_path, name, git_repo): checkoutdir.mkdir() os.system(f"git clone --depth=1 {git_repo} {checkoutdir}") filepaths = _get_py_files(scanpath=str(checkoutdir)) - print("Have %d files" % len(filepaths)) + print(f"Have {len(filepaths)} files") runner = Run(filepaths, reporter=Reporter(), do_exit=False) print( - "Had %d files with %d messages" - % (len(filepaths), len(runner.linter.reporter.messages)) + f"Had {len(filepaths)} files with {len(runner.linter.reporter.messages)} messages" ) pprint.pprint(runner.linter.reporter.messages) diff --git a/tests/pyreverse/test_utils.py b/tests/pyreverse/test_utils.py index 67db132b9..e11002035 100644 --- a/tests/pyreverse/test_utils.py +++ b/tests/pyreverse/test_utils.py @@ -66,7 +66,7 @@ def test_get_annotation_annassign(assign, label): ) def test_get_annotation_assignattr(init_method, label): """AssignAttr""" - assign = rf""" + assign = fr""" class A: {init_method} """ diff --git a/tests/test_check_parallel.py b/tests/test_check_parallel.py index 79e373ee0..6e94ed74c 100644 --- a/tests/test_check_parallel.py +++ b/tests/test_check_parallel.py @@ -30,9 +30,9 @@ def _gen_file_data(idx=0): os.path.join(os.path.dirname(__file__), "input", "similar1") ) file_data = ( - "--test-file_data-name-%d--" % idx, + f"--test-file_data-name-{idx}--", filepath, - "--test-file_data-modname-%d--" % idx, + f"--test-file_data-modname-{idx}--", ) return file_data @@ -419,6 +419,7 @@ class TestCheckParallel: # with the number of files. expected_stats = { "by_module": { + # pylint: disable-next=consider-using-f-string "--test-file_data-name-%d--" % idx: { "convention": 0, diff --git a/tests/test_epylint.py b/tests/test_epylint.py index 525627166..796b1ca87 100644 --- a/tests/test_epylint.py +++ b/tests/test_epylint.py @@ -20,6 +20,7 @@ def example_path(tmp_path): def test_epylint_good_command(example_path): out, err = lint.py_run( + # pylint: disable-next=consider-using-f-string "%s -E --disable=E1111 --msg-template '{category} {module} {obj} {line} {column} {msg}'" % example_path, return_std=True, @@ -37,6 +38,7 @@ def test_epylint_good_command(example_path): def test_epylint_strange_command(example_path): out, err = lint.py_run( + # pylint: disable-next=consider-using-f-string "%s -E --disable=E1111 --msg-template={category} {module} {obj} {line} {column} {msg}" % example_path, return_std=True, diff --git a/tests/test_func.py b/tests/test_func.py index 0e093458a..b57dc7d18 100644 --- a/tests/test_func.py +++ b/tests/test_func.py @@ -38,7 +38,7 @@ INFO_TEST_RGX = re.compile(r"^func_i\d\d\d\d$") def exception_str(self, ex): # pylint: disable=unused-argument """function used to replace default __str__ method of exception instances""" - return "in {}\n:: {}".format(ex.file, ", ".join(ex.args)) + return f"in {ex.file}\n:: {', '.join(ex.args)}" class LintTestUsingModule: @@ -55,8 +55,7 @@ class LintTestUsingModule: # pylint: disable=not-an-iterable; can't handle boolean checks for now if self.depends: tocheck += [ - self.package + ".%s" % name.replace(".py", "") - for name, _ in self.depends + self.package + f".{name.replace('.py', '')}" for name, _ in self.depends ] self._test(tocheck) diff --git a/tests/test_self.py b/tests/test_self.py index a8877d6d3..519899021 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -234,7 +234,7 @@ class TestRunTC: output = out.getvalue() # Get rid of the pesky messages that pylint emits if the # configuration file is not found. - pattern = rf"\[{MAIN_CHECKER_NAME.upper()}" + pattern = fr"\[{MAIN_CHECKER_NAME.upper()}" master = re.search(pattern, output) assert master is not None, f"{pattern} not found in {output}" out = StringIO(output[master.start() :]) @@ -298,7 +298,7 @@ class TestRunTC: [ "--py3k", "--disable=no-absolute-import", - "-j %d" % jobs, + f"-j {int(jobs)}", join(HERE, "input", "no_absolute_import.py"), ], code=expected_ret_code, @@ -484,7 +484,7 @@ class TestRunTC: config_path = join(HERE, "regrtest_data", ".pylintrc") expected = "Your code has been rated at 10.00/10" self._test_output( - [path, "--rcfile=%s" % config_path, "-rn"], expected_output=expected + [path, f"--rcfile={config_path}", "-rn"], expected_output=expected ) def test_pylintrc_plugin_duplicate_options(self): @@ -500,7 +500,7 @@ class TestRunTC: ) self._test_output( [ - "--rcfile=%s" % config_path, + f"--rcfile={config_path}", "--help-msg=dummy-message-01,dummy-message-02", ], expected_output=expected, @@ -510,7 +510,7 @@ class TestRunTC: "# Dummy option 2\ndummy_option_2=dummy value 2" ) self._test_output( - ["--rcfile=%s" % config_path, "--generate-rcfile"], expected_output=expected + [f"--rcfile={config_path}", "--generate-rcfile"], expected_output=expected ) sys.path.remove(dummy_plugin_path) @@ -526,7 +526,7 @@ class TestRunTC: """ ) self._test_output( - [path, "--rcfile=%s" % config_path, "-rn"], expected_output=expected + [path, f"--rcfile={config_path}", "-rn"], expected_output=expected ) def test_no_crash_with_formatting_regex_defaults(self): |