( stringified: string, )
| 47 | } |
| 48 | |
| 49 | export function templateFunctionStr<Args extends string[]>( |
| 50 | stringified: string, |
| 51 | ): (...args: Args) => string { |
| 52 | const sourceFile = ts.createSourceFile('test.js', stringified, ts.ScriptTarget.ESNext, true); |
| 53 | |
| 54 | // 1. Find the function. |
| 55 | let decl: ts.FunctionLike | undefined; |
| 56 | ts.forEachChild(sourceFile, function traverse(node) { |
| 57 | if (ts.isFunctionLike(node)) { |
| 58 | decl = node; |
| 59 | } else { |
| 60 | ts.forEachChild(node, traverse); |
| 61 | } |
| 62 | }); |
| 63 | |
| 64 | if (!decl || !('body' in decl) || !decl.body) { |
| 65 | throw new Error(`Could not find function declaration for:\n\n${stringified}`); |
| 66 | } |
| 67 | |
| 68 | // 2. Get parameter names. |
| 69 | const params = decl.parameters.map(p => { |
| 70 | if (!ts.isIdentifier(p.name)) { |
| 71 | throw new Error('Parameter must be identifier'); |
| 72 | } |
| 73 | |
| 74 | return p.name.text; |
| 75 | }); |
| 76 | |
| 77 | // 3. Gather usages of the parameter in the source. |
| 78 | const replacements: { start: number; end: number; param: number }[] = []; |
| 79 | ts.forEachChild(decl.body, function traverse(node) { |
| 80 | if (ts.isIdentifier(node) && params.includes(node.text)) { |
| 81 | replacements.push({ |
| 82 | start: node.getStart(), |
| 83 | end: node.getEnd(), |
| 84 | param: params.indexOf(node.text), |
| 85 | }); |
| 86 | } |
| 87 | |
| 88 | ts.forEachChild(node, traverse); |
| 89 | }); |
| 90 | |
| 91 | replacements.sort((a, b) => b.end - a.end); |
| 92 | |
| 93 | // 4. Sort usages and slice up the function appropriately, wraping in an IIFE. |
| 94 | const parts: string[] = []; |
| 95 | let lastIndex = decl.body.getEnd() - 1; |
| 96 | for (const replacement of replacements) { |
| 97 | parts.push(stringified.slice(replacement.end, lastIndex)); |
| 98 | parts.push(`__args${replacement.param}`); |
| 99 | lastIndex = replacement.start; |
| 100 | } |
| 101 | |
| 102 | parts.push(stringified.slice(decl.body.getStart() + 1, lastIndex)); |
| 103 | const body = parts.reverse().join(''); |
| 104 | |
| 105 | return (...args) => `(() => { |
| 106 | ${args.map((a, i) => `let __args${i} = ${a}`).join('; ')}; |
no test coverage detected