@teamscale/javascript-instrumenter 0.0.1-beta.40 → 0.0.1-beta.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -39,30 +39,6 @@ or via `npx` by running
39
39
  npx @teamscale/javascript-instrumenter
40
40
  ```
41
41
 
42
- ### Instrumenting a Single File
43
-
44
- Adds information for coverage tracking to a single file, either
45
- in-place (by replacing the previous content), or by producing a new file.
46
-
47
- An in-place transformation is only allowed if the given file is already
48
- the result of an automatic transformation process, that is, if a source-map is available.
49
-
50
- If the source-map is not provided as an explicit argument, either
51
- the file must contain source-map information, or the source-map file
52
- must be placed along with the source file in the same directory.
53
-
54
- ```
55
- yarn instrumenter --inplace ./the/path/to/the/file.js
56
- ```
57
-
58
- ```
59
- yarn instrumenter --inplace ./the/path/to/the/file.js --source-map ./the/path/to/the/source.map
60
- ```
61
-
62
- ```
63
- yarn instrumenter ./the/path/to/the/file.js --to ./the/file/path/to/write/to.js
64
- ```
65
-
66
42
  ## Limitations
67
43
 
68
44
  This tool inherits most of the limitations of IstanbulJs, including
package/dist/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@teamscale/javascript-instrumenter",
3
- "version": "0.0.1-beta.40",
4
- "description": "Istanbul-based coverage instrumenter with coverage forwarding via WebSockets",
3
+ "version": "0.0.1-beta.43",
4
+ "description": "JavaScript coverage instrumenter with coverage forwarding to a collector process",
5
5
  "main": "dist/src/main.js",
6
6
  "bin": "dist/src/main.js",
7
7
  "types": "dist/src/main.d.ts",
@@ -17,7 +17,7 @@
17
17
  "build": "tsc --project tsconfig.json && yarn buildVaccine",
18
18
  "buildVaccine": "node esbuild.mjs",
19
19
  "instrumenter": "node dist/src/main.js",
20
- "test": "yarn build && NODE_OPTIONS='--experimental-vm-modules' jest --forceExit --coverage --silent=true --detectOpenHandles"
20
+ "test": "yarn build && NODE_OPTIONS='--experimental-vm-modules --max-old-space-size=8192' jest --forceExit --coverage --silent=true --detectOpenHandles"
21
21
  },
22
22
  "files": [
23
23
  "dist/**/*"
@@ -49,7 +49,7 @@
49
49
  "dependencies": {
50
50
  "@babel/generator": "^7.18.2",
51
51
  "@babel/parser": "^7.18.5",
52
- "@babel/traverse": "^7.18.5",
52
+ "@babel/traverse": "^7.18.6",
53
53
  "@babel/types": "^7.18.4",
54
54
  "@cqse/commons": "^0.0.1-beta.1",
55
55
  "@types/micromatch": "^4.0.2",
@@ -59,10 +59,9 @@
59
59
  "convert-source-map": "^1.7.0",
60
60
  "foreground-child": "^2.0.0",
61
61
  "glob": "^7.1.7",
62
- "istanbul-lib-instrument": "^4.0.0",
62
+ "istanbul-lib-instrument": "^5.2.0",
63
63
  "micromatch": "4.0.4",
64
64
  "mkdirp": "^1.0.4",
65
- "nyc": "^15.1.0",
66
65
  "source-map": "0.7.3",
67
66
  "typescript-optional": "^2.0.1",
68
67
  "unload": "^2.2.0",
@@ -1,4 +1,6 @@
1
- import { CollectorSpecifier, InstrumentationTask, OriginSourcePattern, TaskElement, TaskResult } from './Task';
1
+ import { CollectorSpecifier, InstrumentationTask, OriginSourcePattern, SourceMapReference, TaskElement, TaskResult } from './Task';
2
+ import { RawSourceMap, SourceMapConsumer } from 'source-map';
3
+ import { Optional } from 'typescript-optional';
2
4
  import Logger from 'bunyan';
3
5
  export declare const IS_INSTRUMENTED_TOKEN = "/** $IS_JS_PROFILER_INSTRUMENTED=true **/";
4
6
  /**
@@ -41,22 +43,12 @@ export declare class IstanbulInstrumenter implements IInstrumenter {
41
43
  */
42
44
  instrumentOne(collector: CollectorSpecifier, taskElement: TaskElement, sourcePattern: OriginSourcePattern, dumpOriginsFile: string | undefined): Promise<TaskResult>;
43
45
  private removeUnwantedInstrumentation;
44
- private loadSourceMap;
45
46
  /**
46
47
  * Loads the vaccine from the vaccine file and adjusts some template parameters.
47
48
  *
48
49
  * @param collector - The collector to send coverage information to.
49
50
  */
50
51
  private loadVaccine;
51
- /**
52
- * Should the given file be excluded from the instrumentation,
53
- * based on the source files that have been transpiled into it?
54
- *
55
- * @param pattern - The pattern to match the origin source files.
56
- * @param sourceFile - The bundle file name.
57
- * @param originSourceFiles - The list of files that were transpiled into the bundle.
58
- */
59
- private shouldExcludeFromInstrumentation;
60
52
  /**
61
53
  * @returns whether the given file is supported for instrumentation.
62
54
  */
@@ -66,17 +58,37 @@ export declare class IstanbulInstrumenter implements IInstrumenter {
66
58
  * given task element.
67
59
  */
68
60
  private configurationAlternativesFor;
69
- /**
70
- * Given a source code file, load the corresponding sourcemap.
71
- *
72
- * @param inputSource - The source code that might contain sourcemap comments.
73
- * @param taskFile - The name of the file the `inputSource` is from.
74
- * @param externalSourceMapFile - An external source map file to consider.
75
- */
76
- private loadInputSourceMap;
77
61
  /** Appends all origins from the source map to a given file. Creates the file if it does not exist yet. */
78
62
  private dumpOrigins;
79
63
  /** Clears the dump origins file if it exists, such that it is now ready to be appended for every instrumented file. */
80
64
  private clearDumpOriginsFileIfNeeded;
81
65
  }
66
+ /**
67
+ * Extract the sourcemap from the given source code.
68
+ *
69
+ * @param instrumentedSource - The source code.
70
+ * @param instrumentedSourceFileName - The file name to assume for the file name.
71
+ */
72
+ export declare function loadSourceMap(instrumentedSource: string, instrumentedSourceFileName: string): Promise<SourceMapConsumer | undefined>;
73
+ /**
74
+ * Given a source code file, load the corresponding sourcemap.
75
+ *
76
+ * @param inputSource - The source code that might contain sourcemap comments.
77
+ * @param taskFile - The name of the file the `inputSource` is from.
78
+ * @param externalSourceMapFile - An external source map file to consider.
79
+ */
80
+ export declare function loadInputSourceMap(inputSource: string, taskFile: string, externalSourceMapFile: Optional<SourceMapReference>): RawSourceMap | undefined;
81
+ /**
82
+ * Extract a sourcemap for a given code comment.
83
+ *
84
+ * @param sourcecode - The source code that is scanned for source map comments.
85
+ * @param sourceFilePath - The file name the code was loaded from.
86
+ */
87
+ export declare function sourceMapFromCodeComment(sourcecode: string, sourceFilePath: string): RawSourceMap | undefined;
88
+ /**
89
+ * Read a source map from a source map file.
90
+ *
91
+ * @param mapFilePath
92
+ */
93
+ export declare function sourceMapFromMapFile(mapFilePath: string): RawSourceMap | undefined;
82
94
  //# sourceMappingURL=Instrumenter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Instrumenter.d.ts","sourceRoot":"","sources":["../../../src/instrumenter/Instrumenter.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EAGnB,WAAW,EACX,UAAU,EACV,MAAM,QAAQ,CAAC;AAUhB,OAAO,MAAM,MAAM,QAAQ,CAAC;AAG5B,eAAO,MAAM,qBAAqB,8CAA8C,CAAC;AAEjF;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B;;;;OAIG;IACH,UAAU,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CAC3D;AAED;;GAEG;AACH,qBAAa,oBAAqB,YAAW,aAAa;IACzD;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IAEzC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAS;gBAEX,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IASnD;;OAEG;IACG,UAAU,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,UAAU,CAAC;IAqBhE;;;;;;;OAOG;IACG,aAAa,CAClB,SAAS,EAAE,kBAAkB,EAC7B,WAAW,EAAE,WAAW,EACxB,aAAa,EAAE,mBAAmB,EAClC,eAAe,EAAE,MAAM,GAAG,SAAS,GACjC,OAAO,CAAC,UAAU,CAAC;YAoGR,6BAA6B;YAmC7B,aAAa;IAe3B;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAMnB;;;;;;;OAOG;IACH,OAAO,CAAC,gCAAgC;IAQxC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAK3B;;;OAGG;IACH,OAAO,CAAC,4BAA4B;IAcpC;;;;;;OAMG;IACH,OAAO,CAAC,kBAAkB;IAgB1B,0GAA0G;IAC1G,OAAO,CAAC,WAAW;IASnB,uHAAuH;IACvH,OAAO,CAAC,4BAA4B;CASpC"}
1
+ {"version":3,"file":"Instrumenter.d.ts","sourceRoot":"","sources":["../../../src/instrumenter/Instrumenter.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EAEnB,kBAAkB,EAClB,WAAW,EACX,UAAU,EACV,MAAM,QAAQ,CAAC;AAEhB,OAAO,EAAY,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAOvE,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,MAAM,MAAM,QAAQ,CAAC;AAG5B,eAAO,MAAM,qBAAqB,8CAA8C,CAAC;AAEjF;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B;;;;OAIG;IACH,UAAU,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CAC3D;AAED;;GAEG;AACH,qBAAa,oBAAqB,YAAW,aAAa;IACzD;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IAEzC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAS;gBAEX,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IASnD;;OAEG;IACG,UAAU,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,UAAU,CAAC;IAqBhE;;;;;;;OAOG;IACG,aAAa,CAClB,SAAS,EAAE,kBAAkB,EAC7B,WAAW,EAAE,WAAW,EACxB,aAAa,EAAE,mBAAmB,EAClC,eAAe,EAAE,MAAM,GAAG,SAAS,GACjC,OAAO,CAAC,UAAU,CAAC;YAwGR,6BAA6B;IA8C3C;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAMnB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAK3B;;;OAGG;IACH,OAAO,CAAC,4BAA4B;IAcpC,0GAA0G;IAC1G,OAAO,CAAC,WAAW;IASnB,uHAAuH;IACvH,OAAO,CAAC,4BAA4B;CASpC;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAClC,kBAAkB,EAAE,MAAM,EAC1B,0BAA0B,EAAE,MAAM,GAChC,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAUxC;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CACjC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,qBAAqB,EAAE,QAAQ,CAAC,kBAAkB,CAAC,GACjD,YAAY,GAAG,SAAS,CAU1B;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAsC7G;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAGlF"}
@@ -26,7 +26,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
26
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.IstanbulInstrumenter = exports.IS_INSTRUMENTED_TOKEN = void 0;
29
+ exports.sourceMapFromMapFile = exports.sourceMapFromCodeComment = exports.loadInputSourceMap = exports.loadSourceMap = exports.IstanbulInstrumenter = exports.IS_INSTRUMENTED_TOKEN = void 0;
30
30
  const Task_1 = require("./Task");
31
31
  const commons_1 = require("@cqse/commons");
32
32
  const source_map_1 = require("source-map");
@@ -35,7 +35,7 @@ const fs = __importStar(require("fs"));
35
35
  const mkdirp = __importStar(require("mkdirp"));
36
36
  const path = __importStar(require("path"));
37
37
  const convertSourceMap = __importStar(require("convert-source-map"));
38
- const Cleaner_1 = require("./Cleaner");
38
+ const Postprocessor_1 = require("./Postprocessor");
39
39
  const typescript_optional_1 = require("typescript-optional");
40
40
  const async_1 = __importDefault(require("async"));
41
41
  exports.IS_INSTRUMENTED_TOKEN = '/** $IS_JS_PROFILER_INSTRUMENTED=true **/';
@@ -99,7 +99,7 @@ class IstanbulInstrumenter {
99
99
  let inputSourceMap;
100
100
  try {
101
101
  const instrumenter = istanbul.createInstrumenter(configurationAlternative);
102
- inputSourceMap = this.loadInputSourceMap(inputFileSource, taskElement.fromFile, taskElement.externalSourceMapFile);
102
+ inputSourceMap = loadInputSourceMap(inputFileSource, taskElement.fromFile, taskElement.externalSourceMapFile);
103
103
  // Based on the source maps of the file to instrument, we can now
104
104
  // decide if we should NOT write an instrumented version of it
105
105
  // and use the original code instead and write it to the target path.
@@ -108,26 +108,23 @@ class IstanbulInstrumenter {
108
108
  if (dumpOriginsFile) {
109
109
  this.dumpOrigins(dumpOriginsFile, originSourceFiles);
110
110
  }
111
- if (this.shouldExcludeFromInstrumentation(sourcePattern, taskElement.fromFile, originSourceFiles)) {
112
- writeToFile(taskElement.toFile, inputFileSource);
113
- return new Task_1.TaskResult(0, 1, 0, 0, 0, 0, 0);
114
- }
115
111
  // The main instrumentation (adding coverage statements) is performed now:
116
- instrumentedSource = instrumenter
117
- .instrumentSync(inputFileSource, taskElement.fromFile, inputSourceMap)
118
- .replace(/actualCoverage\s*=\s*coverage\[path\]/g, 'actualCoverage=makeCoverageInterceptor(coverage[path])')
119
- .replace(/new Function\("return this"\)\(\)/g, "typeof window === 'object' ? window : this");
112
+ instrumentedSource = instrumenter.instrumentSync(inputFileSource, taskElement.fromFile, inputSourceMap);
120
113
  this.logger.debug('Instrumentation source maps to:', (_b = instrumenter.lastSourceMap()) === null || _b === void 0 ? void 0 : _b.sources);
121
114
  // In case of a bundle, the initial instrumentation step might have added
122
115
  // too much and undesired instrumentations. Remove them now.
123
- instrumentedSource = await this.removeUnwantedInstrumentation(taskElement, instrumentedSource, configurationAlternative, sourcePattern);
116
+ const instrumentedSourcemap = instrumenter.lastSourceMap();
117
+ let instrumentedAndCleanedSource = await this.removeUnwantedInstrumentation(taskElement, instrumentedSource, configurationAlternative, sourcePattern, instrumentedSourcemap);
118
+ instrumentedAndCleanedSource = instrumentedAndCleanedSource
119
+ .replace(/actualCoverage\s*=\s*coverage\[path\]/g, 'actualCoverage=_$registerCoverageObject(coverage[path])')
120
+ .replace(/new Function\("return this"\)\(\)/g, "typeof window === 'object' ? window : this");
124
121
  // The process also can result in a new source map that we will append in the result.
125
122
  //
126
123
  // `lastSourceMap` === Sourcemap for the last file that was instrumented.
127
124
  finalSourceMap = convertSourceMap.fromObject(instrumenter.lastSourceMap()).toComment();
128
125
  // We now can glue together the final version of the instrumented file.
129
126
  const vaccineSource = this.loadVaccine(collector);
130
- writeToFile(taskElement.toFile, `${exports.IS_INSTRUMENTED_TOKEN} ${vaccineSource} ${instrumentedSource} \n${finalSourceMap}`);
127
+ writeToFile(taskElement.toFile, `${exports.IS_INSTRUMENTED_TOKEN} ${vaccineSource} ${instrumentedAndCleanedSource} \n${finalSourceMap}`);
131
128
  return new Task_1.TaskResult(1, 0, 0, 0, 0, 0, 0);
132
129
  }
133
130
  catch (e) {
@@ -144,35 +141,36 @@ class IstanbulInstrumenter {
144
141
  }
145
142
  return new Task_1.TaskResult(0, 0, 0, 0, 0, 1, 0);
146
143
  }
147
- async removeUnwantedInstrumentation(taskElement, instrumentedSource, configurationAlternative, sourcePattern) {
148
- // Read the source map from the instrumented file
149
- const instrumentedSourceMapConsumer = await this.loadSourceMap(instrumentedSource, taskElement.fromFile);
144
+ async removeUnwantedInstrumentation(taskElement, instrumentedSource, configurationAlternative, sourcePattern, instrumentedSourcemap) {
145
+ const instrumentedSourceMapConsumer = await new source_map_1.SourceMapConsumer(instrumentedSourcemap);
150
146
  // Without a source map, excludes/includes do not work.
151
147
  if (!instrumentedSourceMapConsumer) {
152
148
  return instrumentedSource;
153
149
  }
150
+ const removedInstrumentationFor = new Set();
154
151
  // Remove the unwanted instrumentation
155
- const cleaned = (0, Cleaner_1.cleanSourceCode)(instrumentedSource, configurationAlternative.esModules, location => {
152
+ const cleaned = (0, Postprocessor_1.cleanSourceCode)(instrumentedSource, configurationAlternative.esModules, location => {
156
153
  const originalPosition = instrumentedSourceMapConsumer.originalPositionFor({
157
154
  line: location.start.line,
158
155
  column: location.start.column
159
156
  });
160
157
  if (!originalPosition.source) {
161
- return true;
158
+ return false;
159
+ }
160
+ const isToCover = sourcePattern.isAnyIncluded([originalPosition.source]);
161
+ if (!isToCover) {
162
+ removedInstrumentationFor.add(originalPosition.source);
162
163
  }
163
- return sourcePattern.isAnyIncluded([originalPosition.source]);
164
+ return isToCover;
164
165
  });
166
+ if (removedInstrumentationFor.size) {
167
+ this.logger.info(`Removed from ${taskElement.toFile} instrumentation for:`);
168
+ removedInstrumentationFor.forEach(entry => this.logger.info(entry));
169
+ }
165
170
  // Explicitly free the source map to avoid memory leaks
166
171
  instrumentedSourceMapConsumer.destroy();
167
172
  return cleaned;
168
173
  }
169
- async loadSourceMap(instrumentedSource, instrumentedSourceFileName) {
170
- const instrumentedSourceMap = this.loadInputSourceMap(instrumentedSource, instrumentedSourceFileName, typescript_optional_1.Optional.empty());
171
- if (instrumentedSourceMap) {
172
- return await new source_map_1.SourceMapConsumer(instrumentedSourceMap);
173
- }
174
- return undefined;
175
- }
176
174
  /**
177
175
  * Loads the vaccine from the vaccine file and adjusts some template parameters.
178
176
  *
@@ -183,17 +181,6 @@ class IstanbulInstrumenter {
183
181
  // actual values, for example, the collector to send the coverage information to.
184
182
  return fs.readFileSync(this.vaccineFilePath, 'utf8').replace(/\$REPORT_TO_URL/g, collector.url);
185
183
  }
186
- /**
187
- * Should the given file be excluded from the instrumentation,
188
- * based on the source files that have been transpiled into it?
189
- *
190
- * @param pattern - The pattern to match the origin source files.
191
- * @param sourceFile - The bundle file name.
192
- * @param originSourceFiles - The list of files that were transpiled into the bundle.
193
- */
194
- shouldExcludeFromInstrumentation(pattern, sourceFile, originSourceFiles) {
195
- return !pattern.isAnyIncluded(originSourceFiles);
196
- }
197
184
  /**
198
185
  * @returns whether the given file is supported for instrumentation.
199
186
  */
@@ -209,32 +196,13 @@ class IstanbulInstrumenter {
209
196
  this.logger.debug(`Determining configuration alternatives for ${taskElement.fromFile}`);
210
197
  const baseConfig = {
211
198
  coverageVariable: '__coverage__',
212
- produceSourceMap: true
199
+ produceSourceMap: 'both'
213
200
  };
214
201
  return [
215
202
  { ...baseConfig, ...{ esModules: true } },
216
203
  { ...baseConfig, ...{ esModules: false } }
217
204
  ];
218
205
  }
219
- /**
220
- * Given a source code file, load the corresponding sourcemap.
221
- *
222
- * @param inputSource - The source code that might contain sourcemap comments.
223
- * @param taskFile - The name of the file the `inputSource` is from.
224
- * @param externalSourceMapFile - An external source map file to consider.
225
- */
226
- loadInputSourceMap(inputSourceCode, taskFile, externalSourceMapFile) {
227
- if (externalSourceMapFile.isPresent()) {
228
- const sourceMapOrigin = externalSourceMapFile.get();
229
- if (!(sourceMapOrigin instanceof Task_1.SourceMapFileReference)) {
230
- throw new commons_1.IllegalArgumentException('Type of source map not yet supported!');
231
- }
232
- return sourceMapFromMapFile(sourceMapOrigin.sourceMapFilePath);
233
- }
234
- else {
235
- return sourceMapFromCodeComment(inputSourceCode, taskFile);
236
- }
237
- }
238
206
  /** Appends all origins from the source map to a given file. Creates the file if it does not exist yet. */
239
207
  dumpOrigins(dumpOriginsFile, originSourceFiles) {
240
208
  const jsonContent = JSON.stringify(originSourceFiles, null, 2);
@@ -257,6 +225,40 @@ class IstanbulInstrumenter {
257
225
  }
258
226
  }
259
227
  exports.IstanbulInstrumenter = IstanbulInstrumenter;
228
+ /**
229
+ * Extract the sourcemap from the given source code.
230
+ *
231
+ * @param instrumentedSource - The source code.
232
+ * @param instrumentedSourceFileName - The file name to assume for the file name.
233
+ */
234
+ async function loadSourceMap(instrumentedSource, instrumentedSourceFileName) {
235
+ const instrumentedSourceMap = loadInputSourceMap(instrumentedSource, instrumentedSourceFileName, typescript_optional_1.Optional.empty());
236
+ if (instrumentedSourceMap) {
237
+ return await new source_map_1.SourceMapConsumer(instrumentedSourceMap);
238
+ }
239
+ return undefined;
240
+ }
241
+ exports.loadSourceMap = loadSourceMap;
242
+ /**
243
+ * Given a source code file, load the corresponding sourcemap.
244
+ *
245
+ * @param inputSource - The source code that might contain sourcemap comments.
246
+ * @param taskFile - The name of the file the `inputSource` is from.
247
+ * @param externalSourceMapFile - An external source map file to consider.
248
+ */
249
+ function loadInputSourceMap(inputSource, taskFile, externalSourceMapFile) {
250
+ if (externalSourceMapFile.isPresent()) {
251
+ const sourceMapOrigin = externalSourceMapFile.get();
252
+ if (!(sourceMapOrigin instanceof Task_1.SourceMapFileReference)) {
253
+ throw new commons_1.IllegalArgumentException('Type of source map not yet supported!');
254
+ }
255
+ return sourceMapFromMapFile(sourceMapOrigin.sourceMapFilePath);
256
+ }
257
+ else {
258
+ return sourceMapFromCodeComment(inputSource, taskFile);
259
+ }
260
+ }
261
+ exports.loadInputSourceMap = loadInputSourceMap;
260
262
  /**
261
263
  * Extract a sourcemap for a given code comment.
262
264
  *
@@ -290,11 +292,11 @@ function sourceMapFromCodeComment(sourcecode, sourceFilePath) {
290
292
  // One JS file can refer to several source map files in its comments.
291
293
  failedLoading++;
292
294
  }
293
- if (result) {
294
- return result;
295
- }
296
295
  }
297
296
  } while (matched);
297
+ if (result) {
298
+ return result;
299
+ }
298
300
  if (failedLoading > 0) {
299
301
  throw new commons_1.IllegalArgumentException('None of the referenced source map files loaded!');
300
302
  }
@@ -302,6 +304,7 @@ function sourceMapFromCodeComment(sourcecode, sourceFilePath) {
302
304
  return undefined;
303
305
  }
304
306
  }
307
+ exports.sourceMapFromCodeComment = sourceMapFromCodeComment;
305
308
  /**
306
309
  * Read a source map from a source map file.
307
310
  *
@@ -311,6 +314,7 @@ function sourceMapFromMapFile(mapFilePath) {
311
314
  const content = fs.readFileSync(mapFilePath, 'utf8');
312
315
  return JSON.parse(content);
313
316
  }
317
+ exports.sourceMapFromMapFile = sourceMapFromMapFile;
314
318
  function writeToFile(filePath, fileContent) {
315
319
  mkdirp.sync(path.dirname(filePath));
316
320
  fs.writeFileSync(filePath, fileContent);
@@ -6,4 +6,4 @@ import { SourceLocation } from '@babel/types';
6
6
  * An instrumentation is removed if the hook `makeCoverable` returns `false`.
7
7
  */
8
8
  export declare function cleanSourceCode(code: string, esModules: boolean, makeCoverable: (location: SourceLocation) => boolean): string;
9
- //# sourceMappingURL=Cleaner.d.ts.map
9
+ //# sourceMappingURL=Postprocessor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Postprocessor.d.ts","sourceRoot":"","sources":["../../../src/instrumenter/Postprocessor.ts"],"names":[],"mappings":"AAGA,OAAO,EAQN,cAAc,EAWd,MAAM,cAAc,CAAC;AA0ItB;;;;;GAKG;AACH,wBAAgB,eAAe,CAC9B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,OAAO,EAClB,aAAa,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,OAAO,GAClD,MAAM,CAaR"}
@@ -0,0 +1,254 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.cleanSourceCode = void 0;
7
+ const parser_1 = require("@babel/parser");
8
+ const generator_1 = __importDefault(require("@babel/generator"));
9
+ const traverse_1 = __importDefault(require("@babel/traverse"));
10
+ const types_1 = require("@babel/types");
11
+ const commons_1 = require("@cqse/commons");
12
+ const COVERAGE_OBJ_FUNCTION_NAME_PREFIX = 'cov_';
13
+ function getIstanbulCoverageFunctionDeclarationName(node) {
14
+ var _a;
15
+ if (!(0, types_1.isFunctionDeclaration)(node)) {
16
+ return undefined;
17
+ }
18
+ const functionName = (_a = node.id) === null || _a === void 0 ? void 0 : _a.name;
19
+ if (functionName === null || functionName === void 0 ? void 0 : functionName.startsWith(COVERAGE_OBJ_FUNCTION_NAME_PREFIX)) {
20
+ return functionName;
21
+ }
22
+ else {
23
+ return undefined;
24
+ }
25
+ }
26
+ function createFileIdMappingHandler() {
27
+ const fileIdMap = new Map();
28
+ let fileIdSeq = 0;
29
+ return {
30
+ enterPath(path) {
31
+ var _a;
32
+ if (!(0, types_1.isVariableDeclaration)(path.node)) {
33
+ return;
34
+ }
35
+ const grandParentPath = (_a = path.parentPath) === null || _a === void 0 ? void 0 : _a.parentPath;
36
+ const coverageFunctionName = getIstanbulCoverageFunctionDeclarationName(grandParentPath === null || grandParentPath === void 0 ? void 0 : grandParentPath.node);
37
+ if (grandParentPath && coverageFunctionName) {
38
+ const declaration = path.node;
39
+ if (declaration.declarations.length === 1) {
40
+ const declarator = declaration.declarations[0];
41
+ if ((0, types_1.isIdentifier)(declarator.id) && declarator.id.name === 'hash') {
42
+ // We take note of the hash that is stored within the `cov_*' function.
43
+ const fileIdVarName = `_$fid${fileIdSeq++}`;
44
+ const fileId = declarator.init.value;
45
+ fileIdMap.set(coverageFunctionName, fileIdVarName);
46
+ grandParentPath.insertBefore(newStringConstDeclarationNode(fileIdVarName, fileId));
47
+ }
48
+ }
49
+ }
50
+ },
51
+ getFileHashForCoverageObjectId(coverageObjectId) {
52
+ return fileIdMap.get(coverageObjectId);
53
+ }
54
+ };
55
+ }
56
+ function createPartialInstrumentationHandler(fileIdMappingHandler) {
57
+ return {
58
+ enterPath(path, makeCoverable) {
59
+ if (!(0, types_1.isUpdateExpression)(path.node)) {
60
+ return;
61
+ }
62
+ const increment = extractCoverageIncrement(path.node);
63
+ if (!increment) {
64
+ return;
65
+ }
66
+ const wantCoverageIncrement = path.node.loc && makeCoverable(path.node.loc) && increment.type !== 'function';
67
+ // Add a new coverage instrument if desired
68
+ if (wantCoverageIncrement) {
69
+ const fileIdVarName = fileIdMappingHandler.getFileHashForCoverageObjectId(increment.coverageObjectId);
70
+ if (!fileIdVarName) {
71
+ throw new commons_1.IllegalStateException(`File ID variable for coverage object with ID ${increment.coverageObjectId} not found!`);
72
+ }
73
+ const insertAsExpression = (0, types_1.isSequenceExpression)(path.parent);
74
+ insertNodeBefore(path, newCoverageIncrementNode(fileIdVarName, increment, insertAsExpression));
75
+ }
76
+ // Remove the existing coverage increment node
77
+ path.remove();
78
+ }
79
+ };
80
+ }
81
+ /**
82
+ * Remove IstanbulJs instrumentations based on the given
83
+ * hook `makeCoverable`.
84
+ *
85
+ * An instrumentation is removed if the hook `makeCoverable` returns `false`.
86
+ */
87
+ function cleanSourceCode(code, esModules, makeCoverable) {
88
+ const ast = (0, parser_1.parse)(code, { sourceType: esModules ? 'module' : 'script' });
89
+ const fileIdMappingHandler = createFileIdMappingHandler();
90
+ const partialInstrumentationHandler = createPartialInstrumentationHandler(fileIdMappingHandler);
91
+ (0, traverse_1.default)(ast, {
92
+ enter(path) {
93
+ fileIdMappingHandler.enterPath(path);
94
+ partialInstrumentationHandler.enterPath(path, makeCoverable);
95
+ }
96
+ });
97
+ return (0, generator_1.default)(ast, {}, code).code;
98
+ }
99
+ exports.cleanSourceCode = cleanSourceCode;
100
+ /**
101
+ * We cannot just run `path.insertBefore` to add a new element to an AST.
102
+ * See https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md#toc-inserting-a-sibling-node .
103
+ *
104
+ * Special handling for some container nodes is needed.
105
+ */
106
+ function insertNodeBefore(path, toInsert) {
107
+ if ((0, types_1.isSequenceExpression)(path.parent)) {
108
+ path.parentPath.unshiftContainer('expressions', [toInsert]);
109
+ }
110
+ else {
111
+ path.insertBefore(toInsert);
112
+ }
113
+ }
114
+ /**
115
+ * Creates a new string constant AST node.
116
+ */
117
+ function newStringConstDeclarationNode(name, value) {
118
+ return {
119
+ type: 'VariableDeclaration',
120
+ kind: 'const',
121
+ declarations: [
122
+ {
123
+ type: 'VariableDeclarator',
124
+ id: {
125
+ type: 'Identifier',
126
+ name
127
+ },
128
+ init: {
129
+ type: 'StringLiteral',
130
+ value
131
+ }
132
+ }
133
+ ]
134
+ };
135
+ }
136
+ /**
137
+ * Creates a new coverage increment statement.
138
+ */
139
+ function newCoverageIncrementNode(fileIdVarName, increment, asExpression) {
140
+ let expression;
141
+ if (increment.type === 'branch') {
142
+ expression = newBranchCoverageIncrementExpression(fileIdVarName, increment);
143
+ }
144
+ else if (increment.type === 'statement') {
145
+ expression = newStatementCoverageIncrementExpression(fileIdVarName, increment);
146
+ }
147
+ else {
148
+ throw new Error(`Unexpected coverage increment type: ${increment.type}`);
149
+ }
150
+ if (asExpression) {
151
+ return expression;
152
+ }
153
+ return {
154
+ type: 'ExpressionStatement',
155
+ expression
156
+ };
157
+ }
158
+ /**
159
+ * Create a branch coverage increment node.
160
+ */
161
+ function newBranchCoverageIncrementExpression(fileIdVarName, increment) {
162
+ return {
163
+ type: 'CallExpression',
164
+ callee: { type: 'Identifier', name: '_$brCov' },
165
+ arguments: [
166
+ { type: 'Identifier', name: fileIdVarName },
167
+ { type: 'NumericLiteral', value: increment.branchId },
168
+ { type: 'NumericLiteral', value: increment.locationId }
169
+ ]
170
+ };
171
+ }
172
+ /**
173
+ * Create a statement coverage increment node.
174
+ */
175
+ function newStatementCoverageIncrementExpression(fileIdVarName, increment) {
176
+ return {
177
+ type: 'CallExpression',
178
+ callee: { type: 'Identifier', name: '_$stmtCov' },
179
+ arguments: [
180
+ { type: 'Identifier', name: fileIdVarName },
181
+ { type: 'NumericLiteral', value: increment.statementId }
182
+ ]
183
+ };
184
+ }
185
+ /**
186
+ * Returns the call expression from `cov_2pvvu1hl8v().b[2][0]++;` if
187
+ * the given UpdateExpression is a branch coverage update expression.
188
+ */
189
+ function extractBranchCoverageIncrement(expr) {
190
+ if (expr.operator === '++' &&
191
+ (0, types_1.isMemberExpression)(expr.argument) &&
192
+ (0, types_1.isMemberExpression)(expr.argument.object) &&
193
+ (0, types_1.isMemberExpression)(expr.argument.object.object) &&
194
+ (0, types_1.isCallExpression)(expr.argument.object.object.object) &&
195
+ isCoverageObjectCall(expr.argument.object.object.object)) {
196
+ const coverageObjectId = expr.argument.object.object.object.callee.name;
197
+ const branchId = expr.argument.object.property.value;
198
+ const locationId = expr.argument.property.value;
199
+ return { type: 'branch', branchId, locationId, coverageObjectId };
200
+ }
201
+ return null;
202
+ }
203
+ /**
204
+ * Returns the call expression from `cov_104fq7oo4i().s[0]++;` if
205
+ * the given UpdateExpression is a statement coverage update expression.
206
+ */
207
+ function extractStatementCoverageIncrement(expr) {
208
+ if (expr.operator === '++' &&
209
+ (0, types_1.isMemberExpression)(expr.argument) &&
210
+ (0, types_1.isMemberExpression)(expr.argument.object) &&
211
+ (0, types_1.isCallExpression)(expr.argument.object.object) &&
212
+ (0, types_1.isIdentifier)(expr.argument.object.property) &&
213
+ expr.argument.object.property.name === 's' &&
214
+ isCoverageObjectCall(expr.argument.object.object)) {
215
+ const coverageObjectId = expr.argument.object.object.callee.name;
216
+ const statementId = expr.argument.property.value;
217
+ return { type: 'statement', statementId, coverageObjectId };
218
+ }
219
+ return null;
220
+ }
221
+ /**
222
+ * Returns the call expression from `cov_104fq7oo4i().f[0]++;` if
223
+ * the given UpdateExpression is a function coverage update expression.
224
+ */
225
+ function extractFunctionCoverageIncrement(expr) {
226
+ if (expr.operator === '++' &&
227
+ (0, types_1.isMemberExpression)(expr.argument) &&
228
+ (0, types_1.isMemberExpression)(expr.argument.object) &&
229
+ (0, types_1.isCallExpression)(expr.argument.object.object) &&
230
+ (0, types_1.isIdentifier)(expr.argument.object.property) &&
231
+ expr.argument.object.property.name === 'f' &&
232
+ isCoverageObjectCall(expr.argument.object.object)) {
233
+ const coverageObjectId = expr.argument.object.object.callee.name;
234
+ const functionId = expr.argument.property.value;
235
+ return { type: 'function', functionId, coverageObjectId };
236
+ }
237
+ return null;
238
+ }
239
+ /**
240
+ * Given an `UpdateExpression` extract the call expression returning the coverage object.
241
+ */
242
+ function extractCoverageIncrement(expr) {
243
+ var _a, _b;
244
+ return ((_b = (_a = extractBranchCoverageIncrement(expr)) !== null && _a !== void 0 ? _a : extractStatementCoverageIncrement(expr)) !== null && _b !== void 0 ? _b : extractFunctionCoverageIncrement(expr));
245
+ }
246
+ /**
247
+ * Check if the given call expression is a coverage call expression.
248
+ * If this is not the case return `undefined`, and the call expression itself otherwise.
249
+ */
250
+ function isCoverageObjectCall(callExpression) {
251
+ return (callExpression !== undefined &&
252
+ (0, types_1.isIdentifier)(callExpression.callee) &&
253
+ callExpression.callee.name.startsWith(COVERAGE_OBJ_FUNCTION_NAME_PREFIX));
254
+ }
package/dist/vaccine.js CHANGED
@@ -1 +1 @@
1
- (()=>{var L=Object.create;var p=Object.defineProperty;var D=Object.getOwnPropertyDescriptor;var H=Object.getOwnPropertyNames;var U=Object.getPrototypeOf,B=Object.prototype.hasOwnProperty;var P=e=>p(e,"__esModule",{value:!0});var E=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var j=(e,t,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of H(t))!B.call(e,r)&&r!=="default"&&p(e,r,{get:()=>t[r],enumerable:!(n=D(t,r))||n.enumerable});return e},S=e=>j(P(p(e!=null?L(U(e)):{},"default",e&&e.__esModule&&"default"in e?{get:()=>e.default,enumerable:!0}:{value:e,enumerable:!0})),e);var O=E((re,R)=>{R.exports=!1});var $=E(()=>{});function m(e){let t=new Blob([e],{type:"text/javascript"}),n=URL.createObjectURL(t),r=new Worker(n);return URL.revokeObjectURL(n),r}function g(){return m('var a=class{constructor(e){this.cachedMessages=[];this.url=e,this.socket=this.createSocket()}createSocket(){let e=new WebSocket(this.url);return e.onopen=()=>this.onopen(),e.onclose=()=>this.onclose(),e}onclose(){this.socket=this.createSocket()}onopen(){console.log("Connection to Coverage Collector established."),this.cachedMessages.forEach(e=>this.socket.send(e)),this.cachedMessages=[]}send(e){this.socket.readyState===WebSocket.OPEN?this.socket.send(e):(this.cachedMessages.push(e),this.cachedMessages.length%500===0&&console.log(`More than ${this.cachedMessages.length} messages are queued to be sent.`))}};var E=20,v=1e3,g=class{constructor(e,t){this.milliseconds=e;this.onCountedToZero=t;this.timerHandle=null}restartCountdown(){this.stopCountdown(),this.timerHandle=self.setTimeout(()=>{this.stopCountdown(),this.onCountedToZero()},this.milliseconds)}stopCountdown(){this.timerHandle!==null&&(self.clearTimeout(this.timerHandle),this.timerHandle=null)}},c=class{constructor(e){this.socket=e,this.cachedCoveredRanges=new Map,this.numberOfCachedPositions=0,this.flushCountdown=new g(v,()=>this.flush())}addRange(e,t){let o=this.cachedCoveredRanges.get(e);o||(o=new Set,this.cachedCoveredRanges.set(e,o)),o.add(t),this.numberOfCachedPositions+=1,this.flushCountdown.restartCountdown(),this.numberOfCachedPositions>=E&&this.flush()}flush(){this.numberOfCachedPositions!==0&&(this.flushCountdown.stopCountdown(),this.cachedCoveredRanges.forEach((e,t)=>{let o=Array.from(e).map(n=>`${n.start.line}:${n.start.column}:${n.end.line}:${n.end.column}`);this.socket.send(`${"c"} ${t} ${o.join(" ")}`),e.clear()}),this.cachedCoveredRanges.clear(),this.numberOfCachedPositions=0)}};var C="s",m="b";console.log("Starting coverage forwarding worker.");var p=new a("$REPORT_TO_URL/socket"),d=new c(p),h=new Map;onmessage=s=>{let e=s.data;if(e.startsWith("s"))p.send(e);else if(e.startsWith("i")){let t=JSON.parse(e.substring(2));h.set(t.hash,t),console.info(`Received coverage mapping information for "${t.hash}".`)}else e.startsWith("u")?S(e):e==="unload"?d.flush():console.error(`No handler for message: ${e}`)};function S(s){var l;let e=s.split(" ");if(e.length<4||h===null)return;let t=e[1],o=e[2],n=h.get(t);if(!n){console.log(`No coverage mapping information for ${t} available!`);return}if(o===C){let i=e[3],r=n.statementMap[i];r&&d.addRange(t,r)}else if(o===m){let i=e[3],r=Number.parseInt(e[4]),u=(l=n.branchMap[i])==null?void 0:l.locations[r];u&&d.addRange(t,u)}}\n')}var b="s",w="b";var a;(function(o){o.MESSAGE_TYPE_SOURCEMAP="s",o.MESSAGE_TYPE_COVERAGE="c",o.ISTANBUL_COV_OBJECT="i",o.UNRESOLVED_CODE_ENTITY="u"})(a||(a={}));var k=class{constructor(t,n,r){this.worker=t;this.fileHash=n;this.path=r}get(t,n,r){let o=t[n];return o!==Object(o)?o:C(this.worker,this.fileHash,o,[...this.path,n])}set(t,n,r){let o=[...this.path,n],i=o[0];if(i===b){let s=o[1];this.worker.postMessage(`${a.UNRESOLVED_CODE_ENTITY} ${this.fileHash} ${b} ${s}`)}else if(i===w){let s=o[1],u=o[2];this.worker.postMessage(`${a.UNRESOLVED_CODE_ENTITY} ${this.fileHash} ${w} ${s} ${u}`)}return!0}};function C(e,t,n,r){return new Proxy(n,new k(e,t,r))}var T=S(O());function G(e){if(!(typeof WorkerGlobalScope=="function"&&self instanceof WorkerGlobalScope)){if(typeof window.addEventListener!="function")return;window.addEventListener("beforeunload",function(){e()},!0),window.addEventListener("unload",function(){e()},!0)}}var _={add:G};var W=S($()),V=T.default?W.default:_,c=new Set,M=!1;function Y(){M||(M=!0,V.add(J))}function x(e){if(Y(),typeof e!="function")throw new Error("Listener is no function");c.add(e);var t={remove:function(){return c.delete(e)},run:function(){return c.delete(e),e()}};return t}function J(){var e=[];return c.forEach(function(t){e.push(t()),c.delete(t)}),Promise.all(e)}function l(){return d()}function A(){return typeof window!="undefined"}function d(){return window}function v(e,t){let n=l()[e];return n||(n=t,l()[e]=n),n}var I=v("__TS_AGENT",{});function h(){return I._$BcWorker}function z(e){return I._$BcWorker=e,e}var N=new Set;l().makeCoverageInterceptor=function(e){let t=e.hash;if(N.has(t)){console.log(`Coverage interceptor added twice for ${t}. This seems to be a bug in the instrumentation.`);return}else N.add(t);if(!h()){let n=z(new g);(function(){let o=function(i){let s=d()[i];d()[i]=function(...u){if(n.postMessage("unload"),s)return s.apply(this,u)},A()&&Object.defineProperty(d(),i,{get:function(){return s},set:function(u){s=u}})};o("onunload"),o("onbeforeunload"),x(()=>n.postMessage("unload"))})()}return function(){h().postMessage(`${a.ISTANBUL_COV_OBJECT} ${JSON.stringify(e)}`);let r=v("sentMaps",new Set);e.inputSourceMap&&(r.has(e.path)||(h().postMessage(`${a.MESSAGE_TYPE_SOURCEMAP} ${t}:${JSON.stringify(e.inputSourceMap)}`),r.add(e.path)))}(),function(){let r=new Set;l()._$Bc=(o,i,s,u,y)=>{let f=`${o}:${i}:${s}:${u}:${y}`;r.has(f)||(h().postMessage(f),r.add(f))}}(),C(h(),e.hash,e,[])};})();
1
+ (()=>{var N=Object.create;var f=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var L=Object.getOwnPropertyNames;var I=Object.getPrototypeOf,D=Object.prototype.hasOwnProperty;var U=e=>f(e,"__esModule",{value:!0});var C=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var B=(e,t,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of L(t))!D.call(e,o)&&o!=="default"&&f(e,o,{get:()=>t[o],enumerable:!(n=x(t,o))||n.enumerable});return e},E=e=>B(U(f(e!=null?N(I(e)):{},"default",e&&e.__esModule&&"default"in e?{get:()=>e.default,enumerable:!0}:{value:e,enumerable:!0})),e);var b=C((Z,w)=>{w.exports=!1});var v=C(()=>{});function p(e){let t=new Blob([e],{type:"text/javascript"}),n=URL.createObjectURL(t),o=new Worker(n);return URL.revokeObjectURL(n),o}function m(){return p('var i=class{constructor(e){this.cachedMessages=[];this.url=e,this.socket=this.createSocket()}createSocket(){let e=new WebSocket(this.url);return e.onopen=()=>this.onopen(),e.onclose=()=>this.onclose(),e}onclose(){this.socket=this.createSocket()}onopen(){console.log("Connection to Coverage Collector established."),this.cachedMessages.forEach(e=>this.socket.send(e)),this.cachedMessages=[]}send(e){this.socket.readyState===WebSocket.OPEN?this.socket.send(e):(this.cachedMessages.push(e),this.cachedMessages.length%500===0&&console.log(`More than ${this.cachedMessages.length} messages are queued to be sent.`))}};var E=20,v=1e3,d=class{constructor(e,t){this.milliseconds=e;this.onCountedToZero=t;this.timerHandle=null}restartCountdown(){this.stopCountdown(),this.timerHandle=self.setTimeout(()=>{this.stopCountdown(),this.onCountedToZero()},this.milliseconds)}stopCountdown(){this.timerHandle!==null&&(self.clearTimeout(this.timerHandle),this.timerHandle=null)}},a=class{constructor(e){this.socket=e,this.cachedCoveredRanges=new Map,this.numberOfCachedPositions=0,this.flushCountdown=new d(v,()=>this.flush())}addRange(e,t){let o=this.cachedCoveredRanges.get(e);o||(o=new Set,this.cachedCoveredRanges.set(e,o)),o.add(t),this.numberOfCachedPositions+=1,this.flushCountdown.restartCountdown(),this.numberOfCachedPositions>=E&&this.flush()}flush(){this.numberOfCachedPositions!==0&&(this.flushCountdown.stopCountdown(),this.cachedCoveredRanges.forEach((e,t)=>{let o=Array.from(e).map(s=>`${s.start.line}:${s.start.column}:${s.end.line}:${s.end.column}`);this.socket.send(`${"c"} ${t} ${o.join(" ")}`),e.clear()}),this.cachedCoveredRanges.clear(),this.numberOfCachedPositions=0)}};var C="s",m="b";console.log("Starting coverage forwarding worker.");var p=new i("$REPORT_TO_URL/socket"),h=new a(p),l=new Map;onmessage=n=>{let e=n.data;if(e.startsWith("s"))p.send(e);else if(e.startsWith("i")){let t=JSON.parse(e.substring(2));l.set(t.hash,t),console.info(`Received coverage mapping information for "${t.hash}".`)}else e.startsWith("u")?S(e):e==="unload"?h.flush():console.error(`No handler for message: ${e}`)};function S(n){var u;let e=n.split(" ");if(e.length<4||l===null)return;let t=e[1],o=e[2],s=l.get(t);if(!s){console.log(`No coverage mapping information for ${t} available!`);return}if(o===C){let c=e[3],r=s.statementMap[c];r&&h.addRange(t,r)}else if(o===m){let c=e[3],r=Number.parseInt(e[4]),g=(u=s.branchMap[c])==null?void 0:u.locations[r];g&&h.addRange(t,g)}}\n')}var k=E(b());function G(e){if(!(typeof WorkerGlobalScope=="function"&&self instanceof WorkerGlobalScope)){if(typeof window.addEventListener!="function")return;window.addEventListener("beforeunload",function(){e()},!0),window.addEventListener("unload",function(){e()},!0)}}var S={add:G};var R=E(v()),j=k.default?R.default:S,a=new Set,_=!1;function V(){_||(_=!0,j.add(H))}function O(e){if(V(),typeof e!="function")throw new Error("Listener is no function");a.add(e);var t={remove:function(){return a.delete(e)},run:function(){return a.delete(e),e()}};return t}function H(){var e=[];return a.forEach(function(t){e.push(t()),a.delete(t)}),Promise.all(e)}function i(){return u()}function $(){return typeof window!="undefined"}function u(){return window}function g(e,t){let n=i()[e];return n||(n=t,i()[e]=n),n}var s;(function(r){r.MESSAGE_TYPE_SOURCEMAP="s",r.MESSAGE_TYPE_COVERAGE="c",r.ISTANBUL_COV_OBJECT="i",r.UNRESOLVED_CODE_ENTITY="u"})(s||(s={}));var W="s",M="b";var T=g("__TS_AGENT",{});function d(){return T._$BcWorker}function y(e){return T._$BcWorker=e,e}var A=new Set;i()._$stmtCov=function(e,t){d().postMessage(`${s.UNRESOLVED_CODE_ENTITY} ${e} ${W} ${t}`)};i()._$brCov=function(e,t,n){d().postMessage(`${s.UNRESOLVED_CODE_ENTITY} ${e} ${M} ${t} ${n}`)};i()._$registerCoverageObject=function(e){let t=e.hash;if(A.has(t)){console.log(`Coverage interceptor added twice for ${t}. This seems to be a bug in the instrumentation.`);return}else A.add(t);if(!d()){let n=y(new m);(function(){let r=function(l){let c=u()[l];u()[l]=function(...h){if(n.postMessage("unload"),c)return c.apply(this,h)},$()&&Object.defineProperty(u(),l,{get:function(){return c},set:function(h){c=h}})};r("onunload"),r("onbeforeunload"),O(()=>n.postMessage("unload"))})()}(function(){d().postMessage(`${s.ISTANBUL_COV_OBJECT} ${JSON.stringify(e)}`);let o=g("sentMaps",new Set);e.inputSourceMap&&(o.has(e.path)||(d().postMessage(`${s.MESSAGE_TYPE_SOURCEMAP} ${t}:${JSON.stringify(e.inputSourceMap)}`),o.add(e.path)))})()};})();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@teamscale/javascript-instrumenter",
3
- "version": "0.0.1-beta.40",
4
- "description": "Istanbul-based coverage instrumenter with coverage forwarding via WebSockets",
3
+ "version": "0.0.1-beta.43",
4
+ "description": "JavaScript coverage instrumenter with coverage forwarding to a collector process",
5
5
  "main": "dist/src/main.js",
