Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | 39x 39x 39x 57x | import { parseFileExports, isTestFile } from '@aiready/core';
import type { ExportInfo } from './types';
import { inferDomain, extractExports } from './semantic/domain-inference';
/**
* Extract exports using high-fidelity AST parsing across 5+ languages.
*
* @param content - File contents to parse.
* @param filePath - Path to the file for language detection and context.
* @param domainOptions - Optional configuration for domain detection.
* @param fileImports - Optional array of strings for resolving imports.
* @returns Array of high-fidelity export metadata.
* @lastUpdated 2026-03-18
*/
export async function extractExportsWithAST(
content: string,
filePath: string,
domainOptions?: { domainKeywords?: string[] },
fileImports?: string[]
): Promise<ExportInfo[]> {
try {
const { exports: astExports } = await parseFileExports(content, filePath);
Iif (astExports.length === 0 && !isTestFile(filePath)) {
// If AST fails to find anything, we still use regex as a last resort
// ONLY for unknown file types or very complex macros
return extractExports(content, filePath, domainOptions, fileImports);
}
return astExports.map((exp) => ({
name: exp.name,
type: exp.type as any,
inferredDomain: inferDomain(
exp.name,
filePath,
domainOptions,
fileImports
),
imports: exp.imports,
dependencies: exp.dependencies,
typeReferences: (exp as any).typeReferences,
}));
} catch {
// Ultimate fallback
return extractExports(content, filePath, domainOptions, fileImports);
}
}
/**
* Heuristic to check if all exports share a common entity noun
*/
export function allExportsShareEntityNoun(exports: ExportInfo[]): boolean {
if (exports.length < 2) return true;
const getEntityNoun = (name: string): string | null => {
// Basic heuristic: last part of camelCase name often is the entity
// e.g. createOrder -> order, getUserProfile -> profile
// But we also look for common domain nouns in the middle
const commonNouns = [
'user',
'order',
'product',
'session',
'account',
'receipt',
'token',
];
const lower = name.toLowerCase();
for (const noun of commonNouns) {
if (lower.includes(noun)) return noun;
}
// Fallback: split by capital letters and take the last part
const parts = name.split(/(?=[A-Z])/);
return parts[parts.length - 1].toLowerCase();
};
const nouns = exports.map((e) => getEntityNoun(e.name)).filter(Boolean);
if (nouns.length < exports.length * 0.7) return false;
const firstNoun = nouns[0];
return nouns.every((n) => n === firstNoun);
}
|