mirror of
https://github.com/unclebob/dryer.git
synced 2026-11-06 18:33:49 +00:00
Bring every CRAP score under 10 except the option parser.
The reader, the normalizer, the TypeScript picker, and the fingerprint printer are each a small function. parse_args stays the option switch.
This commit is contained in:
+46
-26
@@ -178,11 +178,25 @@ def _without_type_args(nodes):
|
||||
return rest, type_args
|
||||
|
||||
|
||||
def _callee_leaf(node):
|
||||
if node.type in _IDENTIFIERS:
|
||||
return [K("symbol"), _text(node)]
|
||||
return _PENDING
|
||||
|
||||
|
||||
def _named_leaf(node, head: bool):
|
||||
if node.type in _IDENTIFIERS:
|
||||
if head:
|
||||
return [K("symbol"), _text(node)]
|
||||
return K("symbol")
|
||||
if _is_literal(node):
|
||||
return K("literal")
|
||||
return _PENDING
|
||||
|
||||
|
||||
def _try_leaf(node, mode: str, head: bool):
|
||||
if mode == "callee":
|
||||
if node.type in _IDENTIFIERS:
|
||||
return [K("symbol"), _text(node)]
|
||||
return _PENDING
|
||||
return _callee_leaf(node)
|
||||
if mode == "path":
|
||||
return _PENDING
|
||||
if node.type in _SKIP:
|
||||
@@ -191,13 +205,7 @@ def _try_leaf(node, mode: str, head: bool):
|
||||
return [K("symbol"), node.type]
|
||||
if not node.is_named:
|
||||
return None
|
||||
if node.type in _IDENTIFIERS:
|
||||
if head:
|
||||
return [K("symbol"), _text(node)]
|
||||
return K("symbol")
|
||||
if _is_literal(node):
|
||||
return K("literal")
|
||||
return _PENDING
|
||||
return _named_leaf(node, head)
|
||||
|
||||
|
||||
def _call_jobs(node):
|
||||
@@ -241,13 +249,17 @@ def _path_jobs(node):
|
||||
return jobs
|
||||
|
||||
|
||||
def _callee_jobs(node):
|
||||
if node.type in _ATTRIBUTES:
|
||||
return _attr_jobs(node, True)
|
||||
if node.type in _SCOPED or node.type == "generic_type":
|
||||
return _path_jobs(node)
|
||||
return [(node, "norm", True)]
|
||||
|
||||
|
||||
def _jobs(node, mode: str, head: bool):
|
||||
if mode == "callee":
|
||||
if node.type in _ATTRIBUTES:
|
||||
return _attr_jobs(node, True)
|
||||
if node.type in _SCOPED or node.type == "generic_type":
|
||||
return _path_jobs(node)
|
||||
return [(node, "norm", True)]
|
||||
return _callee_jobs(node)
|
||||
if mode == "path":
|
||||
return _path_jobs(node)
|
||||
if node.type == "parenthesized_expression" and len(node.named_children) == 1:
|
||||
@@ -325,13 +337,26 @@ def _assemble_path(node, cache):
|
||||
return parts
|
||||
|
||||
|
||||
def _callee_value(node, cache):
|
||||
if node.type in _ATTRIBUTES:
|
||||
return _assemble_attr(node, True, cache)
|
||||
if node.type in _SCOPED or node.type == "generic_type":
|
||||
return _assemble_path(node, cache)
|
||||
return cache[(id(node), "norm", True)]
|
||||
|
||||
|
||||
def _assemble_children(node, cache):
|
||||
children = []
|
||||
for child in node.children:
|
||||
norm = cache[(id(child), "norm", False)]
|
||||
if norm is not None:
|
||||
children.append(norm)
|
||||
return [K(node.type), *children]
|
||||
|
||||
|
||||
def _assemble(node, mode: str, head: bool, cache):
|
||||
if mode == "callee":
|
||||
if node.type in _ATTRIBUTES:
|
||||
return _assemble_attr(node, True, cache)
|
||||
if node.type in _SCOPED or node.type == "generic_type":
|
||||
return _assemble_path(node, cache)
|
||||
return cache[(id(node), "norm", True)]
|
||||
return _callee_value(node, cache)
|
||||
if mode == "path":
|
||||
return _assemble_path(node, cache)
|
||||
if node.type == "parenthesized_expression" and len(node.named_children) == 1:
|
||||
@@ -341,12 +366,7 @@ def _assemble(node, mode: str, head: bool, cache):
|
||||
return _assemble_call(node, cache)
|
||||
if node.type in _ATTRIBUTES:
|
||||
return _assemble_attr(node, head, cache)
|
||||
children = []
|
||||
for child in node.children:
|
||||
norm = cache[(id(child), "norm", False)]
|
||||
if norm is not None:
|
||||
children.append(norm)
|
||||
return [K(node.type), *children]
|
||||
return _assemble_children(node, cache)
|
||||
|
||||
|
||||
def _normalize(node, head: bool = False):
|
||||
|
||||
+121
-87
@@ -17,6 +17,9 @@ from dryer.shape import K
|
||||
|
||||
_WHITESPACE = set(" \t\n\r,")
|
||||
_TERMINATORS = set("()[]{}\";'@^`~\\,#:")
|
||||
_COLLECTIONS = {"(": (")", "list"), "[": ("]", "vector"), "{": ("}", "map")}
|
||||
_WRAPPERS = {"'": "quote", "@": "deref", "`": "syntax-quote"}
|
||||
_ATOMS = set("\"\\:")
|
||||
_NUMBER = re.compile(
|
||||
r"^[+-]?(?:"
|
||||
r"\d+/\d+"
|
||||
@@ -190,25 +193,31 @@ class Reader:
|
||||
raise ReadError(self.line, "unexpected end of file")
|
||||
return self._open(stack)
|
||||
|
||||
def _deliver_coll(self, frame, produced):
|
||||
items = frame[4]
|
||||
if produced is None:
|
||||
return _NEED
|
||||
if isinstance(produced, Splice):
|
||||
items.extend(produced.items)
|
||||
else:
|
||||
items.append(produced)
|
||||
return _NEED
|
||||
|
||||
def _deliver_wrap(self, frame, produced):
|
||||
_tag, name, line = frame
|
||||
items = [Sym(name, line)]
|
||||
if produced is not None and not isinstance(produced, Splice):
|
||||
items.append(produced)
|
||||
return Coll("list", line, items=items)
|
||||
|
||||
def _deliver(self, stack: list[tuple], produced):
|
||||
frame = stack[-1]
|
||||
tag = frame[0]
|
||||
if tag == "coll":
|
||||
items = frame[4]
|
||||
if produced is None:
|
||||
return _NEED
|
||||
if isinstance(produced, Splice):
|
||||
items.extend(produced.items)
|
||||
else:
|
||||
items.append(produced)
|
||||
return _NEED
|
||||
return self._deliver_coll(frame, produced)
|
||||
stack.pop()
|
||||
if tag == "wrap":
|
||||
_tag, name, line = frame
|
||||
items = [Sym(name, line)]
|
||||
if produced is not None and not isinstance(produced, Splice):
|
||||
items.append(produced)
|
||||
return Coll("list", line, items=items)
|
||||
return self._deliver_wrap(frame, produced)
|
||||
if tag == "discard":
|
||||
return None
|
||||
if tag == "meta":
|
||||
@@ -232,97 +241,122 @@ class Reader:
|
||||
raise ReadError(self.line, "splicing reader conditional needs a collection")
|
||||
return spliced
|
||||
|
||||
def _open(self, stack: list[tuple]):
|
||||
ch = self.peek()
|
||||
line = self.line
|
||||
if ch == "(":
|
||||
self.get()
|
||||
stack.append(("coll", ")", "list", line, [], False))
|
||||
return _NEED
|
||||
if ch == "[":
|
||||
self.get()
|
||||
stack.append(("coll", "]", "vector", line, [], False))
|
||||
return _NEED
|
||||
if ch == "{":
|
||||
self.get()
|
||||
stack.append(("coll", "}", "map", line, [], False))
|
||||
return _NEED
|
||||
def _open_collection(self, stack: list[tuple], ch: str, line: int):
|
||||
self.get()
|
||||
end, kind = _COLLECTIONS[ch]
|
||||
stack.append(("coll", end, kind, line, [], False))
|
||||
return _NEED
|
||||
|
||||
def _open_atom(self, ch: str):
|
||||
if ch == '"':
|
||||
return self.read_string()
|
||||
if ch == "\\":
|
||||
return self.read_char()
|
||||
if ch == ":":
|
||||
return self.read_keyword()
|
||||
if ch == "'":
|
||||
return self.read_keyword()
|
||||
|
||||
def _open_unquote(self, stack: list[tuple], line: int):
|
||||
name = "unquote"
|
||||
if self.peek() == "@":
|
||||
self.get()
|
||||
stack.append(("wrap", "quote", line))
|
||||
return _NEED
|
||||
if ch == "@":
|
||||
self.get()
|
||||
stack.append(("wrap", "deref", line))
|
||||
return _NEED
|
||||
if ch == "`":
|
||||
self.get()
|
||||
stack.append(("wrap", "syntax-quote", line))
|
||||
name = "unquote-splicing"
|
||||
stack.append(("wrap", name, line))
|
||||
return _NEED
|
||||
|
||||
def _open_wrapper(self, stack: list[tuple], ch: str, line: int):
|
||||
self.get()
|
||||
if ch in _WRAPPERS:
|
||||
stack.append(("wrap", _WRAPPERS[ch], line))
|
||||
return _NEED
|
||||
if ch == "~":
|
||||
self.get()
|
||||
name = "unquote"
|
||||
if self.peek() == "@":
|
||||
self.get()
|
||||
name = "unquote-splicing"
|
||||
stack.append(("wrap", name, line))
|
||||
return _NEED
|
||||
if ch == "^":
|
||||
self.get()
|
||||
stack.append(("meta",))
|
||||
return _NEED
|
||||
if ch == "#":
|
||||
return self._dispatch(stack, line)
|
||||
return self._open_unquote(stack, line)
|
||||
stack.append(("meta",))
|
||||
return _NEED
|
||||
|
||||
def _open_token(self, line: int):
|
||||
token = self.read_token()
|
||||
if _NUMBER.match(token):
|
||||
return Lit(line)
|
||||
return Sym(token, line)
|
||||
|
||||
def _open(self, stack: list[tuple]):
|
||||
ch = self.peek()
|
||||
line = self.line
|
||||
if ch in _COLLECTIONS:
|
||||
return self._open_collection(stack, ch, line)
|
||||
if ch in _ATOMS:
|
||||
return self._open_atom(ch)
|
||||
if ch in _WRAPPERS or ch in "~^":
|
||||
return self._open_wrapper(stack, ch, line)
|
||||
if ch == "#":
|
||||
return self._dispatch(stack, line)
|
||||
return self._open_token(line)
|
||||
|
||||
def _dispatch_discard(self, stack: list[tuple], _line: int):
|
||||
self.get()
|
||||
stack.append(("discard",))
|
||||
return _NEED
|
||||
|
||||
def _dispatch_set(self, stack: list[tuple], _line: int):
|
||||
brace = self.line
|
||||
self.get()
|
||||
stack.append(("coll", "}", "set", brace, [], False))
|
||||
return _NEED
|
||||
|
||||
def _dispatch_fn(self, stack: list[tuple], line: int):
|
||||
self.get()
|
||||
stack.append(("coll", ")", "list", line, [], True))
|
||||
return _NEED
|
||||
|
||||
def _dispatch_string(self, _stack: list[tuple], line: int):
|
||||
self.read_string()
|
||||
return Lit(line)
|
||||
|
||||
def _dispatch_cond(self, stack: list[tuple], _line: int):
|
||||
self.get()
|
||||
splicing = self.peek() == "@"
|
||||
if splicing:
|
||||
self.get()
|
||||
stack.append(("cond", splicing))
|
||||
return _NEED
|
||||
|
||||
def _read_map_qualifier(self) -> None:
|
||||
peeked = self.peek()
|
||||
if peeked == ":":
|
||||
self.get()
|
||||
return
|
||||
if peeked is None or peeked == "{" or peeked in _WHITESPACE:
|
||||
return
|
||||
self.read_token()
|
||||
|
||||
def _dispatch_map(self, _stack: list[tuple], _line: int):
|
||||
self.get()
|
||||
self._read_map_qualifier()
|
||||
return _NEED
|
||||
|
||||
def _dispatch_extension(self, stack: list[tuple], line: int):
|
||||
self.read_token()
|
||||
stack.append(("lit", line))
|
||||
return _NEED
|
||||
|
||||
def _dispatch(self, stack: list[tuple], line: int):
|
||||
self.get()
|
||||
ch = self.peek()
|
||||
if ch is None:
|
||||
raise ReadError(line, "incomplete dispatch")
|
||||
if ch == "_":
|
||||
self.get()
|
||||
stack.append(("discard",))
|
||||
return _NEED
|
||||
if ch == "{":
|
||||
brace = self.line
|
||||
self.get()
|
||||
stack.append(("coll", "}", "set", brace, [], False))
|
||||
return _NEED
|
||||
if ch == "(":
|
||||
self.get()
|
||||
stack.append(("coll", ")", "list", line, [], True))
|
||||
return _NEED
|
||||
if ch == '"':
|
||||
self.read_string()
|
||||
return Lit(line)
|
||||
if ch == "?":
|
||||
self.get()
|
||||
splicing = False
|
||||
if self.peek() == "@":
|
||||
self.get()
|
||||
splicing = True
|
||||
stack.append(("cond", splicing))
|
||||
return _NEED
|
||||
if ch == ":":
|
||||
self.get()
|
||||
if self.peek() == ":":
|
||||
self.get()
|
||||
elif self.peek() not in (None, "{") and self.peek() not in _WHITESPACE:
|
||||
self.read_token()
|
||||
return _NEED
|
||||
self.read_token()
|
||||
stack.append(("lit", line))
|
||||
return _NEED
|
||||
handler = _DISPATCH.get(ch)
|
||||
if handler is None:
|
||||
return self._dispatch_extension(stack, line)
|
||||
return handler(self, stack, line)
|
||||
|
||||
|
||||
_DISPATCH = {
|
||||
"_": Reader._dispatch_discard,
|
||||
"{": Reader._dispatch_set,
|
||||
"(": Reader._dispatch_fn,
|
||||
'"': Reader._dispatch_string,
|
||||
"?": Reader._dispatch_cond,
|
||||
":": Reader._dispatch_map,
|
||||
}
|
||||
|
||||
|
||||
def _branch_name(feature):
|
||||
|
||||
+18
-11
@@ -104,22 +104,29 @@ def _python_nodes(data: bytes, root) -> list:
|
||||
return found
|
||||
|
||||
|
||||
def _keep_typescript_function(node) -> bool:
|
||||
return _has(node, "statement_block") and not _inside(node, _TS_FUNCTION)
|
||||
|
||||
|
||||
def _typescript_parent(node):
|
||||
parent = node.parent
|
||||
if parent is None or parent.type not in _DEFINITION_PARENTS:
|
||||
return None
|
||||
if _inside(parent, _TS_FUNCTION):
|
||||
return None
|
||||
return parent
|
||||
|
||||
|
||||
def _typescript_nodes(data: bytes, root) -> list:
|
||||
found = []
|
||||
for node in descendants(root):
|
||||
if node.type == "function_declaration":
|
||||
if _has(node, "statement_block") and not _inside(node, _TS_FUNCTION):
|
||||
found.append(node)
|
||||
elif node.type == "method_definition":
|
||||
if _has(node, "statement_block") and not _inside(node, _TS_FUNCTION):
|
||||
if node.type in {"function_declaration", "method_definition"}:
|
||||
if _keep_typescript_function(node):
|
||||
found.append(node)
|
||||
elif node.type in {"arrow_function", "function_expression"}:
|
||||
parent = node.parent
|
||||
if parent is None or parent.type not in _DEFINITION_PARENTS:
|
||||
continue
|
||||
if _inside(parent, _TS_FUNCTION):
|
||||
continue
|
||||
found.append(parent)
|
||||
parent = _typescript_parent(node)
|
||||
if parent is not None:
|
||||
found.append(parent)
|
||||
return found
|
||||
|
||||
|
||||
|
||||
+35
-39
@@ -27,11 +27,23 @@ def _pr_atom(node) -> str:
|
||||
raise TypeError(f"cannot print {node!r}")
|
||||
|
||||
|
||||
def pr(node) -> str:
|
||||
"""Print a normalized node the way Clojure `pr-str` prints it."""
|
||||
def _print_list(current: list, done: dict[int, str]) -> str:
|
||||
pieces = []
|
||||
for child in current:
|
||||
if isinstance(child, list):
|
||||
pieces.append(done[id(child)])
|
||||
else:
|
||||
pieces.append(_pr_atom(child))
|
||||
return "[" + " ".join(pieces) + "]"
|
||||
|
||||
if not isinstance(node, list):
|
||||
return _pr_atom(node)
|
||||
|
||||
def _queue_lists(stack: list, current: list) -> None:
|
||||
for child in reversed(current):
|
||||
if isinstance(child, list):
|
||||
stack.append((child, False))
|
||||
|
||||
|
||||
def _printed(node: list) -> dict[int, str]:
|
||||
# Post-order. A 1,200-deep tree must not raise RecursionError.
|
||||
done: dict[int, str] = {}
|
||||
stack: list[tuple[list, bool]] = [(node, False)]
|
||||
@@ -39,21 +51,21 @@ def pr(node) -> str:
|
||||
current, expanded = stack.pop()
|
||||
key = id(current)
|
||||
if expanded:
|
||||
pieces = []
|
||||
for child in current:
|
||||
if isinstance(child, list):
|
||||
pieces.append(done[id(child)])
|
||||
else:
|
||||
pieces.append(_pr_atom(child))
|
||||
done[key] = "[" + " ".join(pieces) + "]"
|
||||
done[key] = _print_list(current, done)
|
||||
continue
|
||||
if key in done:
|
||||
continue
|
||||
stack.append((current, True))
|
||||
for child in reversed(current):
|
||||
if isinstance(child, list):
|
||||
stack.append((child, False))
|
||||
return done[id(node)]
|
||||
_queue_lists(stack, current)
|
||||
return done
|
||||
|
||||
|
||||
def pr(node) -> str:
|
||||
"""Print a normalized node the way Clojure `pr-str` prints it."""
|
||||
|
||||
if not isinstance(node, list):
|
||||
return _pr_atom(node)
|
||||
return _printed(node)[id(node)]
|
||||
|
||||
|
||||
def node_count(node) -> int:
|
||||
@@ -71,29 +83,7 @@ def node_count(node) -> int:
|
||||
return total
|
||||
|
||||
|
||||
def fingerprints(node) -> frozenset[str]:
|
||||
if not isinstance(node, list):
|
||||
return frozenset({pr(node)})
|
||||
done: dict[int, str] = {}
|
||||
stack: list[tuple[list, bool]] = [(node, False)]
|
||||
while stack:
|
||||
current, expanded = stack.pop()
|
||||
key = id(current)
|
||||
if expanded:
|
||||
pieces = []
|
||||
for child in current:
|
||||
if isinstance(child, list):
|
||||
pieces.append(done[id(child)])
|
||||
else:
|
||||
pieces.append(_pr_atom(child))
|
||||
done[key] = "[" + " ".join(pieces) + "]"
|
||||
continue
|
||||
if key in done:
|
||||
continue
|
||||
stack.append((current, True))
|
||||
for child in reversed(current):
|
||||
if isinstance(child, list):
|
||||
stack.append((child, False))
|
||||
def _atom_prints(node) -> set[str]:
|
||||
atoms: set[str] = set()
|
||||
pending: list = [node]
|
||||
while pending:
|
||||
@@ -102,7 +92,13 @@ def fingerprints(node) -> frozenset[str]:
|
||||
pending.extend(current)
|
||||
else:
|
||||
atoms.add(_pr_atom(current))
|
||||
return frozenset(set(done.values()) | atoms)
|
||||
return atoms
|
||||
|
||||
|
||||
def fingerprints(node) -> frozenset[str]:
|
||||
if not isinstance(node, list):
|
||||
return frozenset({pr(node)})
|
||||
return frozenset(set(_printed(node).values()) | _atom_prints(node))
|
||||
|
||||
|
||||
def jaccard(left: frozenset[str], right: frozenset[str]) -> float:
|
||||
|
||||
Reference in New Issue
Block a user