Skip to main content
Glama
language-detector.ts7.4 kB
import * as fs from 'fs'; import * as path from 'path'; import { glob } from 'glob'; import { LanguageDetector, Language } from '../types.js'; import { ProjectBuildFileDetector } from './build-file-detector.js'; export class ProjectLanguageDetector implements LanguageDetector { private buildFileDetector: ProjectBuildFileDetector; constructor() { this.buildFileDetector = new ProjectBuildFileDetector(); } async detectFromBuildFiles(projectPath: string): Promise<Language[]> { try { const result = await this.buildFileDetector.detect(projectPath); return result.detectedLanguages; } catch (error) { console.warn(`Failed to detect languages from build files: ${error instanceof Error ? error.message : String(error)}`); return []; } } async detectFromFileExtensions(projectPath: string): Promise<Language[]> { const detectedLanguages: Language[] = []; try { // Find all source files const files = await glob('**/*.{ts,tsx,js,jsx,java,py,cs}', { cwd: projectPath, ignore: ['node_modules/**', 'dist/**', 'build/**', '.git/**', 'target/**', 'coverage/**'] }); const extensions = new Set(files.map(f => path.extname(f).toLowerCase())); // Map extensions to languages if (extensions.has('.ts') || extensions.has('.tsx')) { detectedLanguages.push('typescript'); } if (extensions.has('.js') || extensions.has('.jsx')) { detectedLanguages.push('javascript'); } if (extensions.has('.java')) { detectedLanguages.push('java'); } if (extensions.has('.py')) { detectedLanguages.push('python'); } if (extensions.has('.cs')) { detectedLanguages.push('csharp'); } return detectedLanguages; } catch (error) { console.warn(`Failed to detect languages from file extensions: ${error instanceof Error ? error.message : String(error)}`); return []; } } async detectPrimaryLanguage(languages: Language[], projectPath: string): Promise<Language | undefined> { if (languages.length === 0) return undefined; if (languages.length === 1) return languages[0]; // First, try to determine primary language from build files const buildFileResult = await this.buildFileDetector.detect(projectPath); if (buildFileResult.primaryLanguage) { return buildFileResult.primaryLanguage; } // Fallback: count files by language const languageCounts: Record<Language, number> = {} as any; for (const lang of languages) { const extensions = this.getLanguageExtensions(lang); let count = 0; for (const ext of extensions) { try { const files = await glob(`**/*${ext}`, { cwd: projectPath, ignore: ['node_modules/**', 'dist/**', 'build/**', '.git/**', 'target/**'] }); count += files.length; } catch (error) { // Continue with other extensions } } languageCounts[lang] = count; } // Return language with most files const sortedLanguages = Object.entries(languageCounts) .sort(([, a], [, b]) => b - a); return sortedLanguages[0]?.[0] as Language; } private getLanguageExtensions(language: Language): string[] { switch (language) { case 'typescript': return ['.ts', '.tsx']; case 'javascript': return ['.js', '.jsx']; case 'java': return ['.java']; case 'python': return ['.py']; case 'csharp': return ['.cs']; default: return []; } } /** * Comprehensive language detection combining build files and file extensions */ async detectLanguages(projectPath: string): Promise<{ languages: Language[]; primaryLanguage?: Language; buildFileLanguages: Language[]; extensionLanguages: Language[]; }> { const buildFileLanguages = await this.detectFromBuildFiles(projectPath); const extensionLanguages = await this.detectFromFileExtensions(projectPath); // Combine and deduplicate languages const allLanguages = [...new Set([...buildFileLanguages, ...extensionLanguages])]; // Determine primary language const primaryLanguage = await this.detectPrimaryLanguage(allLanguages, projectPath); return { languages: allLanguages, primaryLanguage, buildFileLanguages, extensionLanguages }; } /** * Validate that detected languages are supported */ validateLanguages(languages: Language[]): { supported: Language[]; unsupported: Language[]; warnings: string[]; } { const supportedLanguages: Language[] = ['typescript', 'javascript', 'java', 'python']; const supported: Language[] = []; const unsupported: Language[] = []; const warnings: string[] = []; for (const lang of languages) { if (supportedLanguages.includes(lang)) { supported.push(lang); } else { unsupported.push(lang); warnings.push(`⚠️ ${lang} is detected but not yet fully supported for parsing`); } } if (supported.length === 0 && unsupported.length > 0) { warnings.push('❌ No supported languages detected in project'); } return { supported, unsupported, warnings }; } /** * Get recommended scan configuration based on detected languages */ async getRecommendedScanConfig(projectPath: string): Promise<{ languages: Language[]; primaryLanguage?: Language; buildSystems: string[]; frameworks: string[]; suggestions: string[]; includeTests: boolean; excludePaths: string[]; }> { const detection = await this.detectLanguages(projectPath); const validation = this.validateLanguages(detection.languages); const buildFileResult = await this.buildFileDetector.detect(projectPath); const suggestions: string[] = []; const buildSystems: string[] = []; const frameworks: string[] = []; const excludePaths = ['node_modules', 'dist', 'build', '.git', 'target', 'coverage']; // Extract build systems and frameworks for (const metadata of buildFileResult.projectMetadata) { if (metadata.buildSystem && !buildSystems.includes(metadata.buildSystem)) { buildSystems.push(metadata.buildSystem); } if (metadata.framework && !frameworks.includes(metadata.framework)) { frameworks.push(metadata.framework); } } // Add language-specific exclude paths if (detection.languages.includes('java')) { excludePaths.push('target/**', '*.class'); } if (detection.languages.includes('python')) { excludePaths.push('__pycache__/**', '*.pyc', 'venv/**', '.venv/**'); } if (detection.languages.includes('csharp')) { excludePaths.push('bin/**', 'obj/**'); } // Generate suggestions suggestions.push(...validation.warnings); suggestions.push(...buildFileResult.suggestions); if (buildFileResult.isMonoRepo) { suggestions.push('🏗️ Mono-repository structure detected - consider scanning sub-projects separately'); } if (frameworks.length > 0) { suggestions.push(`🚀 Frameworks detected: ${frameworks.join(', ')}`); } return { languages: validation.supported, primaryLanguage: detection.primaryLanguage, buildSystems, frameworks, suggestions, includeTests: true, excludePaths }; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/JonnoC/CodeRAG'

If you have feedback or need assistance with the MCP directory API, please join our Discord server