0
# Code Instrumentation
1
2
Code instrumentation transforms JavaScript source code to track statement, line, function, and branch coverage during execution. The Instrumenter class provides both synchronous and asynchronous APIs for instrumenting code.
3
4
## Capabilities
5
6
### Instrumenter Class
7
8
Creates an instrumenter instance for transforming JavaScript code to add coverage tracking.
9
10
```javascript { .api }
11
/**
12
* Creates an instrumenter for transforming JavaScript code
13
* @param {InstrumentOptions} options - Configuration options for instrumentation
14
*/
15
class Instrumenter {
16
constructor(options?: InstrumentOptions);
17
18
/**
19
* Synchronously instruments JavaScript code
20
* @param {string} code - The JavaScript source code to instrument
21
* @param {string} filename - The filename/path for the code (used in coverage data)
22
* @returns {string} The instrumented JavaScript code
23
*/
24
instrumentSync(code: string, filename: string): string;
25
26
/**
27
* Asynchronously instruments JavaScript code
28
* @param {string} code - The JavaScript source code to instrument
29
* @param {string} filename - The filename/path for the code
30
* @param {Function} callback - Callback function (err, instrumentedCode) => void
31
*/
32
instrument(code: string, filename: string, callback: (err: Error | null, code?: string) => void): void;
33
34
/**
35
* Instruments an AST directly (advanced usage)
36
* @param {Object} program - The parsed AST program node
37
* @param {string} filename - The filename/path for the code
38
* @param {string} originalCode - The original source code
39
* @returns {string} The instrumented JavaScript code
40
*/
41
instrumentASTSync(program: Object, filename: string, originalCode: string): string;
42
43
/**
44
* Returns coverage object template for the last instrumented file
45
* @returns {Object} Zero-coverage object with all statements, functions, branches, and lines
46
*/
47
lastFileCoverage(): Object;
48
49
/**
50
* Returns source map for the last instrumented file
51
* @returns {Object|null} Source map object or null if not available
52
*/
53
lastSourceMap(): Object | null;
54
55
/**
56
* Filters comments to extract Istanbul ignore hints
57
* @param {Object[]} comments - Array of comment objects from esprima
58
* @returns {Object[]} Filtered array of Istanbul hint comments
59
*/
60
filterHints(comments: Object[]): Object[];
61
62
/**
63
* Extracts the current hint for an AST node based on position
64
* @param {Object} node - AST node to check for hints
65
* @returns {Object|null} Current hint object or null
66
*/
67
extractCurrentHint(node: Object): Object | null;
68
69
/**
70
* Fixes column positions after code wrapping (internal method)
71
* @param {Object} coverState - Coverage state object to fix
72
*/
73
fixColumnPositions(coverState: Object): void;
74
75
/**
76
* Generates the preamble code for coverage tracking (internal method)
77
* @param {string} sourceCode - Original source code
78
* @param {boolean} emitUseStrict - Whether to emit 'use strict'
79
* @returns {string} Preamble code string
80
*/
81
getPreamble(sourceCode: string, emitUseStrict: boolean): string;
82
83
/**
84
* Starts ignoring coverage for subsequent code (internal method)
85
*/
86
startIgnore(): void;
87
88
/**
89
* Ends ignoring coverage for subsequent code (internal method)
90
*/
91
endIgnore(): void;
92
93
/**
94
* Converts AST nodes to block statements (internal method)
95
* @param {Object} node - AST node to convert
96
* @returns {Object} Block statement AST node
97
*/
98
convertToBlock(node: Object): Object;
99
100
/**
101
* Converts arrow function expressions to block form (internal method)
102
* @param {Object} node - Arrow function AST node
103
*/
104
arrowBlockConverter(node: Object): void;
105
106
/**
107
* Ensures try-catch handler compatibility (internal method)
108
* @param {Object} node - Try statement AST node
109
*/
110
paranoidHandlerCheck(node: Object): void;
111
}
112
113
interface InstrumentOptions {
114
/** Name of the global variable to store coverage data (default: '__coverage__') */
115
coverageVariable?: string;
116
117
/** Whether to embed original source code in coverage object (default: false) */
118
embedSource?: boolean;
119
120
/** Whether to preserve comments in instrumented output (default: false) */
121
preserveComments?: boolean;
122
123
/** Whether to emit readable code instead of compact (default: false) */
124
noCompact?: boolean;
125
126
/** Whether the code uses ES6 import/export statements (default: false) */
127
esModules?: boolean;
128
129
/** Whether to skip auto-wrapping code in anonymous function (default: false) */
130
noAutoWrap?: boolean;
131
132
/** Options passed directly to escodegen for code generation */
133
codeGenerationOptions?: Object;
134
135
/** Enable debug mode for instrumenter (default: false) */
136
debug?: boolean;
137
138
/** Enable debug mode for AST walker (default: false) */
139
walkDebug?: boolean;
140
}
141
```
142
143
**Usage Examples:**
144
145
```javascript
146
const fs = require('fs');
147
const { Instrumenter } = require('istanbul');
148
149
// Basic instrumentation
150
const instrumenter = new Instrumenter();
151
const code = fs.readFileSync('app.js', 'utf8');
152
const instrumentedCode = instrumenter.instrumentSync(code, 'app.js');
153
154
// Instrumentation with options
155
const instrumenter2 = new Instrumenter({
156
coverageVariable: '__myCoverage__',
157
embedSource: true,
158
preserveComments: true,
159
esModules: true
160
});
161
162
// Async instrumentation
163
instrumenter.instrument(code, 'app.js', (err, instrumentedCode) => {
164
if (err) throw err;
165
console.log('Code instrumented successfully');
166
167
// Get coverage template
168
const coverageObject = instrumenter.lastFileCoverage();
169
console.log('Coverage template:', coverageObject);
170
});
171
172
// Advanced: Direct AST instrumentation
173
const esprima = require('esprima');
174
const ast = esprima.parseScript(code);
175
const instrumentedFromAST = instrumenter.instrumentASTSync(ast, 'app.js', code);
176
```
177
178
### Coverage Variable Structure
179
180
The instrumented code populates a global coverage variable (default: `__coverage__`) with the following structure:
181
182
```javascript { .api }
183
interface CoverageObject {
184
[filename: string]: FileCoverage;
185
}
186
187
interface FileCoverage {
188
/** File path */
189
path: string;
190
191
/** Statement coverage data */
192
s: { [statementId: string]: number };
193
194
/** Branch coverage data */
195
b: { [branchId: string]: number[] };
196
197
/** Function coverage data */
198
f: { [functionId: string]: number };
199
200
/** Function name mapping */
201
fnMap: { [functionId: string]: FunctionMapping };
202
203
/** Statement location mapping */
204
statementMap: { [statementId: string]: Location };
205
206
/** Branch location mapping */
207
branchMap: { [branchId: string]: BranchMapping };
208
209
/** Line coverage data (derived) */
210
l?: { [lineNumber: string]: number };
211
212
/** Original source code (if embedSource is true) */
213
code?: string[];
214
}
215
216
interface Location {
217
start: { line: number; column: number };
218
end: { line: number; column: number };
219
}
220
221
interface FunctionMapping {
222
name: string;
223
decl: Location;
224
loc: Location;
225
line: number;
226
}
227
228
interface BranchMapping {
229
loc: Location;
230
type: 'if' | 'switch' | 'cond-expr' | 'default-arg';
231
locations: Location[];
232
line: number;
233
}
234
```
235
236
### Instrumentation Process
237
238
The instrumentation process involves:
239
240
1. **Parse**: Code is parsed into an Abstract Syntax Tree (AST)
241
2. **Transform**: AST is traversed and modified to add coverage tracking
242
3. **Generate**: Modified AST is converted back to JavaScript code
243
4. **Template**: Coverage template is created with all trackable elements
244
245
The instrumented code will automatically populate the global coverage variable as it executes, tracking:
246
- **Statement coverage**: Which statements were executed
247
- **Branch coverage**: Which conditional branches were taken
248
- **Function coverage**: Which functions were called
249
- **Line coverage**: Which lines contained executed code
250
251
### Error Handling
252
253
```javascript
254
try {
255
const instrumentedCode = instrumenter.instrumentSync(code, filename);
256
} catch (error) {
257
if (error.name === 'SyntaxError') {
258
console.error('JavaScript syntax error in:', filename);
259
} else {
260
console.error('Instrumentation failed:', error.message);
261
}
262
}
263
```
264
265
Common instrumentation errors include:
266
- **SyntaxError**: Invalid JavaScript syntax in source code
267
- **TypeError**: Invalid options passed to instrumenter
268
- **Error**: AST transformation failures or code generation issues