( ctx: ts.TransformationContext, sf: ts.SourceFile, opts?: Opts )
| 128 | } |
| 129 | |
| 130 | function visitSourceFile( |
| 131 | ctx: ts.TransformationContext, |
| 132 | sf: ts.SourceFile, |
| 133 | opts?: Opts |
| 134 | ): ts.SourceFile { |
| 135 | /** |
| 136 | * Find the 1st node that we can inject hoisted variable. This means: |
| 137 | * 1. Pass the prologue directive |
| 138 | * 2. Pass shebang (not a node) |
| 139 | * 3. Pass top level comments (not a node) |
| 140 | * 4. Pass React import (bc hoisted var uses React) |
| 141 | */ |
| 142 | const firstHoistableNodeIndex = sf.statements.findIndex( |
| 143 | node => isNotPrologueDirective(node) && isReactImport(node, sf) |
| 144 | ); |
| 145 | // Can't find where to hoist |
| 146 | if (!~firstHoistableNodeIndex) { |
| 147 | return sf; |
| 148 | } |
| 149 | |
| 150 | const hoistedVariables: ts.VariableStatement[] = []; |
| 151 | const elVisitor = constantElementVisitor(ctx, hoistedVariables); |
| 152 | |
| 153 | // We assume we only care about nodes after React import |
| 154 | const transformedStatements = sf.statements |
| 155 | .slice(firstHoistableNodeIndex + 1, sf.statements.length) |
| 156 | .map(node => ts.visitNode(node, elVisitor)); |
| 157 | if (opts.verbose) { |
| 158 | console.log( |
| 159 | `Hoisting ${hoistedVariables.length} elements in ${sf.fileName}:` |
| 160 | ); |
| 161 | hoistedVariables.forEach(n => |
| 162 | console.log(n.declarationList.declarations[0].initializer.getText(sf)) |
| 163 | ); |
| 164 | } |
| 165 | |
| 166 | return ts.updateSourceFileNode( |
| 167 | sf, |
| 168 | ts.setTextRange( |
| 169 | ts.createNodeArray([ |
| 170 | ...sf.statements.slice(0, firstHoistableNodeIndex + 1), |
| 171 | // Inject hoisted variables |
| 172 | ...hoistedVariables, |
| 173 | ...transformedStatements |
| 174 | ]), |
| 175 | sf.statements |
| 176 | ) |
| 177 | ); |
| 178 | } |
| 179 | |
| 180 | export interface Opts { |
| 181 | verbose?: boolean; |
no test coverage detected