|
| 1 | +from __future__ import annotations |
| 2 | + |
| 3 | +import ast |
| 4 | +import functools |
| 5 | +from typing import Iterable |
| 6 | + |
| 7 | +from tokenize_rt import NON_CODING_TOKENS |
| 8 | +from tokenize_rt import Offset |
| 9 | +from tokenize_rt import Token |
| 10 | + |
| 11 | +from pyupgrade._ast_helpers import ast_to_offset |
| 12 | +from pyupgrade._data import register |
| 13 | +from pyupgrade._data import State |
| 14 | +from pyupgrade._data import TokenFunc |
| 15 | +from pyupgrade._token_helpers import find_open_paren |
| 16 | +from pyupgrade._token_helpers import find_token |
| 17 | +from pyupgrade._token_helpers import victims |
| 18 | + |
| 19 | + |
| 20 | +def _fix_shlex_join(i: int, tokens: list[Token], *, arg: ast.expr) -> None: |
| 21 | + j = find_open_paren(tokens, i) |
| 22 | + comp_victims = victims(tokens, j, arg, gen=True) |
| 23 | + k = find_token(tokens, comp_victims.arg_index, 'in') + 1 |
| 24 | + while tokens[k].name in NON_CODING_TOKENS: |
| 25 | + k += 1 |
| 26 | + tokens[comp_victims.ends[0]:comp_victims.ends[-1] + 1] = [Token('OP', ')')] |
| 27 | + tokens[i:k] = [Token('CODE', 'shlex.join'), Token('OP', '(')] |
| 28 | + |
| 29 | + |
| 30 | +@register(ast.Call) |
| 31 | +def visit_Call( |
| 32 | + state: State, |
| 33 | + node: ast.Call, |
| 34 | + parent: ast.AST, |
| 35 | +) -> Iterable[tuple[Offset, TokenFunc]]: |
| 36 | + if state.settings.min_version < (3, 8): |
| 37 | + return |
| 38 | + |
| 39 | + if ( |
| 40 | + isinstance(node.func, ast.Attribute) and |
| 41 | + isinstance(node.func.value, ast.Constant) and |
| 42 | + isinstance(node.func.value.value, str) and |
| 43 | + node.func.attr == 'join' and |
| 44 | + not node.keywords and |
| 45 | + len(node.args) == 1 and |
| 46 | + isinstance(node.args[0], (ast.ListComp, ast.GeneratorExp)) and |
| 47 | + isinstance(node.args[0].elt, ast.Call) and |
| 48 | + isinstance(node.args[0].elt.func, ast.Attribute) and |
| 49 | + isinstance(node.args[0].elt.func.value, ast.Name) and |
| 50 | + node.args[0].elt.func.value.id == 'shlex' and |
| 51 | + node.args[0].elt.func.attr == 'quote' and |
| 52 | + not node.args[0].elt.keywords and |
| 53 | + len(node.args[0].elt.args) == 1 and |
| 54 | + isinstance(node.args[0].elt.args[0], ast.Name) and |
| 55 | + len(node.args[0].generators) == 1 and |
| 56 | + isinstance(node.args[0].generators[0].target, ast.Name) and |
| 57 | + not node.args[0].generators[0].ifs and |
| 58 | + not node.args[0].generators[0].is_async and |
| 59 | + node.args[0].elt.args[0].id == node.args[0].generators[0].target.id |
| 60 | + ): |
| 61 | + func = functools.partial(_fix_shlex_join, arg=node.args[0]) |
| 62 | + yield ast_to_offset(node), func |
0 commit comments