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 "declared_namespace", :text "declared_namespace"}
{:name "namespace_from_path", :text "namespace_from_path"} {:name "namespace_from_path", :text "namespace_from_path"}
{:name "functions_in_source", :text "functions_in_source"} {:name "functions_in_source", :text "functions_in_source"}
{:name "Clojure", :text "Clojure"}], {:name "Clojure", :text "Clojure"}
{:name "ClojureFactory", :text "ClojureFactory"}],
:level 1, :level 1,
:lang :python, :lang :python,
:id :languages.clojure} :id :languages.clojure}
@@ -195,7 +196,8 @@
:ops :ops
[{:name "package_namespace", :text "package_namespace"} [{:name "package_namespace", :text "package_namespace"}
{:name "functions_in_source", :text "functions_in_source"} {:name "functions_in_source", :text "functions_in_source"}
{:name "Go", :text "Go"}], {:name "Go", :text "Go"}
{:name "GoFactory", :text "GoFactory"}],
:level 1, :level 1,
:lang :python, :lang :python,
:id :languages.golang} :id :languages.golang}
@@ -204,14 +206,19 @@
:file "src/crapper/languages/java.py", :file "src/crapper/languages/java.py",
:ops :ops
[{:name "functions_in_source", :text "functions_in_source"} [{:name "functions_in_source", :text "functions_in_source"}
{:name "Java", :text "Java"}], {:name "Java", :text "Java"}
{:name "JavaFactory", :text "JavaFactory"}],
:level 1, :level 1,
:lang :python, :lang :python,
:id :languages.java} :id :languages.java}
{:ns "crapper.languages.language", {:ns "crapper.languages.language",
:name "Language", :name "Language",
:file "src/crapper/languages/language.py", :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, :level 1,
:lang :python, :lang :python,
:id :languages.language} :id :languages.language}
@@ -220,7 +227,8 @@
:file "src/crapper/languages/python.py", :file "src/crapper/languages/python.py",
:ops :ops
[{:name "functions_in_source", :text "functions_in_source"} [{:name "functions_in_source", :text "functions_in_source"}
{:name "Python", :text "Python"}], {:name "Python", :text "Python"}
{:name "PythonFactory", :text "PythonFactory"}],
:level 1, :level 1,
:lang :python, :lang :python,
:id :languages.python} :id :languages.python}
@@ -229,7 +237,8 @@
:file "src/crapper/languages/rust.py", :file "src/crapper/languages/rust.py",
:ops :ops
[{:name "functions_in_source", :text "functions_in_source"} [{:name "functions_in_source", :text "functions_in_source"}
{:name "Rust", :text "Rust"}], {:name "Rust", :text "Rust"}
{:name "RustFactory", :text "RustFactory"}],
:level 1, :level 1,
:lang :python, :lang :python,
:id :languages.rust} :id :languages.rust}
@@ -254,7 +263,8 @@
:file "src/crapper/languages/typescript.py", :file "src/crapper/languages/typescript.py",
:ops :ops
[{:name "functions_in_source", :text "functions_in_source"} [{:name "functions_in_source", :text "functions_in_source"}
{:name "TypeScript", :text "TypeScript"}], {:name "TypeScript", :text "TypeScript"}
{:name "TypeScriptFactory", :text "TypeScriptFactory"}],
:level 1, :level 1,
:lang :python, :lang :python,
:id :languages.typescript} :id :languages.typescript}
+17 -17
View File
@@ -1,27 +1,27 @@
"""Per-language function extraction.""" """Per-language function extraction."""
from crapper.languages.clojure import Clojure from crapper.languages.clojure import ClojureFactory
from crapper.languages.golang import Go from crapper.languages.golang import GoFactory
from crapper.languages.java import Java from crapper.languages.java import JavaFactory
from crapper.languages.language import Unknown from crapper.languages.language import LanguageFactory, UnknownFactory
from crapper.languages.python import Python from crapper.languages.python import PythonFactory
from crapper.languages.rust import Rust from crapper.languages.rust import RustFactory
from crapper.languages.typescript import TypeScript from crapper.languages.typescript import TypeScriptFactory
from crapper.model import Function from crapper.model import Function
_LANGUAGES = { _FACTORIES: dict[str, LanguageFactory] = {
"clojure": Clojure(), "clojure": ClojureFactory(),
"java": Java(), "java": JavaFactory(),
"go": Go(), "go": GoFactory(),
"typescript": TypeScript(), "typescript": TypeScriptFactory(),
"python": Python(), "python": PythonFactory(),
"rust": Rust(), "rust": RustFactory(),
} }
_UNKNOWN = Unknown() _UNKNOWN = UnknownFactory()
def functions_in_file( def functions_in_file(
language: str, source: str, path: str, project_root: str language: str, source: str, path: str, project_root: str
) -> list[Function]: ) -> list[Function]:
chosen = _LANGUAGES.get(language, _UNKNOWN) factory = _FACTORIES.get(language, _UNKNOWN)
return chosen.functions(source, path, project_root) 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 import re
from crapper.languages.language import Language from crapper.languages.language import Language, LanguageFactory
from crapper.model import Function from crapper.model import Function
_DECISION = re.compile( _DECISION = re.compile(
@@ -291,3 +291,8 @@ def functions_in_source(
class Clojure(Language): class Clojure(Language):
def functions(self, source: str, path: str, project_root: str) -> list[Function]: def functions(self, source: str, path: str, project_root: str) -> list[Function]:
return functions_in_source(source, path, project_root) 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, parse,
start_line, start_line,
) )
from crapper.languages.language import Language from crapper.languages.language import Language, LanguageFactory
from crapper.model import Function from crapper.model import Function
_DECISIONS = { _DECISIONS = {
@@ -155,3 +155,8 @@ def functions_in_source(
class Go(Language): class Go(Language):
def functions(self, source: str, path: str, project_root: str) -> list[Function]: def functions(self, source: str, path: str, project_root: str) -> list[Function]:
return functions_in_source(source, path, project_root) 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, parse,
start_line, start_line,
) )
from crapper.languages.language import Language from crapper.languages.language import Language, LanguageFactory
from crapper.model import Function from crapper.model import Function
_PACKAGE = re.compile(r"(?m)^\s*package\s+([a-zA-Z_][\w.]*)\s*;") _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): class Java(Language):
def functions(self, source: str, path: str, project_root: str) -> list[Function]: def functions(self, source: str, path: str, project_root: str) -> list[Function]:
return functions_in_source(source, path) 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 from abc import ABC, abstractmethod
@@ -14,3 +14,14 @@ class Language(ABC):
class Unknown(Language): class Unknown(Language):
def functions(self, source: str, path: str, project_root: str) -> list[Function]: def functions(self, source: str, path: str, project_root: str) -> list[Function]:
return [] 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, parse,
start_line, start_line,
) )
from crapper.languages.language import Language from crapper.languages.language import Language, LanguageFactory
from crapper.model import Function from crapper.model import Function
_DECISIONS = { _DECISIONS = {
@@ -137,3 +137,8 @@ def functions_in_source(
class Python(Language): class Python(Language):
def functions(self, source: str, path: str, project_root: str) -> list[Function]: def functions(self, source: str, path: str, project_root: str) -> list[Function]:
return functions_in_source(source, path, project_root) 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, parse,
start_line, start_line,
) )
from crapper.languages.language import Language from crapper.languages.language import Language, LanguageFactory
from crapper.model import Function from crapper.model import Function
_DECISIONS = { _DECISIONS = {
@@ -192,3 +192,8 @@ def functions_in_source(
class Rust(Language): class Rust(Language):
def functions(self, source: str, path: str, project_root: str) -> list[Function]: def functions(self, source: str, path: str, project_root: str) -> list[Function]:
return functions_in_source(source, path, project_root) 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, parse,
start_line, start_line,
) )
from crapper.languages.language import Language from crapper.languages.language import Language, LanguageFactory
from crapper.model import Function from crapper.model import Function
_DECISIONS = { _DECISIONS = {
@@ -184,3 +184,8 @@ def functions_in_source(
class TypeScript(Language): class TypeScript(Language):
def functions(self, source: str, path: str, project_root: str) -> list[Function]: def functions(self, source: str, path: str, project_root: str) -> list[Function]:
return functions_in_source(source, path, project_root) return functions_in_source(source, path, project_root)
class TypeScriptFactory(LanguageFactory):
def create(self) -> Language:
return TypeScript()