(content: string, lang: string)
| 47 | |
| 48 | /** Extract raw import specifiers from source. Regex-based (fast, language-agnostic enough). */ |
| 49 | export function extractImportSpecifiers(content: string, lang: string): string[] { |
| 50 | const specs: string[] = []; |
| 51 | const push = (s: string | undefined) => { if (s) specs.push(s); }; |
| 52 | |
| 53 | if (lang === 'python') { |
| 54 | // from X import ... / import X |
| 55 | const re = /^\s*(?:from\s+([.\w]+)\s+import|import\s+([.\w]+))/gm; |
| 56 | let m: RegExpExecArray | null; |
| 57 | while ((m = re.exec(content)) !== null) push(m[1] ?? m[2]); |
| 58 | return specs; |
| 59 | } |
| 60 | if (lang === 'go') { |
| 61 | const re = /"([^"]+)"/g; // crude: import block strings |
| 62 | const block = /import\s*\(([\s\S]*?)\)/.exec(content); |
| 63 | const src = block ? block[1]! : content; |
| 64 | let m: RegExpExecArray | null; |
| 65 | while ((m = re.exec(src)) !== null) push(m[1]); |
| 66 | return specs; |
| 67 | } |
| 68 | if (lang === 'rust') { |
| 69 | const re = /^\s*(?:use|mod)\s+([\w:]+)/gm; |
| 70 | let m: RegExpExecArray | null; |
| 71 | while ((m = re.exec(content)) !== null) push(m[1]?.split('::')[0]); |
| 72 | return specs; |
| 73 | } |
| 74 | // JS/TS/PHP/etc: import ... from 'x', require('x'), dynamic import('x') |
| 75 | const reFrom = /(?:import|export)\s[^'"]*?from\s*['"]([^'"]+)['"]/g; |
| 76 | const reBare = /import\s*['"]([^'"]+)['"]/g; // side-effect import 'x' |
| 77 | const reReq = /require\(\s*['"]([^'"]+)['"]\s*\)/g; |
| 78 | const reDyn = /import\(\s*['"]([^'"]+)['"]\s*\)/g; |
| 79 | for (const re of [reFrom, reBare, reReq, reDyn]) { |
| 80 | let m: RegExpExecArray | null; |
| 81 | while ((m = re.exec(content)) !== null) push(m[1]); |
| 82 | } |
| 83 | return specs; |
| 84 | } |
| 85 | |
| 86 | function detectLang(file: string): string { |
| 87 | const ext = path.extname(file).toLowerCase(); |
no test coverage detected