bowler/helpers.py (170 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 List, Optional, Sequence, Union
import click
from fissix.pgen2.token import tok_name
from fissix.pytree import Leaf, Node, type_repr
from .types import LN, SYMBOL, TOKEN, Capture, Filename, FilenameMatcher
log = logging.getLogger(__name__)
INDENT_STR = ". "
def print_selector_pattern(
node: LN,
results: Capture = None,
filename: Filename = None,
first: bool = True,
):
key = ""
if results:
for k, v in results.items():
if node == v:
key = k + "="
elif isinstance(v, list) and node in v: # v is a list?
key = k + "="
if isinstance(node, Leaf):
click.echo(f"{key}{repr(node.value)} ", nl=False)
else:
click.echo(f"{key}{type_repr(node.type)} ", nl=False)
if node.children:
click.echo("< ", nl=False)
for child in node.children:
print_selector_pattern(child, results, filename, first=False)
click.echo("> ", nl=False)
if first:
click.echo()
def print_tree(
node: LN,
results: Capture = None,
filename: Filename = None,
indent: int = 0,
recurse: int = -1,
):
filename = filename or Filename("")
tab = INDENT_STR * indent
if filename and indent == 0:
click.secho(filename, fg="red", bold=True)
if isinstance(node, Leaf):
click.echo(
click.style(tab, fg="black", bold=True)
+ click.style(
f"[{tok_name[node.type]}] {repr(node.prefix)} {repr(node.value)}",
fg="yellow",
)
)
else:
click.echo(
click.style(tab, fg="black", bold=True)
+ click.style(f"[{type_repr(node.type)}] {repr(node.prefix)}", fg="blue")
)
if node.children:
if recurse:
for child in node.children:
# N.b. do not pass results here since we print them once
# at the end.
print_tree(child, indent=indent + 1, recurse=recurse - 1)
else:
click.echo(INDENT_STR * (indent + 1) + "...")
if results is None:
return
for key in results:
if key == "node":
continue
value = results[key]
if isinstance(value, (Leaf, Node)):
click.secho(f"results[{repr(key)}] =", fg="red")
print_tree(value, indent=1, recurse=1)
else:
# TODO: Improve display of multi-match here, see
# test_print_tree_captures test.
click.secho(f"results[{repr(key)}] = {value}", fg="red")
def dotted_parts(name: str) -> List[str]:
pre, dot, post = name.partition(".")
if post:
post_parts = dotted_parts(post)
else:
post_parts = []
result = []
if pre:
result.append(pre)
if pre and dot:
result.append(dot)
if post_parts:
result.extend(post_parts)
return result
def quoted_parts(name: str) -> List[str]:
return [f"'{part}'" for part in dotted_parts(name)]
def power_parts(name: str) -> List[str]:
parts = quoted_parts(name)
index = 0
while index < len(parts):
if parts[index] == "'.'":
parts.insert(index, "trailer<")
parts.insert(index + 3, ">")
index += 1
index += 1
return parts
def is_method(node: LN) -> bool:
return (
node.type == SYMBOL.funcdef
and node.parent is not None
and node.parent.type == SYMBOL.suite
and node.parent.parent is not None
and node.parent.parent.type == SYMBOL.classdef
)
def is_call_to(node: LN, func_name: str) -> bool:
"""Returns whether the node represents a call to the named function."""
return (
node.type == SYMBOL.power
and node.children[0].type == TOKEN.NAME
and node.children[0].value == func_name
)
def find_first(node: LN, target: int, recursive: bool = False) -> Optional[LN]:
queue: List[LN] = [node]
queue.extend(node.children)
while queue:
child = queue.pop(0)
if child.type == target:
return child
if recursive:
queue = child.children + queue
return None
def find_previous(node: LN, target: int, recursive: bool = False) -> Optional[LN]:
while node.prev_sibling is not None:
node = node.prev_sibling
result = find_last(node, target, recursive)
if result:
return result
return None
def find_next(node: LN, target: int, recursive: bool = False) -> Optional[LN]:
while node.next_sibling is not None:
node = node.next_sibling
result = find_first(node, target, recursive)
if result:
return result
return None
def find_last(node: LN, target: int, recursive: bool = False) -> Optional[LN]:
queue: List[LN] = []
queue.extend(reversed(node.children))
while queue:
child = queue.pop(0)
if recursive:
result = find_last(child, target, recursive)
if result:
return result
if child.type == target:
return child
return None
def get_class(node: LN) -> LN:
while node.parent is not None:
if node.type == SYMBOL.classdef:
return node
node = node.parent
raise ValueError(f"classdef node not found")
class Once:
"""Simple object that evaluates to True once, and then always False."""
def __init__(self) -> None:
self.done = False
def __bool__(self) -> bool:
if self.done:
return False
else:
self.done = True
return True
def filename_endswith(ext: Union[Sequence, str]) -> FilenameMatcher:
if isinstance(ext, str):
ext = [ext]
def inner(filename: Filename) -> bool:
return any(filename.endswith(e) for e in ext)
return inner