🌳
pt0/deployF/jsImportsF/getAnchoredPathsAstAI.mts
1import { execSync } from 'child_process'
2import path from 'path'
3import ts from 'typescript'
7import { type absFileDirPath } from '../../ptDirF.mts'
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[] = []
17 try {
18 const {contents, tsExt} = await getTsExtContents({ptPath: ptPath as absFileDirPath})
20 if (tsExt) {
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))
28 }
29 }
30 ts.forEachChild(node, visit)
31 }
32 visit(sourceFile)
33 } else {
34 const ast = acornParse({contents, ptPath: ptPath as absFileDirPath})
35 walkAcornNodes(ast, (node: AcornNode) => {
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))
44 })
45 }
46 } catch {}
47 return anchored
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[] = []
55 try {
56 const rgOutput = execSync(
57 `rg -l "(ptAnchorPath|ptAnchorInclude|epFromPath)\\(" --glob "${extGlob(epFileExtA)}" .`,
58 {encoding: 'utf8', cwd: ptDir, maxBuffer: 10 * 1024 * 1024}
59 )
60 filePaths = rgOutput.trim().split('\n').filter(Boolean).map(p => p.replace(/^\.\//, ''))
61 } catch (e: any) {
62 if (e.status !== 1) throw e
63 }
65 for (const ptPath of filePaths) {
66 const anchored = await getAnchoredPathsFromFile(ptPath)
67 if (anchored.length) resultH[ptPath] = anchored
68 }
70 return resultH
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)