Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions apps/backend/project/command_registry/frameworks.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,22 @@
"dart",
"pub",
}, # Dart HTTP framework (deprecated but still used)
# JVM frameworks
"spring-boot": {"mvn", "gradle", "gradlew"},
"quarkus": {"quarkus", "mvn", "gradle"},
"micronaut": {"mn", "mvn", "gradle"},
"android": {"gradlew", "adb"},
"compose": {"gradle", "gradlew"}, # Jetpack/Multiplatform Compose
# .NET frameworks
"aspnet": {"dotnet"},
"maui": {"dotnet"},
"unity": {"dotnet", "msbuild"},
# C/C++ frameworks
"qt": {"qmake", "moc", "uic", "rcc"},
"gtest": {"ctest"},
"catch2": {"ctest"},
# Swift frameworks
"vapor": {"vapor", "swift"},
}


Expand Down
99 changes: 99 additions & 0 deletions apps/backend/project/framework_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ def detect_all(self) -> list[str]:
self.detect_dart_frameworks()
self.detect_go_frameworks()
self.detect_rust_frameworks()
self.detect_jvm_frameworks()
self.detect_dotnet_frameworks()
self.detect_cpp_frameworks()
self.detect_elixir_frameworks()
self.detect_swift_frameworks()
return self.frameworks

def detect_nodejs_frameworks(self) -> None:
Expand Down Expand Up @@ -307,3 +312,97 @@ def detect_rust_frameworks(self) -> None:
for dep, framework in rust_framework_deps.items():
if dep in content:
self.frameworks.append(framework)

def detect_jvm_frameworks(self) -> None:
"""Detect JVM frameworks from Maven/Gradle build files."""
content = ""
for build_file in (
"pom.xml",
"build.gradle",
"build.gradle.kts",
"settings.gradle",
"settings.gradle.kts",
):
file_content = self.parser.read_text(build_file)
if file_content:
content += file_content

if not content:
return

jvm_framework_deps = {
"spring-boot": "spring-boot",
"org.springframework": "spring-boot",
"io.quarkus": "quarkus",
"io.micronaut": "micronaut",
"com.android.application": "android",
"com.android.library": "android",
"org.jetbrains.compose": "compose",
}

for dep, framework in jvm_framework_deps.items():
if dep in content and framework not in self.frameworks:
self.frameworks.append(framework)

def detect_dotnet_frameworks(self) -> None:
"""Detect .NET frameworks from project files."""
csproj_files = self.parser.glob_files("*.csproj") + self.parser.glob_files(
"*/*.csproj"
)
content = ""
for csproj in csproj_files:
try:
with open(csproj, encoding="utf-8") as f:
content += f.read()
except (OSError, UnicodeDecodeError):
continue

if content:
if "Microsoft.AspNetCore" in content or "Microsoft.NET.Sdk.Web" in content:
self.frameworks.append("aspnet")
if "Microsoft.Maui" in content or "UseMaui" in content:
self.frameworks.append("maui")

# Unity projects carry a ProjectVersion.txt under ProjectSettings/
if self.parser.file_exists("ProjectSettings/ProjectVersion.txt"):
self.frameworks.append("unity")

def detect_cpp_frameworks(self) -> None:
"""Detect C/C++ frameworks and test libraries from CMake files."""
content = self.parser.read_text("CMakeLists.txt")
if not content:
return

cpp_framework_markers = {
"Qt5": "qt",
"Qt6": "qt",
"find_package(Qt": "qt",
"Boost": "boost",
"GTest": "gtest",
"googletest": "gtest",
"Catch2": "catch2",
}

for marker, framework in cpp_framework_markers.items():
if marker in content and framework not in self.frameworks:
self.frameworks.append(framework)

def detect_elixir_frameworks(self) -> None:
"""Detect Elixir frameworks from mix.exs."""
content = self.parser.read_text("mix.exs")
if not content:
return

if ":phoenix" in content:
self.frameworks.append("phoenix")
if ":ecto" in content and "ecto" not in self.frameworks:
self.frameworks.append("ecto")

