| 5785 | * Their nodes/references/errors are merged into the returned result. |
| 5786 | */ |
| 5787 | export function extractFromSource( |
| 5788 | filePath: string, |
| 5789 | source: string, |
| 5790 | language?: Language, |
| 5791 | frameworkNames?: string[] |
| 5792 | ): ExtractionResult { |
| 5793 | const detectedLanguage = language || detectLanguage(filePath, source); |
| 5794 | const fileExtension = path.extname(filePath).toLowerCase(); |
| 5795 | |
| 5796 | let result: ExtractionResult; |
| 5797 | |
| 5798 | // Use custom extractor for Svelte |
| 5799 | if (detectedLanguage === 'svelte') { |
| 5800 | const extractor = new SvelteExtractor(filePath, source); |
| 5801 | result = extractor.extract(); |
| 5802 | } else if (detectedLanguage === 'vue') { |
| 5803 | // Use custom extractor for Vue |
| 5804 | const extractor = new VueExtractor(filePath, source); |
| 5805 | result = extractor.extract(); |
| 5806 | } else if (detectedLanguage === 'astro') { |
| 5807 | // Use custom extractor for Astro (frontmatter + template delegation) |
| 5808 | const extractor = new AstroExtractor(filePath, source); |
| 5809 | result = extractor.extract(); |
| 5810 | } else if (detectedLanguage === 'liquid') { |
| 5811 | // Use custom extractor for Liquid |
| 5812 | const extractor = new LiquidExtractor(filePath, source); |
| 5813 | result = extractor.extract(); |
| 5814 | } else if (detectedLanguage === 'razor') { |
| 5815 | // Use custom extractor for ASP.NET Razor (.cshtml) / Blazor (.razor) markup |
| 5816 | const extractor = new RazorExtractor(filePath, source); |
| 5817 | result = extractor.extract(); |
| 5818 | } else if (detectedLanguage === 'xml') { |
| 5819 | // Custom extractor for MyBatis mapper XML. Non-mapper XML returns just a |
| 5820 | // file node so the watcher tracks it without emitting symbols. |
| 5821 | const extractor = new MyBatisExtractor(filePath, source); |
| 5822 | result = extractor.extract(); |
| 5823 | } else if (isFileLevelOnlyLanguage(detectedLanguage)) { |
| 5824 | // No symbol extraction at this stage — files are tracked at the file-record |
| 5825 | // level only. Framework extractors (Drupal routing yml, Spring `@Value` |
| 5826 | // resolution against application.yml/application.properties) run later and |
| 5827 | // add per-file nodes/references when they apply. |
| 5828 | result = { nodes: [], edges: [], unresolvedReferences: [], errors: [], durationMs: 0 }; |
| 5829 | } else if ( |
| 5830 | detectedLanguage === 'pascal' && |
| 5831 | (fileExtension === '.dfm' || fileExtension === '.fmx') |
| 5832 | ) { |
| 5833 | // Use custom extractor for DFM/FMX form files |
| 5834 | const extractor = new DfmExtractor(filePath, source); |
| 5835 | result = extractor.extract(); |
| 5836 | } else { |
| 5837 | const extractor = new TreeSitterExtractor(filePath, source, detectedLanguage); |
| 5838 | result = extractor.extract(); |
| 5839 | } |
| 5840 | |
| 5841 | // Framework-specific extraction (routes, middleware, etc.) |
| 5842 | if (frameworkNames && frameworkNames.length > 0) { |
| 5843 | const allResolvers = getAllFrameworkResolvers(); |
| 5844 | const applicable = getApplicableFrameworks( |