1
1
import * as ts from 'typescript' ;
2
-
3
2
import { getProgram , ICompilerOptions } from './compiler-helper' ;
4
- import { applyReplacements , Replacement } from './replacement ' ;
3
+ import { transformSourceFile } from './transformer ' ;
5
4
6
5
export interface IInstrumentOptions extends ICompilerOptions {
7
6
instrumentCallExpressions ?: boolean ;
@@ -16,184 +15,19 @@ export interface IExtraOptions {
16
15
thisNeedsComma ?: boolean ;
17
16
}
18
17
19
- function hasParensAroundArguments ( node : ts . FunctionLike ) {
20
- if ( ts . isArrowFunction ( node ) ) {
21
- return (
22
- node . parameters . length !== 1 ||
23
- node
24
- . getText ( )
25
- . substr ( 0 , node . equalsGreaterThanToken . getStart ( ) - node . getStart ( ) )
26
- . includes ( '(' )
27
- ) ;
28
- } else {
29
- return true ;
30
- }
31
- }
32
-
33
- function visit (
34
- node : ts . Node ,
35
- replacements : Replacement [ ] ,
36
- fileName : string ,
37
- options : IInstrumentOptions ,
38
- program ?: ts . Program ,
39
- semanticDiagnostics ?: ReadonlyArray < ts . Diagnostic > ,
40
- ) {
41
- const isArrow = ts . isArrowFunction ( node ) ;
42
- if ( ts . isFunctionDeclaration ( node ) || ts . isMethodDeclaration ( node ) || ts . isArrowFunction ( node ) ) {
43
- if ( node . body ) {
44
- const needsThisInstrumentation =
45
- options . instrumentImplicitThis &&
46
- program &&
47
- semanticDiagnostics &&
48
- semanticDiagnostics . find ( ( diagnostic ) => {
49
- if (
50
- diagnostic . code === 2683 &&
51
- diagnostic . file &&
52
- diagnostic . file . fileName === node . getSourceFile ( ) . fileName &&
53
- diagnostic . start
54
- ) {
55
- if ( node . body && ts . isBlock ( node . body ) ) {
56
- const body = node . body as ts . FunctionBody ;
57
- return (
58
- body . statements . find ( ( statement ) => {
59
- return (
60
- diagnostic . start !== undefined &&
61
- statement . pos <= diagnostic . start &&
62
- diagnostic . start <= statement . end
63
- ) ;
64
- } ) !== undefined
65
- ) ;
66
- } else {
67
- const body = node . body as ts . Expression ;
68
- return body . pos <= diagnostic . start && diagnostic . start <= body . end ;
69
- }
70
- }
71
- return false ;
72
- } ) !== undefined ;
73
- if ( needsThisInstrumentation ) {
74
- const opts : IExtraOptions = { thisType : true } ;
75
- if ( node . parameters . length > 0 ) {
76
- opts . thisNeedsComma = true ;
77
- }
78
- const params = [
79
- JSON . stringify ( 'this' ) ,
80
- 'this' ,
81
- node . parameters . pos ,
82
- JSON . stringify ( fileName ) ,
83
- JSON . stringify ( opts ) ,
84
- ] ;
85
- const instrumentExpr = `$_$twiz(${ params . join ( ',' ) } )` ;
86
-
87
- replacements . push ( Replacement . insert ( node . body . getStart ( ) + 1 , `${ instrumentExpr } ;` ) ) ;
88
- }
89
- }
90
-
91
- const isShortArrow = ts . isArrowFunction ( node ) && ! ts . isBlock ( node . body ) ;
92
- for ( const param of node . parameters ) {
93
- if ( ! param . type && ! param . initializer && node . body ) {
94
- const typeInsertionPos = param . name . getEnd ( ) + ( param . questionToken ? 1 : 0 ) ;
95
- const opts : IExtraOptions = { } ;
96
- if ( isArrow ) {
97
- opts . arrow = true ;
98
- }
99
- if ( ! hasParensAroundArguments ( node ) ) {
100
- opts . parens = [ node . parameters [ 0 ] . getStart ( ) , node . parameters [ 0 ] . getEnd ( ) ] ;
101
- }
102
- const params = [
103
- JSON . stringify ( param . name . getText ( ) ) ,
104
- param . name . getText ( ) ,
105
- typeInsertionPos ,
106
- JSON . stringify ( fileName ) ,
107
- JSON . stringify ( opts ) ,
108
- ] ;
109
- const instrumentExpr = `$_$twiz(${ params . join ( ',' ) } )` ;
110
- if ( isShortArrow ) {
111
- replacements . push ( Replacement . insert ( node . body . getStart ( ) , `(${ instrumentExpr } ,` ) ) ;
112
- replacements . push ( Replacement . insert ( node . body . getEnd ( ) , `)` , 10 ) ) ;
113
- } else {
114
- replacements . push ( Replacement . insert ( node . body . getStart ( ) + 1 , `${ instrumentExpr } ;` ) ) ;
115
- }
116
- }
117
- }
118
- }
119
-
120
- if (
121
- options . instrumentCallExpressions &&
122
- ts . isCallExpression ( node ) &&
123
- node . expression . getText ( ) !== 'require.context'
124
- ) {
125
- for ( const arg of node . arguments ) {
126
- if ( ! ts . isStringLiteral ( arg ) && ! ts . isNumericLiteral ( arg ) && ! ts . isSpreadElement ( arg ) ) {
127
- replacements . push ( Replacement . insert ( arg . getStart ( ) , '$_$twiz.track(' ) ) ;
128
- replacements . push ( Replacement . insert ( arg . getEnd ( ) , `,${ JSON . stringify ( fileName ) } ,${ arg . getStart ( ) } )` ) ) ;
129
- }
130
- }
131
- }
132
-
133
- if (
134
- ts . isPropertyDeclaration ( node ) &&
135
- ts . isIdentifier ( node . name ) &&
136
- ! node . type &&
137
- ! node . initializer &&
138
- ! node . decorators
139
- ) {
140
- const name = node . name . getText ( ) ;
141
- const params = [
142
- JSON . stringify ( node . name . getText ( ) ) ,
143
- 'value' ,
144
- node . name . getEnd ( ) + ( node . questionToken ? 1 : 0 ) ,
145
- JSON . stringify ( fileName ) ,
146
- JSON . stringify ( { } ) ,
147
- ] ;
148
- const instrumentExpr = `$_$twiz(${ params . join ( ',' ) } );` ;
149
- const preamble = `
150
- get ${ name } () { return this._twiz_private_${ name } ; }
151
- set ${ name } (value: any) { ${ instrumentExpr } this._twiz_private_${ name } = value; }
152
- ` ;
153
- // we need to remove any readonly modifiers, otherwise typescript will not let us update
154
- // our _twiz_private_... variable inside the setter.
155
- for ( const modifier of node . modifiers || [ ] ) {
156
- if ( modifier . kind === ts . SyntaxKind . ReadonlyKeyword ) {
157
- replacements . push ( Replacement . delete ( modifier . getStart ( ) , modifier . getEnd ( ) ) ) ;
158
- }
159
- }
160
- if ( node . getStart ( ) === node . name . getStart ( ) ) {
161
- replacements . push ( Replacement . insert ( node . getStart ( ) , `${ preamble } _twiz_private_` ) ) ;
162
- } else {
163
- replacements . push ( Replacement . insert ( node . name . getStart ( ) , '_twiz_private_' ) ) ;
164
- replacements . push ( Replacement . insert ( node . getStart ( ) , `${ preamble } ` ) ) ;
165
- }
166
- }
167
-
168
- node . forEachChild ( ( child ) => visit ( child , replacements , fileName , options , program , semanticDiagnostics ) ) ;
169
- }
170
-
171
- const declaration = `
172
- declare function $_$twiz(name: string, value: any, pos: number, filename: string, opts: any): void;
173
- declare namespace $_$twiz {
174
- function track<T>(value: T, filename: string, offset: number): T;
175
- function track(value: any, filename: string, offset: number): any;
176
- }
177
- ` ;
178
-
179
18
export function instrument ( source : string , fileName : string , options ?: IInstrumentOptions ) {
180
19
const instrumentOptions : IInstrumentOptions = {
181
20
instrumentCallExpressions : false ,
182
21
instrumentImplicitThis : false ,
183
22
skipTwizDeclarations : false ,
184
23
...options ,
185
24
} ;
25
+
186
26
const program : ts . Program | undefined = getProgram ( instrumentOptions ) ;
187
27
const sourceFile = program
188
28
? program . getSourceFile ( fileName )
189
29
: ts . createSourceFile ( fileName , source , ts . ScriptTarget . Latest , true ) ;
190
- const replacements = [ ] as Replacement [ ] ;
191
- if ( sourceFile ) {
192
- const semanticDiagnostics = program ? program . getSemanticDiagnostics ( sourceFile ) : undefined ;
193
- visit ( sourceFile , replacements , fileName , instrumentOptions , program , semanticDiagnostics ) ;
194
- }
195
- if ( replacements . length && ! instrumentOptions . skipTwizDeclarations ) {
196
- replacements . push ( Replacement . insert ( 0 , declaration ) ) ;
197
- }
198
- return applyReplacements ( source , replacements ) ;
30
+
31
+ const transformed = transformSourceFile ( sourceFile , options , program ) ;
32
+ return ts . createPrinter ( ) . printFile ( transformed ) ;
199
33
}
0 commit comments