Coverage for src / moai_adk / core / project / detector.py: 12.64%
87 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-20 20:52 +0900
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-20 20:52 +0900
1# TEST: tests/unit/test_language_detector_extended.py
2"""Language detector module.
4Automatically detects 20 programming languages.
6Extended detection supports:
7- 11 new languages: Ruby, PHP, Java, Rust, Dart, Swift, Kotlin, C#, C, C++, Shell
8- 5 build tool detection: Maven, Gradle, CMake, SPM, dotnet
9- Package manager detection: bundle, composer, cargo
10- Priority conflict resolution for multi-language projects
11"""
13from pathlib import Path
16class LanguageDetector:
17 """Automatically detect up to 20 programming languages.
19 Prioritizes framework-specific files (e.g., Laravel, Django) over
20 generic language files to improve accuracy in mixed-language projects.
21 """
23 LANGUAGE_PATTERNS = {
24 # Priority order (highest to lowest):
25 # 1. Rust, 2. Dart, 3. Swift, 4. Kotlin, 5. C#, 6. Java, 7. Ruby, 8. PHP
26 # 9. Go, 10. Python, 11. TypeScript, 12. JavaScript, 13. C++, 14. C, 15. Shell
27 "rust": ["*.rs", "Cargo.toml"],
28 "dart": ["*.dart", "pubspec.yaml"],
29 "swift": ["*.swift", "Package.swift"],
30 "kotlin": ["*.kt", "build.gradle.kts"],
31 "csharp": ["*.cs", "*.csproj"],
32 "java": ["*.java", "pom.xml", "build.gradle"],
33 # Ruby moved for priority (Rails detection)
34 "ruby": [
35 "*.rb",
36 "Gemfile",
37 "Gemfile.lock", # Bundler: lock file (unique to Ruby)
38 "config/routes.rb", # Rails: routing file (unique identifier)
39 "app/controllers/", # Rails: controller directory
40 "Rakefile", # Rails/Ruby: task file
41 ],
42 # PHP after Ruby (Laravel detection)
43 "php": [
44 "*.php",
45 "composer.json",
46 "artisan", # Laravel: CLI tool (unique identifier)
47 "app/", # Laravel: application directory
48 "bootstrap/laravel.php", # Laravel: bootstrap file
49 ],
50 "go": ["*.go", "go.mod"],
51 "python": ["*.py", "pyproject.toml", "requirements.txt", "setup.py"],
52 "typescript": ["*.ts", "tsconfig.json"],
53 "javascript": ["*.js", "package.json"],
54 "cpp": ["*.cpp", "CMakeLists.txt"],
55 "c": ["*.c", "Makefile"],
56 "shell": ["*.sh", "*.bash"],
57 # Additional languages (lower priority)
58 "elixir": ["*.ex", "mix.exs"],
59 "scala": ["*.scala", "build.sbt"],
60 "clojure": ["*.clj", "project.clj"],
61 "haskell": ["*.hs", "*.cabal"],
62 "lua": ["*.lua"],
63 }
65 def detect(self, path: str | Path = ".") -> str | None:
66 """Detect a single language (in priority order).
68 Args:
69 path: Directory to inspect.
71 Returns:
72 Detected language name (lowercase) or None.
73 """
74 path = Path(path)
76 # Inspect each language in priority order
77 for language, patterns in self.LANGUAGE_PATTERNS.items():
78 if self._check_patterns(path, patterns):
79 return language
81 return None
83 def detect_multiple(self, path: str | Path = ".") -> list[str]:
84 """Detect multiple languages.
86 Args:
87 path: Directory to inspect.
89 Returns:
90 List of all detected language names.
91 """
92 path = Path(path)
93 detected = []
95 for language, patterns in self.LANGUAGE_PATTERNS.items():
96 if self._check_patterns(path, patterns):
97 detected.append(language)
99 return detected
101 def _check_patterns(self, path: Path, patterns: list[str]) -> bool:
102 """Check whether any pattern matches.
104 Args:
105 path: Directory to inspect.
106 patterns: List of glob patterns.
108 Returns:
109 True when any pattern matches.
110 """
111 for pattern in patterns:
112 # Extension pattern (e.g., *.py)
113 if pattern.startswith("*."):
114 if list(path.rglob(pattern)):
115 return True
116 # Specific file name (e.g., pyproject.toml)
117 else:
118 if (path / pattern).exists():
119 return True
121 return False
123 def get_workflow_template_path(self, language: str) -> str:
124 """Get the GitHub Actions workflow template path for a language.
127 Args:
128 language: Programming language name (lowercase).
130 Returns:
131 Workflow template file path relative to templates directory.
133 Raises:
134 ValueError: If language is not supported for workflows.
135 """
136 # Language-specific workflow template mapping
137 workflow_templates = {
138 "python": "python-tag-validation.yml",
139 "javascript": "javascript-tag-validation.yml",
140 "typescript": "typescript-tag-validation.yml",
141 "go": "go-tag-validation.yml",
142 }
144 if language not in workflow_templates:
145 raise ValueError(f"Language '{language}' is not supported for workflows")
147 return workflow_templates[language]
149 def detect_package_manager(self, path: str | Path = ".") -> str | None:
150 """Detect the package manager for the detected language.
153 Args:
154 path: Directory to inspect.
156 Returns:
157 Package manager name or None if not detected.
158 """
159 path = Path(path)
161 # Ruby
162 if (path / "Gemfile").exists():
163 return "bundle"
165 # PHP
166 if (path / "composer.json").exists():
167 return "composer"
169 # Java/Kotlin
170 if (path / "pom.xml").exists():
171 return "maven"
172 if (path / "build.gradle").exists() or (path / "build.gradle.kts").exists():
173 return "gradle"
175 # Rust
176 if (path / "Cargo.toml").exists():
177 return "cargo"
179 # Dart/Flutter
180 if (path / "pubspec.yaml").exists():
181 return "dart_pub"
183 # Swift
184 if (path / "Package.swift").exists():
185 return "spm"
187 # C#
188 if list(path.glob("*.csproj")) or list(path.glob("*.sln")):
189 return "dotnet"
191 # Python
192 if (path / "pyproject.toml").exists():
193 return "pip"
195 # JavaScript/TypeScript (check in priority order)
196 # Check for lock files and package managers
197 if (path / "bun.lockb").exists():
198 return "bun"
199 elif (path / "pnpm-lock.yaml").exists():
200 return "pnpm"
201 elif (path / "yarn.lock").exists():
202 return "yarn"
203 elif (path / "package-lock.json").exists():
204 return "npm"
205 elif (path / "package.json").exists():
206 # Default to npm for package.json without lock files
207 return "npm"
209 # Go
210 if (path / "go.mod").exists():
211 return "go_modules"
213 return None
215 def detect_build_tool(
216 self, path: str | Path = ".", language: str | None = None
217 ) -> str | None:
218 """Detect the build tool for the detected language.
221 Args:
222 path: Directory to inspect.
223 language: Optional language hint for disambiguation.
225 Returns:
226 Build tool name or None if not detected.
227 """
228 path = Path(path)
230 # C/C++
231 if (path / "CMakeLists.txt").exists():
232 return "cmake"
233 if (path / "Makefile").exists():
234 return "make"
236 # Java/Kotlin
237 if language in ["java", "kotlin"]:
238 if (path / "pom.xml").exists():
239 return "maven"
240 if (path / "build.gradle").exists() or (path / "build.gradle.kts").exists():
241 return "gradle"
243 # Rust
244 if (path / "Cargo.toml").exists():
245 return "cargo"
247 # Swift
248 if (path / "Package.swift").exists():
249 return "spm"
250 if list(path.glob("*.xcodeproj")) or list(path.glob("*.xcworkspace")):
251 return "xcode"
253 # C#
254 if list(path.glob("*.csproj")) or list(path.glob("*.sln")):
255 return "dotnet"
257 return None
259 def get_supported_languages_for_workflows(self) -> list[str]:
260 """Get the list of languages with dedicated CI/CD workflow support.
263 Returns:
264 List of supported language names (15 total).
265 """
266 return [
267 "python",
268 "javascript",
269 "typescript",
270 "go",
271 "ruby",
272 "php",
273 "java",
274 "rust",
275 "dart",
276 "swift",
277 "kotlin",
278 "csharp",
279 "c",
280 "cpp",
281 "shell",
282 ]
285def detect_project_language(path: str | Path = ".") -> str | None:
286 """Detect the project language (helper).
288 Args:
289 path: Directory to inspect (default: current directory).
291 Returns:
292 Detected language name (lowercase) or None.
293 """
294 detector = LanguageDetector()
295 return detector.detect(path)