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

1# TEST: tests/unit/test_language_detector_extended.py 

2"""Language detector module. 

3 

4Automatically detects 20 programming languages. 

5 

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""" 

12 

13from pathlib import Path 

14 

15 

16class LanguageDetector: 

17 """Automatically detect up to 20 programming languages. 

18 

19 Prioritizes framework-specific files (e.g., Laravel, Django) over 

20 generic language files to improve accuracy in mixed-language projects. 

21 """ 

22 

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 } 

64 

65 def detect(self, path: str | Path = ".") -> str | None: 

66 """Detect a single language (in priority order). 

67 

68 Args: 

69 path: Directory to inspect. 

70 

71 Returns: 

72 Detected language name (lowercase) or None. 

73 """ 

74 path = Path(path) 

75 

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 

80 

81 return None 

82 

83 def detect_multiple(self, path: str | Path = ".") -> list[str]: 

84 """Detect multiple languages. 

85 

86 Args: 

87 path: Directory to inspect. 

88 

89 Returns: 

90 List of all detected language names. 

91 """ 

92 path = Path(path) 

93 detected = [] 

94 

95 for language, patterns in self.LANGUAGE_PATTERNS.items(): 

96 if self._check_patterns(path, patterns): 

97 detected.append(language) 

98 

99 return detected 

100 

101 def _check_patterns(self, path: Path, patterns: list[str]) -> bool: 

102 """Check whether any pattern matches. 

103 

104 Args: 

105 path: Directory to inspect. 

106 patterns: List of glob patterns. 

107 

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 

120 

121 return False 

122 

123 def get_workflow_template_path(self, language: str) -> str: 

124 """Get the GitHub Actions workflow template path for a language. 

125 

126 

127 Args: 

128 language: Programming language name (lowercase). 

129 

130 Returns: 

131 Workflow template file path relative to templates directory. 

132 

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 } 

143 

144 if language not in workflow_templates: 

145 raise ValueError(f"Language '{language}' is not supported for workflows") 

146 

147 return workflow_templates[language] 

148 

149 def detect_package_manager(self, path: str | Path = ".") -> str | None: 

150 """Detect the package manager for the detected language. 

151 

152 

153 Args: 

154 path: Directory to inspect. 

155 

156 Returns: 

157 Package manager name or None if not detected. 

158 """ 

159 path = Path(path) 

160 

161 # Ruby 

162 if (path / "Gemfile").exists(): 

163 return "bundle" 

164 

165 # PHP 

166 if (path / "composer.json").exists(): 

167 return "composer" 

168 

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" 

174 

175 # Rust 

176 if (path / "Cargo.toml").exists(): 

177 return "cargo" 

178 

179 # Dart/Flutter 

180 if (path / "pubspec.yaml").exists(): 

181 return "dart_pub" 

182 

183 # Swift 

184 if (path / "Package.swift").exists(): 

185 return "spm" 

186 

187 # C# 

188 if list(path.glob("*.csproj")) or list(path.glob("*.sln")): 

189 return "dotnet" 

190 

191 # Python 

192 if (path / "pyproject.toml").exists(): 

193 return "pip" 

194 

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" 

208 

209 # Go 

210 if (path / "go.mod").exists(): 

211 return "go_modules" 

212 

213 return None 

214 

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. 

219 

220 

221 Args: 

222 path: Directory to inspect. 

223 language: Optional language hint for disambiguation. 

224 

225 Returns: 

226 Build tool name or None if not detected. 

227 """ 

228 path = Path(path) 

229 

230 # C/C++ 

231 if (path / "CMakeLists.txt").exists(): 

232 return "cmake" 

233 if (path / "Makefile").exists(): 

234 return "make" 

235 

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" 

242 

243 # Rust 

244 if (path / "Cargo.toml").exists(): 

245 return "cargo" 

246 

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" 

252 

253 # C# 

254 if list(path.glob("*.csproj")) or list(path.glob("*.sln")): 

255 return "dotnet" 

256 

257 return None 

258 

259 def get_supported_languages_for_workflows(self) -> list[str]: 

260 """Get the list of languages with dedicated CI/CD workflow support. 

261 

262 

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 ] 

283 

284 

285def detect_project_language(path: str | Path = ".") -> str | None: 

286 """Detect the project language (helper). 

287 

288 Args: 

289 path: Directory to inspect (default: current directory). 

290 

291 Returns: 

292 Detected language name (lowercase) or None. 

293 """ 

294 detector = LanguageDetector() 

295 return detector.detect(path)