| 37 | * can be inserted by the devkit into a user's app. |
| 38 | */ |
| 39 | export class CodeBlock { |
| 40 | private _imports: PendingImports = new Map<string, Map<string, string>>(); |
| 41 | |
| 42 | // Note: the methods here are defined as arrow function so that they can be destructured by |
| 43 | // consumers without losing their context. This makes the API more concise. |
| 44 | |
| 45 | /** Function used to tag a code block in order to produce a `PendingCode` object. */ |
| 46 | code = (strings: TemplateStringsArray, ...params: unknown[]): PendingCode => { |
| 47 | return { |
| 48 | expression: strings.map((part, index) => part + (params[index] || '')).join(''), |
| 49 | imports: this._imports, |
| 50 | }; |
| 51 | }; |
| 52 | |
| 53 | /** |
| 54 | * Used inside of a code block to mark external symbols and which module they should be imported |
| 55 | * from. When the code is inserted, the required import statements will be produced automatically. |
| 56 | * @param symbolName Name of the external symbol. |
| 57 | * @param moduleName Module from which the symbol should be imported. |
| 58 | */ |
| 59 | external = (symbolName: string, moduleName: string): string => { |
| 60 | if (!this._imports.has(moduleName)) { |
| 61 | this._imports.set(moduleName, new Map()); |
| 62 | } |
| 63 | |
| 64 | const symbolsPerModule = this._imports.get(moduleName) as Map<string, string>; |
| 65 | |
| 66 | if (!symbolsPerModule.has(symbolName)) { |
| 67 | symbolsPerModule.set(symbolName, `@@__SCHEMATIC_PLACEHOLDER_${uniqueIdCounter++}__@@`); |
| 68 | } |
| 69 | |
| 70 | return symbolsPerModule.get(symbolName) as string; |
| 71 | }; |
| 72 | |
| 73 | /** |
| 74 | * Produces the necessary rules to transform a `PendingCode` object into valid code. |
| 75 | * @param initialCode Code pending transformed. |
| 76 | * @param filePath Path of the file in which the code will be inserted. |
| 77 | */ |
| 78 | static transformPendingCode( |
| 79 | initialCode: PendingCode, |
| 80 | filePath: string, |
| 81 | ): { code: PendingCode; rules: Rule[] } { |
| 82 | const code = { ...initialCode }; |
| 83 | const rules: Rule[] = []; |
| 84 | |
| 85 | code.imports.forEach((symbols, moduleName) => { |
| 86 | symbols.forEach((placeholder, symbolName) => { |
| 87 | rules.push((tree: Tree) => { |
| 88 | const recorder = tree.beginUpdate(filePath); |
| 89 | const sourceFile = ts.createSourceFile( |
| 90 | filePath, |
| 91 | tree.readText(filePath), |
| 92 | ts.ScriptTarget.Latest, |
| 93 | true, |
| 94 | ); |
| 95 | |
| 96 | // Note that this could still technically clash if there's a top-level symbol called |