6
6
  "bin": "dist/src/main.js",
7
7
  "types": "dist/src/main.d.ts",
@@ -17,7 +17,7 @@
17
17
  "build": "tsc --project tsconfig.json && yarn buildVaccine",
18
18
  "buildVaccine": "node esbuild.mjs",
19
19
  "instrumenter": "node dist/src/main.js",
20
- "test": "yarn build && NODE_OPTIONS='--experimental-vm-modules' jest --forceExit --coverage --silent=true --detectOpenHandles"
20
+ "test": "yarn build && NODE_OPTIONS='--experimental-vm-modules --max-old-space-size=8192' jest --forceExit --coverage --silent=true --detectOpenHandles"
21
21
  },
22
22
  "files": [
23
23
  "dist/**/*"
@@ -49,7 +49,7 @@
49
49
  "dependencies": {
50
50
  "@babel/generator": "^7.18.2",
51
51
  "@babel/parser": "^7.18.5",
52
- "@babel/traverse": "^7.18.5",
52
+ "@babel/traverse": "^7.18.6",
53
53
  "@babel/types": "^7.18.4",
54
54
  "@cqse/commons": "^0.0.1-beta.1",
55
55
  "@types/micromatch": "^4.0.2",
@@ -59,10 +59,9 @@
59
59
  "convert-source-map": "^1.7.0",
60
60
  "foreground-child": "^2.0.0",
61
61
  "glob": "^7.1.7",
62
- "istanbul-lib-instrument": "^4.0.0",
62
+ "istanbul-lib-instrument": "^5.2.0",
63
63
  "micromatch": "4.0.4",
64
64
  "mkdirp": "^1.0.4",
65
- "nyc": "^15.1.0",
66
65
  "source-map": "0.7.3",
67
66
  "typescript-optional": "^2.0.1",
68
67
  "unload": "^2.2.0",
@@ -1 +0,0 @@
1
- {"version":3,"file":"Cleaner.d.ts","sourceRoot":"","sources":["../../../src/instrumenter/Cleaner.ts"],"names":[],"mappings":"AAGA,OAAO,EAKN,cAAc,EAEd,MAAM,cAAc,CAAC;AAMtB;;;;;GAKG;AACH,wBAAgB,eAAe,CAC9B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,OAAO,EAClB,aAAa,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,OAAO,GAClD,MAAM,CAcR"}
@@ -1,87 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.cleanSourceCode = void 0;
7
- const parser_1 = require("@babel/parser");
8
- const generator_1 = __importDefault(require("@babel/generator"));
9
- const traverse_1 = __importDefault(require("@babel/traverse"));
10
- const types_1 = require("@babel/types");
11
- function isUpdateExpressionPath(path) {
12
- return path.node.type === 'UpdateExpression';
13
- }
14
- /**
15
- * Remove IstanbulJs instrumentations based on the given
16
- * hook `makeCoverable`.
17
- *
18
- * An instrumentation is removed if the hook `makeCoverable` returns `false`.
19
- */
20
- function cleanSourceCode(code, esModules, makeCoverable) {
21
- const ast = (0, parser_1.parse)(code, { sourceType: esModules ? 'module' : 'script' });
22
- (0, traverse_1.default)(ast, {
23
- enter(path) {
24
- if (isUpdateExpressionPath(path)) {
25
- if (isCoverageIncrementNode(path)) {
26
- if (path.node.loc && !makeCoverable(path.node.loc)) {
27
- path.remove();
28
- }
29
- }
30
- }
31
- }
32
- });
33
- return (0, generator_1.default)(ast, {}, code).code;
34
- }
35
- exports.cleanSourceCode = cleanSourceCode;
36
- /**
37
- * Checks if the given `path.node` to a statement like `cov_104fq7oo4i().f[0]++;`
38
- */
39
- function isCoverageIncrementNode(path) {
40
- return extractCoverageCallExpression(path.node) !== undefined;
41
- }
42
- /**
43
- * Returns the call expression from `cov_2pvvu1hl8v().b[2][0]++;` if
44
- * the given UpdateExpression is a branch coverage update expression.
45
- */
46
- function extractBranchCounterExpression(expr) {
47
- if (expr.operator === '++' &&
48
- (0, types_1.isMemberExpression)(expr.argument) &&
49
- (0, types_1.isMemberExpression)(expr.argument.object) &&
50
- (0, types_1.isMemberExpression)(expr.argument.object.object) &&
51
- (0, types_1.isCallExpression)(expr.argument.object.object.object)) {
52
- // Branch counter
53
- return extractCoverageObjectCall(expr.argument.object.object.object);
54
- }
55
- return undefined;
56
- }
57
- /**
58
- * Returns the call expression from `cov_104fq7oo4i().f[0]++;` if
59
- * the given UpdateExpression is a function or statement coverage update expression.
60
- */
61
- function extractFunctionOrStatementCounterExpression(expr) {
62
- if (expr.operator === '++' &&
63
- (0, types_1.isMemberExpression)(expr.argument) &&
64
- (0, types_1.isMemberExpression)(expr.argument.object) &&
65
- (0, types_1.isCallExpression)(expr.argument.object.object)) {
66
- // Function and statement counter
67
- return extractCoverageObjectCall(expr.argument.object.object);
68
- }
69
- return undefined;
70
- }
71
- /**
72
- * Given an `UpdateExpression` extract the call expression returning the coverage object.
73
- */
74
- function extractCoverageCallExpression(expr) {
75
- var _a;
76
- return (_a = extractBranchCounterExpression(expr)) !== null && _a !== void 0 ? _a : extractFunctionOrStatementCounterExpression(expr);
77
- }
78
- /**
79
- * Check if the given call expression is a coverage call expression.
80
- * If this is not the case return `undefined`, and the call expression itself otherwise.
81
- */
82
- function extractCoverageObjectCall(callExpression) {
83
- if (callExpression && (0, types_1.isIdentifier)(callExpression.callee) && callExpression.callee.name.startsWith('cov_')) {
84
- return callExpression;
85
- }
86
- return undefined;
87
- }