예제 #1
0
def _fix_add_metaclass(i: int, tokens: List[Token]) -> None:
    j = find_open_paren(tokens, i)
    func_args, end = parse_call_args(tokens, j)
    metaclass = f'metaclass={arg_str(tokens, *func_args[0])}'
    # insert `metaclass={args[0]}` into `class:`
    # search forward for the `class` token
    j = i + 1
    while tokens[j].src != 'class':
        j += 1
    class_token = j
    # then search forward for a `:` token, not inside a brace
    j = find_block_start(tokens, j)
    last_paren = -1
    for k in range(class_token, j):
        if tokens[k].src == ')':
            last_paren = k

    if last_paren == -1:
        tokens.insert(j, Token('CODE', f'({metaclass})'))
    else:
        insert = last_paren - 1
        while tokens[insert].name in NON_CODING_TOKENS:
            insert -= 1
        if tokens[insert].src == '(':  # no bases
            src = metaclass
        elif tokens[insert].src != ',':
            src = f', {metaclass}'
        else:
            src = f' {metaclass},'
        tokens.insert(insert + 1, Token('CODE', src))
    remove_decorator(i, tokens)
예제 #2
0
def _use_capture_output(
    i: int,
    tokens: List[Token],
    *,
    stdout_arg_idx: int,
    stderr_arg_idx: int,
) -> None:
    j = find_open_paren(tokens, i)
    func_args, _ = parse_call_args(tokens, j)
    if stdout_arg_idx < stderr_arg_idx:
        delete_argument(stderr_arg_idx, tokens, func_args)
        replace_argument(
            stdout_arg_idx,
            tokens,
            func_args,
            new='capture_output=True',
        )
    else:
        replace_argument(
            stdout_arg_idx,
            tokens,
            func_args,
            new='capture_output=True',
        )
        delete_argument(stderr_arg_idx, tokens, func_args)
예제 #3
0
def _fix_native_str(i: int, tokens: List[Token]) -> None:
    j = find_open_paren(tokens, i)
    func_args, end = parse_call_args(tokens, j)
    if any(tok.name == 'NL' for tok in tokens[i:end]):
        return
    if func_args:
        replace_call(tokens, i, end, func_args, '{args[0]}')
    else:
        tokens[i:end] = [tokens[i]._replace(name='STRING', src="''")]
예제 #4
0
def _replace_universal_newlines_with_text(
    i: int,
    tokens: List[Token],
    *,
    arg_idx: int,
) -> None:
    j = find_open_paren(tokens, i)
    func_args, _ = parse_call_args(tokens, j)
    for i in range(*func_args[arg_idx]):
        if tokens[i].src == 'universal_newlines':
            tokens[i] = tokens[i]._replace(src='text')
            break
    else:
        raise AssertionError('`universal_newlines` argument not found')
예제 #5
0
def _fix_with_metaclass(i: int, tokens: List[Token]) -> None:
    j = find_open_paren(tokens, i)
    func_args, end = parse_call_args(tokens, j)
    if len(func_args) == 1:
        tmpl = 'metaclass={args[0]}'
    elif len(func_args) == 2:
        base = arg_str(tokens, *func_args[1])
        if base == 'object':
            tmpl = 'metaclass={args[0]}'
        else:
            tmpl = '{rest}, metaclass={args[0]}'
    else:
        tmpl = '{rest}, metaclass={args[0]}'
    replace_call(tokens, i, end, func_args, tmpl)
예제 #6
0
def _fix_open_mode(i: int, tokens: List[Token]) -> None:
    j = find_open_paren(tokens, i)
    func_args, end = parse_call_args(tokens, j)
    mode = tokens_to_src(tokens[slice(*func_args[1])])
    mode_stripped = mode.strip().strip('"\'')
    if mode_stripped in U_MODE_REMOVE:
        del tokens[func_args[0][1]:func_args[1][1]]
    elif mode_stripped in U_MODE_REPLACE_R:
        new_mode = mode.replace('U', 'r')
        tokens[slice(*func_args[1])] = [Token('SRC', new_mode)]
    elif mode_stripped in U_MODE_REMOVE_U:
        new_mode = mode.replace('U', '')
        tokens[slice(*func_args[1])] = [Token('SRC', new_mode)]
    else:
        raise AssertionError(f'unreachable: {mode!r}')
예제 #7
0
def _fix_open_mode(i: int, tokens: List[Token], *, arg_idx: int) -> None:
    j = find_open_paren(tokens, i)
    func_args, end = parse_call_args(tokens, j)
    mode = tokens_to_src(tokens[slice(*func_args[arg_idx])])
    mode_stripped = mode.split('=')[-1]
    mode_stripped = ast.literal_eval(mode_stripped.strip())
    if mode_stripped in U_MODE_REMOVE:
        delete_argument(arg_idx, tokens, func_args)
    elif mode_stripped in U_MODE_REPLACE_R:
        new_mode = mode.replace('U', 'r')
        tokens[slice(*func_args[arg_idx])] = [Token('SRC', new_mode)]
    elif mode_stripped in U_MODE_REMOVE_U:
        new_mode = mode.replace('U', '')
        tokens[slice(*func_args[arg_idx])] = [Token('SRC', new_mode)]
    else:
        raise AssertionError(f'unreachable: {mode!r}')