def detect_swift_frameworks(self) -> None:
"""Detect Swift frameworks from Package.swift."""
content = self.parser.read_text("Package.swift")
if not content:
return

if "vapor" in content.lower():
self.frameworks.append("vapor")
93 changes: 93 additions & 0 deletions tests/test_project_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,99 @@ def test_detects_pytest(self, temp_dir: Path):

assert "pytest" in analyzer.profile.detected_stack.frameworks

def test_detects_spring_boot_from_pom(self, temp_dir: Path):
"""Detects Spring Boot from pom.xml."""
(temp_dir / "pom.xml").write_text(
"<project><parent><groupId>org.springframework.boot</groupId>"
"<artifactId>spring-boot-starter-parent</artifactId></parent></project>"
)

analyzer = ProjectAnalyzer(temp_dir)
analyzer._detect_frameworks()

assert "spring-boot" in analyzer.profile.detected_stack.frameworks

def test_detects_android_from_gradle_kts(self, temp_dir: Path):
"""Detects Android from Gradle Kotlin-DSL plugin."""
(temp_dir / "build.gradle.kts").write_text(
'plugins { id("com.android.application") }'
)

analyzer = ProjectAnalyzer(temp_dir)
analyzer._detect_frameworks()

assert "android" in analyzer.profile.detected_stack.frameworks

def test_detects_quarkus_from_gradle(self, temp_dir: Path):
"""Detects Quarkus from build.gradle."""
(temp_dir / "build.gradle").write_text(
"plugins { id 'io.quarkus' version '3.0.0' }"
)

analyzer = ProjectAnalyzer(temp_dir)
analyzer._detect_frameworks()

assert "quarkus" in analyzer.profile.detected_stack.frameworks

def test_detects_aspnet_from_csproj(self, temp_dir: Path):
"""Detects ASP.NET Core from csproj SDK."""
(temp_dir / "App.csproj").write_text(
'<Project Sdk="Microsoft.NET.Sdk.Web"></Project>'
)

analyzer = ProjectAnalyzer(temp_dir)
analyzer._detect_frameworks()

assert "aspnet" in analyzer.profile.detected_stack.frameworks

def test_detects_unity_from_project_version(self, temp_dir: Path):
"""Detects Unity from ProjectSettings/ProjectVersion.txt."""
settings = temp_dir / "ProjectSettings"
settings.mkdir()
(settings / "ProjectVersion.txt").write_text("m_EditorVersion: 2022.3.0f1")

analyzer = ProjectAnalyzer(temp_dir)
analyzer._detect_frameworks()

assert "unity" in analyzer.profile.detected_stack.frameworks

def test_detects_qt_and_gtest_from_cmake(self, temp_dir: Path):
"""Detects Qt and GTest from CMakeLists.txt."""
(temp_dir / "CMakeLists.txt").write_text(
"project(demo)\n"
"find_package(Qt6 REQUIRED COMPONENTS Widgets)\n"
"find_package(GTest REQUIRED)\n"
)

analyzer = ProjectAnalyzer(temp_dir)
analyzer._detect_frameworks()

assert "qt" in analyzer.profile.detected_stack.frameworks
assert "gtest" in analyzer.profile.detected_stack.frameworks

def test_detects_phoenix_from_mix(self, temp_dir: Path):
"""Detects Phoenix from mix.exs deps."""
(temp_dir / "mix.exs").write_text(
'defp deps do [{:phoenix, "~> 1.7"}, {:ecto, "~> 3.10"}] end'
)

analyzer = ProjectAnalyzer(temp_dir)
analyzer._detect_frameworks()

assert "phoenix" in analyzer.profile.detected_stack.frameworks
assert "ecto" in analyzer.profile.detected_stack.frameworks

def test_detects_vapor_from_package_swift(self, temp_dir: Path):
"""Detects Vapor from Package.swift."""
(temp_dir / "Package.swift").write_text(
'.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0")'
)

analyzer = ProjectAnalyzer(temp_dir)
analyzer._detect_frameworks()

assert "vapor" in analyzer.profile.detected_stack.frameworks


class TestDatabaseDetection:
"""Tests for database detection."""
Expand Down
Loading