bowler/imr.py (185 lines of code) (raw):
#!/usr/bin/env python3
#
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
import logging
from typing import Any, List, Optional
from attr import dataclass
from fissix.fixer_util import LParen, Name
from .helpers import find_last
from .types import (
ARG_END,
ARG_LISTS,
LN,
STARS,
SYMBOL,
TOKEN,
Capture,
IMRError,
Leaf,
Node,
)
log = logging.getLogger(__name__)
@dataclass
class FunctionArgument:
name: str = ""
value: Any = None
annotation: str = ""
star: Optional[Leaf] = None
prefix: Optional[str] = None
@classmethod
def build(cls, leaf: Leaf, is_def: bool, **kwargs: Any) -> "FunctionArgument":
while leaf is not None and leaf.type not in ARG_END:
if leaf.type in (SYMBOL.star_expr, SYMBOL.argument):
return cls.build(leaf.children[0], is_def, prefix=leaf.prefix)
elif leaf.type in STARS:
kwargs["star"] = leaf.clone()
elif leaf.type == SYMBOL.tname:
kwargs["name"] = leaf.children[0].value
kwargs["annotation"] = leaf.children[-1].value
elif leaf.type == TOKEN.EQUAL:
pass
elif leaf.type == TOKEN.NAME:
if (is_def and "name" not in kwargs) or (
leaf.next_sibling and leaf.next_sibling.type == TOKEN.EQUAL
):
kwargs["name"] = leaf.value
else:
kwargs["value"] = leaf.clone()
else:
# assume everything else is a complex value
kwargs["value"] = leaf.clone()
kwargs.setdefault("prefix", leaf.prefix)
leaf = leaf.next_sibling
return FunctionArgument(**kwargs)
@classmethod
def build_list(
cls, arguments: List[Leaf], is_def: bool
) -> List["FunctionArgument"]:
result: List[FunctionArgument] = []
# empty function
if not arguments:
return result
# only care about what's on the inside
if arguments[0].type in ARG_LISTS:
leaf = arguments[0].children[0]
else:
leaf = arguments[0]
while leaf is not None:
arg = cls.build(leaf, is_def)
log.debug(f"{leaf} -> {arg}")
result.append(arg)
# consume leafs for this argument
while leaf is not None and leaf.type not in ARG_END:
log.debug(f"consuming {leaf}")
leaf = leaf.next_sibling
# assume we stopped on a comma or parenthesis
if leaf:
log.debug(f"separator {leaf}")
leaf = leaf.next_sibling
return result
def explode(self, is_def: bool, prefix: str = "") -> List[LN]:
result: List[LN] = []
prefix = self.prefix if self.prefix else prefix
if is_def:
if self.star:
self.star.prefix = prefix
result.append(self.star)
prefix = ""
if self.annotation:
result.append(
Node(
SYMBOL.tname,
[
Name(self.name, prefix=prefix),
Leaf(TOKEN.COLON, ":", prefix=""),
Name(self.annotation, prefix=" "),
],
prefix=prefix,
)
)
else:
result.append(Name(self.name, prefix=prefix))
if self.value:
space = " " if self.annotation else ""
result.append(Leaf(TOKEN.EQUAL, "=", prefix=space))
result.append(self.value)
else:
if self.star:
if self.star.type == TOKEN.STAR:
node = Node(SYMBOL.star_expr, [self.star], prefix=prefix)
elif self.star.type == TOKEN.DOUBLESTAR:
node = Node(SYMBOL.argument, [self.star], prefix=prefix)
if self.value:
self.value.prefix = ""
node.append_child(self.value)
result.append(node)
return result
if self.name:
self.value.prefix = ""
result.append(
Node(
SYMBOL.argument,
[
Name(self.name, prefix=prefix),
Leaf(TOKEN.EQUAL, value="=", prefix=""),
self.value,
],
prefix=prefix,
)
)
else:
self.value.prefix = prefix
result.append(self.value)
return result
@classmethod
def explode_list(
cls, arguments: List["FunctionArgument"], is_def: bool
) -> Optional[LN]:
nodes: List[LN] = []
prefix = ""
index = 0
for argument in arguments:
if index:
nodes.append(Leaf(TOKEN.COMMA, ",", prefix=""))
prefix = " "
result = argument.explode(is_def, prefix=prefix)
log.debug(f"{argument} -> {result}")
nodes.extend(result)
index += 1
if not nodes:
return None
if len(nodes) == 1:
return nodes[0]
elif is_def:
return Node(SYMBOL.typedargslist, nodes, prefix=nodes[0].prefix)
else:
return Node(SYMBOL.arglist, nodes, prefix=nodes[0].prefix)
@dataclass
class FunctionSpec:
name: str
arguments: List[FunctionArgument]
is_def: bool
capture: Capture
node: Node
@classmethod
def build(cls, node: Node, capture: Capture) -> "FunctionSpec":
try:
name = capture["function_name"]
is_def = "function_def" in capture
args = capture["function_arguments"]
except KeyError as e:
raise IMRError("function spec invalid") from e
arguments = FunctionArgument.build_list(args, is_def)
return FunctionSpec(name.value, arguments, is_def, capture, node)
def explode(self) -> LN:
arguments = FunctionArgument.explode_list(self.arguments, self.is_def)
rparen = find_last(self.capture["function_parameters"], TOKEN.RPAR)
rprefix = rparen.prefix if rparen else ""
if self.is_def:
parameters = Node(
SYMBOL.parameters,
[LParen(), Leaf(TOKEN.RPAR, ")", prefix=rprefix)],
prefix="",
)
else:
parameters = Node(
SYMBOL.trailer,
[LParen(), Leaf(TOKEN.RPAR, ")", prefix=rprefix)],
prefix="",
)
if arguments:
parameters.insert_child(1, arguments)
self.capture["function_parameters"].replace(parameters)
return self.node