| 7429 | * Their nodes/references/errors are merged into the returned result. |
| 7430 | */ |
| 7431 | export function extractFromSource( |
| 7432 | filePath: string, |
| 7433 | source: string, |
| 7434 | language?: Language, |
| 7435 | frameworkNames?: string[] |
| 7436 | ): ExtractionResult { |
| 7437 | const detectedLanguage = language || detectLanguage(filePath, source); |
| 7438 | const fileExtension = path.extname(filePath).toLowerCase(); |
| 7439 | |
| 7440 | let result: ExtractionResult; |
| 7441 | |
| 7442 | // Use custom extractor for Svelte |
| 7443 | if (detectedLanguage === 'svelte') { |
| 7444 | const extractor = new SvelteExtractor(filePath, source); |
| 7445 | result = extractor.extract(); |
| 7446 | } else if (detectedLanguage === 'vue') { |
| 7447 | // Use custom extractor for Vue |
| 7448 | const extractor = new VueExtractor(filePath, source); |
| 7449 | result = extractor.extract(); |
| 7450 | } else if (detectedLanguage === 'astro') { |
| 7451 | // Use custom extractor for Astro (frontmatter + template delegation) |
| 7452 | const extractor = new AstroExtractor(filePath, source); |
| 7453 | result = extractor.extract(); |
| 7454 | } else if (detectedLanguage === 'liquid') { |
| 7455 | // Use custom extractor for Liquid |
| 7456 | const extractor = new LiquidExtractor(filePath, source); |
| 7457 | result = extractor.extract(); |
| 7458 | } else if (detectedLanguage === 'razor') { |
| 7459 | // Use custom extractor for ASP.NET Razor (.cshtml) / Blazor (.razor) markup |
| 7460 | const extractor = new RazorExtractor(filePath, source); |
| 7461 | result = extractor.extract(); |
| 7462 | } else if (detectedLanguage === 'xml') { |
| 7463 | // Custom extractor for MyBatis mapper XML. Non-mapper XML returns just a |
| 7464 | // file node so the watcher tracks it without emitting symbols. |
| 7465 | const extractor = new MyBatisExtractor(filePath, source); |
| 7466 | result = extractor.extract(); |
| 7467 | } else if (detectedLanguage === 'cfml' || detectedLanguage === 'cfscript') { |
| 7468 | // Custom extractor for CFML (.cfc/.cfm) — dialect-switches between the |
| 7469 | // tag-based cfml grammar and the bare-script cfscript grammar. Standalone |
| 7470 | // `.cfs` files (language 'cfscript') are always pure script (never `<`-led), |
| 7471 | // so routing them through here too gets them the same anonymous-component |
| 7472 | // filename fallback as a bare-script `.cfc` — without it a `.cfs` whose |
| 7473 | // `component { ... }` declares no name (the grammar has no `name` field; |
| 7474 | // CFML never spells one in source) stays `<anonymous>`. |
| 7475 | const extractor = new CfmlExtractor(filePath, source, detectedLanguage); |
| 7476 | result = extractor.extract(); |
| 7477 | } else if (isFileLevelOnlyLanguage(detectedLanguage)) { |
| 7478 | // No symbol extraction at this stage — files are tracked at the file-record |
| 7479 | // level only. Framework extractors (Drupal routing yml, Spring `@Value` |
| 7480 | // resolution against application.yml/application.properties) run later and |
| 7481 | // add per-file nodes/references when they apply. |
| 7482 | result = { nodes: [], edges: [], unresolvedReferences: [], errors: [], durationMs: 0 }; |
| 7483 | } else if ( |
| 7484 | detectedLanguage === 'pascal' && |
| 7485 | (fileExtension === '.dfm' || fileExtension === '.fmx') |
| 7486 | ) { |
| 7487 | // Use custom extractor for DFM/FMX form files |
| 7488 | const extractor = new DfmExtractor(filePath, source); |