Route function extraction through a Language type.

Each language implements functions. An unknown language returns nothing.
This commit is contained in:
Robert C. Martin
2026-09-30 08:38:14 -05:00
parent a4b5fc2aed
commit bd91c20ebf
9 changed files with 94 additions and 14 deletions
+19 -14
View File
@@ -1,22 +1,27 @@
"""Per-language function extraction."""
from crapper.languages import clojure, golang, java, python, rust, typescript
from crapper.languages.clojure import Clojure
from crapper.languages.golang import Go
from crapper.languages.java import Java
from crapper.languages.language import Unknown
from crapper.languages.python import Python
from crapper.languages.rust import Rust
from crapper.languages.typescript import TypeScript
from crapper.model import Function
_LANGUAGES = {
"clojure": Clojure(),
"java": Java(),
"go": Go(),
"typescript": TypeScript(),
"python": Python(),
"rust": Rust(),
}
_UNKNOWN = Unknown()
def functions_in_file(
language: str, source: str, path: str, project_root: str
) -> list[Function]:
if language == "clojure":
return clojure.functions_in_source(source, path, project_root)
if language == "java":
return java.functions_in_source(source, path)
if language == "go":
return golang.functions_in_source(source, path, project_root)
if language == "typescript":
return typescript.functions_in_source(source, path, project_root)
if language == "python":
return python.functions_in_source(source, path, project_root)
if language == "rust":
return rust.functions_in_source(source, path, project_root)
return []
chosen = _LANGUAGES.get(language, _UNKNOWN)
return chosen.functions(source, path, project_root)
+6
View File
@@ -7,6 +7,7 @@ variants, `and`, `or`, `loop`, `catch`, and each clause of `cond`, `condp`,
import re
from crapper.languages.language import Language
from crapper.model import Function
_DECISION = re.compile(
@@ -285,3 +286,8 @@ def functions_in_source(
)
for item in extract_functions(source)
]
class Clojure(Language):
def functions(self, source: str, path: str, project_root: str) -> list[Function]:
return functions_in_source(source, path, project_root)
+6
View File
@@ -19,6 +19,7 @@ from crapper.languages.treesitter import (
parse,
start_line,
)
from crapper.languages.language import Language
from crapper.model import Function
_DECISIONS = {
@@ -149,3 +150,8 @@ def functions_in_source(
)
)
return found
class Go(Language):
def functions(self, source: str, path: str, project_root: str) -> list[Function]:
return functions_in_source(source, path, project_root)
+6
View File
@@ -17,6 +17,7 @@ from crapper.languages.treesitter import (
parse,
start_line,
)
from crapper.languages.language import Language
from crapper.model import Function
_PACKAGE = re.compile(r"(?m)^\s*package\s+([a-zA-Z_][\w.]*)\s*;")
@@ -114,3 +115,8 @@ def functions_in_source(source: str, path: str) -> list[Function]:
visit(tree.root_node)
return found
class Java(Language):
def functions(self, source: str, path: str, project_root: str) -> list[Function]:
return functions_in_source(source, path)
+16
View File
@@ -0,0 +1,16 @@
"""The operation every language implements."""
from abc import ABC, abstractmethod
from crapper.model import Function
class Language(ABC):
@abstractmethod
def functions(self, source: str, path: str, project_root: str) -> list[Function]:
"""Functions and methods defined in one source file."""
class Unknown(Language):
def functions(self, source: str, path: str, project_root: str) -> list[Function]:
return []
+6
View File
@@ -16,6 +16,7 @@ from crapper.languages.treesitter import (
parse,
start_line,
)
from crapper.languages.language import Language
from crapper.model import Function
_DECISIONS = {
@@ -131,3 +132,8 @@ def functions_in_source(
)
)
return found
class Python(Language):
def functions(self, source: str, path: str, project_root: str) -> list[Function]:
return functions_in_source(source, path, project_root)
+6
View File
@@ -20,6 +20,7 @@ from crapper.languages.treesitter import (
parse,
start_line,
)
from crapper.languages.language import Language
from crapper.model import Function
_DECISIONS = {
@@ -186,3 +187,8 @@ def functions_in_source(
if recorded is not None:
found.append(recorded)
return found
class Rust(Language):
def functions(self, source: str, path: str, project_root: str) -> list[Function]:
return functions_in_source(source, path, project_root)
+6
View File
@@ -17,6 +17,7 @@ from crapper.languages.treesitter import (
parse,
start_line,
)
from crapper.languages.language import Language
from crapper.model import Function
_DECISIONS = {
@@ -178,3 +179,8 @@ def functions_in_source(
elif node.type == "lexical_declaration":
_append_arrows(found, data, node, module, path)
return found
class TypeScript(Language):
def functions(self, source: str, path: str, project_root: str) -> list[Function]:
return functions_in_source(source, path, project_root)
+23
View File
@@ -0,0 +1,23 @@
import pytest
from crapper.languages import functions_in_file
_SOURCES = [
("clojure", "(defn choose [x] x)\n", "src/demo/core.clj", "choose"),
("java", "class Board { void place() { if (true) return; } }\n", "Board.java", "place"),
("go", "package demo\nfunc Place() int { return 1 }\n", "board.go", "Place"),
("typescript", "export function view() { return 1 }\n", "src/ui.ts", "view"),
("python", "def run():\n return 1\n", "src/app.py", "run"),
("rust", "pub fn open() {}\n", "src/lib.rs", "open"),
]
@pytest.mark.parametrize(("language", "source", "path", "name"), _SOURCES)
def test_each_language_answers_the_same_message(language, source, path, name):
found = functions_in_file(language, source, path, "/proj")
assert found[0].name == name
assert found[0].language == language
def test_unknown_language_has_no_functions():
assert functions_in_file("ruby", "def x\nend\n", "a.rb", "/proj") == []