Create each language through a LanguageFactory.

functions_in_file asks the factory for a language instance instead of constructing the language itself.
This commit is contained in:
Robert C. Martin
2026-09-30 08:48:29 -05:00
parent 103df22cb5
commit 4909ad241b
9 changed files with 82 additions and 31 deletions
+17 -7
View File
@@ -185,7 +185,8 @@
{:name "declared_namespace", :text "declared_namespace"}
{:name "namespace_from_path", :text "namespace_from_path"}
{:name "functions_in_source", :text "functions_in_source"}
{:name "Clojure", :text "Clojure"}],
{:name "Clojure", :text "Clojure"}
{:name "ClojureFactory", :text "ClojureFactory"}],
:level 1,
:lang :python,
:id :languages.clojure}
@@ -195,7 +196,8 @@
:ops
[{:name "package_namespace", :text "package_namespace"}
{:name "functions_in_source", :text "functions_in_source"}
{:name "Go", :text "Go"}],
{:name "Go", :text "Go"}
{:name "GoFactory", :text "GoFactory"}],
:level 1,
:lang :python,
:id :languages.golang}
@@ -204,14 +206,19 @@
:file "src/crapper/languages/java.py",
:ops
[{:name "functions_in_source", :text "functions_in_source"}
{:name "Java", :text "Java"}],
{:name "Java", :text "Java"}
{:name "JavaFactory", :text "JavaFactory"}],
:level 1,
:lang :python,
:id :languages.java}
{:ns "crapper.languages.language",
:name "Language",
:file "src/crapper/languages/language.py",
:ops [{:name "Language", :text "Language"} {:name "Unknown", :text "Unknown"}],
:ops
[{:name "Language", :text "Language"}
{:name "Unknown", :text "Unknown"}
{:name "LanguageFactory", :text "LanguageFactory"}
{:name "UnknownFactory", :text "UnknownFactory"}],
:level 1,
:lang :python,
:id :languages.language}
@@ -220,7 +227,8 @@
:file "src/crapper/languages/python.py",
:ops
[{:name "functions_in_source", :text "functions_in_source"}
{:name "Python", :text "Python"}],
{:name "Python", :text "Python"}
{:name "PythonFactory", :text "PythonFactory"}],
:level 1,
:lang :python,
:id :languages.python}
@@ -229,7 +237,8 @@
:file "src/crapper/languages/rust.py",
:ops
[{:name "functions_in_source", :text "functions_in_source"}
{:name "Rust", :text "Rust"}],
{:name "Rust", :text "Rust"}
{:name "RustFactory", :text "RustFactory"}],
:level 1,
:lang :python,
:id :languages.rust}
@@ -254,7 +263,8 @@
:file "src/crapper/languages/typescript.py",
:ops
[{:name "functions_in_source", :text "functions_in_source"}
{:name "TypeScript", :text "TypeScript"}],
{:name "TypeScript", :text "TypeScript"}
{:name "TypeScriptFactory", :text "TypeScriptFactory"}],
:level 1,
:lang :python,
:id :languages.typescript}
+17 -17
View File
@@ -1,27 +1,27 @@
"""Per-language function extraction."""
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.languages.clojure import ClojureFactory
from crapper.languages.golang import GoFactory
from crapper.languages.java import JavaFactory
from crapper.languages.language import LanguageFactory, UnknownFactory
from crapper.languages.python import PythonFactory
from crapper.languages.rust import RustFactory
from crapper.languages.typescript import TypeScriptFactory
from crapper.model import Function
_LANGUAGES = {
"clojure": Clojure(),
"java": Java(),
"go": Go(),
"typescript": TypeScript(),
"python": Python(),
"rust": Rust(),
_FACTORIES: dict[str, LanguageFactory] = {
"clojure": ClojureFactory(),
"java": JavaFactory(),
"go": GoFactory(),
"typescript": TypeScriptFactory(),
"python": PythonFactory(),
"rust": RustFactory(),
}
_UNKNOWN = Unknown()
_UNKNOWN = UnknownFactory()
def functions_in_file(
language: str, source: str, path: str, project_root: str
) -> list[Function]:
chosen = _LANGUAGES.get(language, _UNKNOWN)
return chosen.functions(source, path, project_root)
factory = _FACTORIES.get(language, _UNKNOWN)
return factory.create().functions(source, path, project_root)
+6 -1
View File
@@ -7,7 +7,7 @@ variants, `and`, `or`, `loop`, `catch`, and each clause of `cond`, `condp`,
import re
from crapper.languages.language import Language
from crapper.languages.language import Language, LanguageFactory
from crapper.model import Function
_DECISION = re.compile(
@@ -291,3 +291,8 @@ def functions_in_source(
class Clojure(Language):
def functions(self, source: str, path: str, project_root: str) -> list[Function]:
return functions_in_source(source, path, project_root)
class ClojureFactory(LanguageFactory):
def create(self) -> Language:
return Clojure()
+6 -1
View File
@@ -19,7 +19,7 @@ from crapper.languages.treesitter import (
parse,
start_line,
)
from crapper.languages.language import Language
from crapper.languages.language import Language, LanguageFactory
from crapper.model import Function
_DECISIONS = {
@@ -155,3 +155,8 @@ def functions_in_source(
class Go(Language):
def functions(self, source: str, path: str, project_root: str) -> list[Function]:
return functions_in_source(source, path, project_root)
class GoFactory(LanguageFactory):
def create(self) -> Language:
return Go()
+6 -1
View File
@@ -17,7 +17,7 @@ from crapper.languages.treesitter import (
parse,
start_line,
)
from crapper.languages.language import Language
from crapper.languages.language import Language, LanguageFactory
from crapper.model import Function
_PACKAGE = re.compile(r"(?m)^\s*package\s+([a-zA-Z_][\w.]*)\s*;")
@@ -120,3 +120,8 @@ def functions_in_source(source: str, path: str) -> list[Function]:
class Java(Language):
def functions(self, source: str, path: str, project_root: str) -> list[Function]:
return functions_in_source(source, path)
class JavaFactory(LanguageFactory):
def create(self) -> Language:
return Java()
+12 -1
View File
@@ -1,4 +1,4 @@
"""The operation every language implements."""
"""The language operation, and the factories that create language instances."""
from abc import ABC, abstractmethod
@@ -14,3 +14,14 @@ class Language(ABC):
class Unknown(Language):
def functions(self, source: str, path: str, project_root: str) -> list[Function]:
return []
class LanguageFactory(ABC):
@abstractmethod
def create(self) -> Language:
"""A language instance ready to read source files."""
class UnknownFactory(LanguageFactory):
def create(self) -> Language:
return Unknown()
+6 -1
View File
@@ -16,7 +16,7 @@ from crapper.languages.treesitter import (
parse,
start_line,
)
from crapper.languages.language import Language
from crapper.languages.language import Language, LanguageFactory
from crapper.model import Function
_DECISIONS = {
@@ -137,3 +137,8 @@ def functions_in_source(
class Python(Language):
def functions(self, source: str, path: str, project_root: str) -> list[Function]:
return functions_in_source(source, path, project_root)
class PythonFactory(LanguageFactory):
def create(self) -> Language:
return Python()
+6 -1
View File
@@ -20,7 +20,7 @@ from crapper.languages.treesitter import (
parse,
start_line,
)
from crapper.languages.language import Language
from crapper.languages.language import Language, LanguageFactory
from crapper.model import Function
_DECISIONS = {
@@ -192,3 +192,8 @@ def functions_in_source(
class Rust(Language):
def functions(self, source: str, path: str, project_root: str) -> list[Function]:
return functions_in_source(source, path, project_root)
class RustFactory(LanguageFactory):
def create(self) -> Language:
return Rust()
+6 -1
View File
@@ -17,7 +17,7 @@ from crapper.languages.treesitter import (
parse,
start_line,
)
from crapper.languages.language import Language
from crapper.languages.language import Language, LanguageFactory
from crapper.model import Function
_DECISIONS = {
@@ -184,3 +184,8 @@ def functions_in_source(
class TypeScript(Language):
def functions(self, source: str, path: str, project_root: str) -> list[Function]:
return functions_in_source(source, path, project_root)
class TypeScriptFactory(LanguageFactory):
def create(self) -> Language:
return TypeScript()