| 6653 | * Their nodes/references/errors are merged into the returned result. |
| 6654 | */ |
| 6655 | export function extractFromSource( |
| 6656 | filePath: string, |
| 6657 | source: string, |
| 6658 | language?: Language, |
| 6659 | frameworkNames?: string[] |
| 6660 | ): ExtractionResult { |
| 6661 | const detectedLanguage = language || detectLanguage(filePath, source); |
| 6662 | const fileExtension = path.extname(filePath).toLowerCase(); |
| 6663 | |
| 6664 | let result: ExtractionResult; |
| 6665 | |
| 6666 | // Use custom extractor for Svelte |
| 6667 | if (detectedLanguage === 'svelte') { |
| 6668 | const extractor = new SvelteExtractor(filePath, source); |
| 6669 | result = extractor.extract(); |
| 6670 | } else if (detectedLanguage === 'vue') { |
| 6671 | // Use custom extractor for Vue |
| 6672 | const extractor = new VueExtractor(filePath, source); |
| 6673 | result = extractor.extract(); |
| 6674 | } else if (detectedLanguage === 'astro') { |
| 6675 | // Use custom extractor for Astro (frontmatter + template delegation) |
| 6676 | const extractor = new AstroExtractor(filePath, source); |
| 6677 | result = extractor.extract(); |
| 6678 | } else if (detectedLanguage === 'liquid') { |
| 6679 | // Use custom extractor for Liquid |
| 6680 | const extractor = new LiquidExtractor(filePath, source); |
| 6681 | result = extractor.extract(); |
| 6682 | } else if (detectedLanguage === 'razor') { |
| 6683 | // Use custom extractor for ASP.NET Razor (.cshtml) / Blazor (.razor) markup |
| 6684 | const extractor = new RazorExtractor(filePath, source); |
| 6685 | result = extractor.extract(); |
| 6686 | } else if (detectedLanguage === 'xml') { |
| 6687 | // Custom extractor for MyBatis mapper XML. Non-mapper XML returns just a |
| 6688 | // file node so the watcher tracks it without emitting symbols. |
| 6689 | const extractor = new MyBatisExtractor(filePath, source); |
| 6690 | result = extractor.extract(); |
| 6691 | } else if (detectedLanguage === 'cfml' || detectedLanguage === 'cfscript') { |
| 6692 | // Custom extractor for CFML (.cfc/.cfm) — dialect-switches between the |
| 6693 | // tag-based cfml grammar and the bare-script cfscript grammar. Standalone |
| 6694 | // `.cfs` files (language 'cfscript') are always pure script (never `<`-led), |
| 6695 | // so routing them through here too gets them the same anonymous-component |
| 6696 | // filename fallback as a bare-script `.cfc` — without it a `.cfs` whose |
| 6697 | // `component { ... }` declares no name (the grammar has no `name` field; |
| 6698 | // CFML never spells one in source) stays `<anonymous>`. |
| 6699 | const extractor = new CfmlExtractor(filePath, source, detectedLanguage); |
| 6700 | result = extractor.extract(); |
| 6701 | } else if (isFileLevelOnlyLanguage(detectedLanguage)) { |
| 6702 | // No symbol extraction at this stage — files are tracked at the file-record |
| 6703 | // level only. Framework extractors (Drupal routing yml, Spring `@Value` |
| 6704 | // resolution against application.yml/application.properties) run later and |
| 6705 | // add per-file nodes/references when they apply. |
| 6706 | result = { nodes: [], edges: [], unresolvedReferences: [], errors: [], durationMs: 0 }; |
| 6707 | } else if ( |
| 6708 | detectedLanguage === 'pascal' && |
| 6709 | (fileExtension === '.dfm' || fileExtension === '.fmx') |
| 6710 | ) { |
| 6711 | // Use custom extractor for DFM/FMX form files |
| 6712 | const extractor = new DfmExtractor(filePath, source); |