1import { execSync } from 'child_process' 3import ts from 'typescript' 10const anchorFnNames = new Set(['ptAnchorPath', 'ptAnchorInclude', 'epFromPath']) 12const resolvePath = (ptPath: string, strVal: string) => 13 (strVal.startsWith('./') || strVal.startsWith('../')) ? path.join(path.dirname(ptPath), strVal) : strVal 15const getAnchoredPathsFromFile = async (ptPath: string): Promise<string[]> => { 16 const anchored: string[] = [] 21 const sourceFile = tsSrcFile({ptPath: ptPath as absFileDirPath, contents}) 22 const visit = (node: ts.Node) => { 23 if (ts.isCallExpression(node)) { 24 const fnName = ts.isIdentifier(node.expression) ? node.expression.text 25 : ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.name) ? node.expression.name.text : null 26 if (fnName && anchorFnNames.has(fnName) && node.arguments.length > 0 && ts.isStringLiteral(node.arguments[0])) { 27 anchored.push(resolvePath(ptPath, node.arguments[0].text)) 30 ts.forEachChild(node, visit) 34 const ast = acornParse({contents, ptPath: ptPath as absFileDirPath}) 36 if (node.type !== 'CallExpression') return 37 const fnName = node.callee?.type === 'Identifier' ? node.callee.name 38 : node.callee?.type === 'MemberExpression' && node.callee.property?.type === 'Identifier' ? node.callee.property.name : null 39 if (!fnName || !anchorFnNames.has(fnName)) return 40 const firstArg = node.arguments?.[0] 41 const strVal = firstArg?.type === 'Literal' && typeof firstArg.value === 'string' ? firstArg.value 42 : firstArg?.type === 'TemplateLiteral' && firstArg.quasis?.length === 1 && !firstArg.expressions?.length ? firstArg.quasis[0].value?.cooked : null 43 if (strVal) anchored.push(resolvePath(ptPath, strVal)) 50export const getAnchoredPathsAstH = async (): Promise<Record<string, string[]>> => { 51 const resultH: Record<string, string[]> = {} 53 // Find files that might contain anchor calls 54 let filePaths: string[] = [] 56 const rgOutput = execSync( 58 {encoding: 'utf8', cwd: ptDir, maxBuffer: 10 * 1024 * 1024} 60 filePaths = rgOutput.trim().split('\n').filter(Boolean).map(p => p.replace(/^\.\//, '')) 62 if (e.status !== 1) throw e 65 for (const ptPath of filePaths) { 66 const anchored = await getAnchoredPathsFromFile(ptPath) 67 if (anchored.length) resultH[ptPath] = anchored 73export const getAllAnchoredPaths = async (): Promise<string[]> => { 74 const h = await getAnchoredPathsAstH() 75 return [...new Set(Object.values(h).flat())] 78export const getAnchorersOfPath = async (filePath: string): Promise<string[]> => { 79 const h = await getAnchoredPathsAstH() 80 return Object.entries(h).filter(([, paths]) => paths.includes(filePath)).map(([anchorer]) => anchorer) 83export const getAnchorersOfPaths = async (filePaths: string[]): Promise<string[]> => { 84 const h = await getAnchoredPathsAstH() 85 const fileSet = new Set(filePaths) 86 return Object.entries(h).filter(([, paths]) => paths.some(p => fileSet.has(p))).map(([anchorer]) => anchorer)