예제 #8
0
def _fix_oserror_except(
    i: int,
    tokens: List[Token],
    *,
    from_imports: Dict[str, Set[str]],
) -> None:
    # find all the arg strs in the tuple
    except_index = i
    while tokens[except_index].src != 'except':
        except_index -= 1
    start = find_open_paren(tokens, except_index)
    func_args, end = parse_call_args(tokens, start)

    # save the exceptions and remove the block
    arg_strs = [arg_str(tokens, *arg) for arg in func_args]
    del tokens[start:end]

    # rewrite the block without dupes
    args = []
    for arg in arg_strs:
        left, part, right = arg.partition('.')
        if left in ERROR_MODULES and part == '.' and right == 'error':
            args.append('OSError')
        elif left in ERROR_NAMES and part == right == '':
            args.append('OSError')
        elif (left == 'error' and part == right == ''
              and any('error' in from_imports[mod] for mod in ERROR_MODULES)):
            args.append('OSError')
        else:
            args.append(arg)

    unique_args = tuple(dict.fromkeys(args))

    if len(unique_args) > 1:
        joined = '({})'.format(', '.join(unique_args))
    elif tokens[start - 1].name != 'UNIMPORTANT_WS':
        joined = ' {}'.format(unique_args[0])
    else:
        joined = unique_args[0]

    new = Token('CODE', joined)
    tokens.insert(start, new)
예제 #9
0
def _fix_py36_plus(contents_text: str) -> str:
    try:
        ast_obj = ast_parse(contents_text)
    except SyntaxError:
        return contents_text

    visitor = FindPy36Plus()
    visitor.visit(ast_obj)

    if not any((
            visitor.fstrings,
            visitor.named_tuples,
            visitor.dict_typed_dicts,
            visitor.kw_typed_dicts,
    )):
        return contents_text

    try:
        tokens = src_to_tokens(contents_text)
    except tokenize.TokenError:  # pragma: no cover (bpo-2180)
        return contents_text
    for i, token in reversed_enumerate(tokens):
        if token.offset in visitor.fstrings:
            # TODO: handle \N escape sequences
            if r'\N' in token.src:
                continue

            paren = i + 3
            if tokens_to_src(tokens[i + 1:paren + 1]) != '.format(':
                continue

            args, end = parse_call_args(tokens, paren)
            # if it spans more than one line, bail
            if tokens[end - 1].line != token.line:
                continue

            args_src = tokens_to_src(tokens[paren:end])
            if '\\' in args_src or '"' in args_src or "'" in args_src:
                continue

            tokens[i] = token._replace(
                src=_to_fstring(token.src, tokens, args),
            )
            del tokens[i + 1:end]
        elif token.offset in visitor.named_tuples and token.name == 'NAME':
            call = visitor.named_tuples[token.offset]
            types: Dict[str, ast.expr] = {
                tup.elts[0].s: tup.elts[1]  # type: ignore  # (checked above)
                for tup in call.args[1].elts  # type: ignore  # (checked above)
            }
            end, attrs = _typed_class_replacement(tokens, i, call, types)
            src = f'class {tokens[i].src}({_unparse(call.func)}):\n{attrs}'
            tokens[i:end] = [Token('CODE', src)]
        elif token.offset in visitor.kw_typed_dicts and token.name == 'NAME':
            call = visitor.kw_typed_dicts[token.offset]
            types = {
                arg.arg: arg.value  # type: ignore  # (checked above)
                for arg in call.keywords
            }
            end, attrs = _typed_class_replacement(tokens, i, call, types)
            src = f'class {tokens[i].src}({_unparse(call.func)}):\n{attrs}'
            tokens[i:end] = [Token('CODE', src)]
        elif token.offset in visitor.dict_typed_dicts and token.name == 'NAME':
            call = visitor.dict_typed_dicts[token.offset]
            types = {
                k.s: v  # type: ignore  # (checked above)
                for k, v in zip(
                    call.args[1].keys,  # type: ignore  # (checked above)
                    call.args[1].values,  # type: ignore  # (checked above)
                )
            }
            if call.keywords:
                total = call.keywords[0].value.value  # type: ignore # (checked above)  # noqa: E501
                end, attrs = _typed_class_replacement(tokens, i, call, types)
                src = (
                    f'class {tokens[i].src}('
                    f'{_unparse(call.func)}, total={total}'
                    f'):\n'
                    f'{attrs}'
                )
                tokens[i:end] = [Token('CODE', src)]
            else:
                end, attrs = _typed_class_replacement(tokens, i, call, types)
                src = f'class {tokens[i].src}({_unparse(call.func)}):\n{attrs}'
                tokens[i:end] = [Token('CODE', src)]

    return tokens_to_src(tokens)
예제 #10
0
def _fix_six_b(i: int, tokens: List[Token]) -> None:
    j = find_open_paren(tokens, i)
    if (tokens[j + 1].name == 'STRING' and is_ascii(tokens[j + 1].src)
            and tokens[j + 2].src == ')'):
        func_args, end = parse_call_args(tokens, j)
        replace_call(tokens, i, end, func_args, 'b{args[0]}')