@teamscale/javascript-instrumenter 0.0.1-beta.9 → 0.1.0-beta.3
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/LICENSE +176 -0
- package/README.md +7 -62
- package/dist/package.json +45 -38
- package/dist/src/App.d.ts +6 -1
- package/dist/src/App.d.ts.map +1 -1
- package/dist/src/App.js +92 -24
- package/dist/src/instrumenter/FileSystem.d.ts +11 -0
- package/dist/src/instrumenter/FileSystem.d.ts.map +1 -1
- package/dist/src/instrumenter/FileSystem.js +37 -4
- package/dist/src/instrumenter/Instrumenter.d.ts +39 -23
- package/dist/src/instrumenter/Instrumenter.d.ts.map +1 -1
- package/dist/src/instrumenter/Instrumenter.js +214 -101
- package/dist/src/instrumenter/Task.d.ts +69 -12
- package/dist/src/instrumenter/Task.d.ts.map +1 -1
- package/dist/src/instrumenter/Task.js +101 -42
- package/dist/src/instrumenter/TaskBuilder.d.ts +19 -11
- package/dist/src/instrumenter/TaskBuilder.d.ts.map +1 -1
- package/dist/src/instrumenter/TaskBuilder.js +28 -14
- package/dist/src/instrumenter/WebToolkit.d.ts +40 -0
- package/dist/src/instrumenter/WebToolkit.d.ts.map +1 -0
- package/dist/src/instrumenter/WebToolkit.js +147 -0
- package/dist/src/main.js +1 -0
- package/dist/vaccine.js +1 -1
- package/package.json +49 -43
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
3
|
if (k2 === undefined) k2 = k;
|
|
4
|
-
Object.
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
5
9
|
}) : (function(o, m, k, k2) {
|
|
6
10
|
if (k2 === undefined) k2 = k;
|
|
7
11
|
o[k2] = m[k];
|
|
@@ -22,10 +26,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
22
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
23
27
|
};
|
|
24
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
-
exports.expandToFileSet = exports.isDirectoryEmpty = exports.ensureExistingDirectory = exports.isExistingDirectory = exports.isExistingFile = void 0;
|
|
29
|
+
exports.sourceMapFromMapFile = exports.expandToFileSet = exports.isDirectoryEmpty = exports.findSubFolders = exports.ensureExistingDirectory = exports.isExistingDirectory = exports.isExistingFile = void 0;
|
|
26
30
|
const commons_1 = require("@cqse/commons");
|
|
27
31
|
const fs = __importStar(require("fs"));
|
|
28
|
-
const
|
|
32
|
+
const mkdirp = __importStar(require("mkdirp"));
|
|
29
33
|
const path_1 = __importDefault(require("path"));
|
|
30
34
|
const glob_1 = require("glob");
|
|
31
35
|
/**
|
|
@@ -47,13 +51,32 @@ exports.isExistingDirectory = isExistingDirectory;
|
|
|
47
51
|
*/
|
|
48
52
|
function ensureExistingDirectory(path) {
|
|
49
53
|
if (!fs.existsSync(path)) {
|
|
50
|
-
|
|
54
|
+
mkdirp.sync(path);
|
|
51
55
|
}
|
|
52
56
|
if (!fs.lstatSync(path).isDirectory()) {
|
|
53
57
|
throw new commons_1.InvalidConfigurationException(`The specified path '${path}' does not point to an existing directory!`);
|
|
54
58
|
}
|
|
55
59
|
}
|
|
56
60
|
exports.ensureExistingDirectory = ensureExistingDirectory;
|
|
61
|
+
/**
|
|
62
|
+
* Given a root folder find a folder with a given name.
|
|
63
|
+
*/
|
|
64
|
+
function findSubFolders(startFromFolder, folderName) {
|
|
65
|
+
const matches = [];
|
|
66
|
+
const entries = fs.readdirSync(startFromFolder, { withFileTypes: true });
|
|
67
|
+
for (const entry of entries) {
|
|
68
|
+
const fullPath = path_1.default.join(startFromFolder, entry.name);
|
|
69
|
+
if (entry.name === folderName && entry.isDirectory()) {
|
|
70
|
+
matches.push(fullPath);
|
|
71
|
+
}
|
|
72
|
+
if (entry.isDirectory()) {
|
|
73
|
+
const nestedMatches = findSubFolders(fullPath, folderName);
|
|
74
|
+
matches.push(...nestedMatches);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return matches;
|
|
78
|
+
}
|
|
79
|
+
exports.findSubFolders = findSubFolders;
|
|
57
80
|
/**
|
|
58
81
|
* Is the given directory empty?
|
|
59
82
|
*/
|
|
@@ -80,3 +103,13 @@ function expandToFileSet(toExpand) {
|
|
|
80
103
|
return glob_1.glob.sync(globPattern, { nodir: true });
|
|
81
104
|
}
|
|
82
105
|
exports.expandToFileSet = expandToFileSet;
|
|
106
|
+
/**
|
|
107
|
+
* Read a source map from a source map file.
|
|
108
|
+
*
|
|
109
|
+
* @param mapFilePath
|
|
110
|
+
*/
|
|
111
|
+
function sourceMapFromMapFile(mapFilePath) {
|
|
112
|
+
const content = fs.readFileSync(mapFilePath, 'utf8');
|
|
113
|
+
return JSON.parse(content);
|
|
114
|
+
}
|
|
115
|
+
exports.sourceMapFromMapFile = sourceMapFromMapFile;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { CollectorSpecifier, InstrumentationTask, OriginSourcePattern, TaskElement, TaskResult } from './Task';
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { CollectorSpecifier, FileExcludePattern, InstrumentationTask, OriginSourcePattern, SourceMapReference, TaskElement, TaskResult } from './Task';
|
|
2
|
+
import { RawSourceMap, SourceMapConsumer } from 'source-map';
|
|
3
|
+
import { Optional } from 'typescript-optional';
|
|
4
|
+
import Logger from 'bunyan';
|
|
5
|
+
export declare const IS_INSTRUMENTED_TOKEN = "$IS_JS_PROFILER_INSTRUMENTED=true";
|
|
4
6
|
/**
|
|
5
7
|
* An instrumenter that can conduct a {@code InstrumentationTask}.
|
|
6
8
|
*/
|
|
@@ -17,16 +19,16 @@ export interface IInstrumenter {
|
|
|
17
19
|
*/
|
|
18
20
|
export declare class IstanbulInstrumenter implements IInstrumenter {
|
|
19
21
|
/**
|
|
20
|
-
* The
|
|
22
|
+
* The vaccine to inject. The vaccine is a JavaScript
|
|
21
23
|
* file with the code to forward the coverage information
|
|
22
24
|
* produced by the Istanbul instrumentation.
|
|
23
25
|
*/
|
|
24
|
-
private readonly
|
|
26
|
+
private readonly vaccineSource;
|
|
25
27
|
/**
|
|
26
28
|
* The logger instance to log to.
|
|
27
29
|
*/
|
|
28
30
|
private logger;
|
|
29
|
-
constructor(vaccineFilePath: string, logger: Logger);
|
|
31
|
+
constructor(vaccineFilePath: string, logger: Logger, collector: CollectorSpecifier);
|
|
30
32
|
/**
|
|
31
33
|
* {@inheritDoc #IInstrumenter.instrument}
|
|
32
34
|
*/
|
|
@@ -36,24 +38,19 @@ export declare class IstanbulInstrumenter implements IInstrumenter {
|
|
|
36
38
|
*
|
|
37
39
|
* @param collector - The collector to send the coverage information to.
|
|
38
40
|
* @param taskElement - The task element to perform the instrumentation for.
|
|
41
|
+
* @param excludeBundles - A exclude pattern to restrict which bundles should be instrumented
|
|
39
42
|
* @param sourcePattern - A pattern to restrict the instrumentation to only a fraction of the task element.
|
|
43
|
+
* @param dumpOriginsFile - A file path where all origins from the source map should be dumped in json format, or undefined if no origins should be dumped
|
|
40
44
|
*/
|
|
41
|
-
instrumentOne(
|
|
45
|
+
instrumentOne(taskElement: TaskElement, excludeBundles: FileExcludePattern, sourcePattern: OriginSourcePattern, dumpOriginsFile: string | undefined): Promise<TaskResult>;
|
|
46
|
+
private instrumentBundle;
|
|
47
|
+
private writeBundleFile;
|
|
42
48
|
/**
|
|
43
49
|
* Loads the vaccine from the vaccine file and adjusts some template parameters.
|
|
44
50
|
*
|
|
45
51
|
* @param collector - The collector to send coverage information to.
|
|
46
52
|
*/
|
|
47
53
|
private loadVaccine;
|
|
48
|
-
/**
|
|
49
|
-
* Should the given file be excluded from the instrumentation,
|
|
50
|
-
* based on the source files that have been transpiled into it?
|
|
51
|
-
*
|
|
52
|
-
* @param pattern - The pattern to match the origin source files.
|
|
53
|
-
* @param sourceFile - The bundle file name.
|
|
54
|
-
* @param originSourceFiles - The list of files that were transpiled into the bundle.
|
|
55
|
-
*/
|
|
56
|
-
private shouldExcludeFromInstrumentation;
|
|
57
54
|
/**
|
|
58
55
|
* @returns whether the given file is supported for instrumentation.
|
|
59
56
|
*/
|
|
@@ -63,12 +60,31 @@ export declare class IstanbulInstrumenter implements IInstrumenter {
|
|
|
63
60
|
* given task element.
|
|
64
61
|
*/
|
|
65
62
|
private configurationAlternativesFor;
|
|
66
|
-
/**
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
* @param taskElement - The task element that can have a reference to an external sourcemap.
|
|
71
|
-
*/
|
|
72
|
-
private loadInputSourceMap;
|
|
63
|
+
/** Appends all origins from the source map to a given file. Creates the file if it does not exist yet. */
|
|
64
|
+
private dumpOrigins;
|
|
65
|
+
/** Clears the dump origins file if it exists, such that it is now ready to be appended for every instrumented file. */
|
|
66
|
+
private clearDumpOriginsFileIfNeeded;
|
|
73
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Extract the sourcemap from the given source code.
|
|
70
|
+
*
|
|
71
|
+
* @param instrumentedSource - The source code.
|
|
72
|
+
* @param instrumentedSourceFileName - The file name to assume for the file name.
|
|
73
|
+
*/
|
|
74
|
+
export declare function loadSourceMap(instrumentedSource: string, instrumentedSourceFileName: string): Promise<SourceMapConsumer | undefined>;
|
|
75
|
+
/**
|
|
76
|
+
* Given a source code file, load the corresponding sourcemap.
|
|
77
|
+
*
|
|
78
|
+
* @param inputSource - The source code that might contain sourcemap comments.
|
|
79
|
+
* @param taskFile - The name of the file the `inputSource` is from.
|
|
80
|
+
* @param externalSourceMapFile - An external source map file to consider.
|
|
81
|
+
*/
|
|
82
|
+
export declare function loadInputSourceMap(inputSource: string, taskFile: string, externalSourceMapFile: Optional<SourceMapReference>): RawSourceMap | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* Extract a sourcemap for a given code comment.
|
|
85
|
+
*
|
|
86
|
+
* @param sourcecode - The source code that is scanned for source map comments.
|
|
87
|
+
* @param sourceFilePath - The file name the code was loaded from.
|
|
88
|
+
*/
|
|
89
|
+
export declare function sourceMapFromCodeComment(sourcecode: string, sourceFilePath: string): RawSourceMap | undefined;
|
|
74
90
|
//# sourceMappingURL=Instrumenter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Instrumenter.d.ts","sourceRoot":"","sources":["../../../src/instrumenter/Instrumenter.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"Instrumenter.d.ts","sourceRoot":"","sources":["../../../src/instrumenter/Instrumenter.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,kBAAkB,EAClB,kBAAkB,EAElB,mBAAmB,EACnB,mBAAmB,EAEnB,kBAAkB,EAElB,WAAW,EACX,UAAU,EACV,MAAM,QAAQ,CAAC;AAEhB,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAM7D,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,MAAM,MAAM,QAAQ,CAAC;AAQ5B,eAAO,MAAM,qBAAqB,sCAAsC,CAAC;AAEzE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B;;;;OAIG;IACH,UAAU,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CAC3D;AA0BD;;GAEG;AACH,qBAAa,oBAAqB,YAAW,aAAa;IACzD;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAEvC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAS;gBAEX,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,kBAAkB;IAUlF;;OAEG;IACG,UAAU,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,UAAU,CAAC;IAqBhE;;;;;;;;OAQG;IACG,aAAa,CAClB,WAAW,EAAE,WAAW,EACxB,cAAc,EAAE,kBAAkB,EAClC,aAAa,EAAE,mBAAmB,EAClC,eAAe,EAAE,MAAM,GAAG,SAAS,GACjC,OAAO,CAAC,UAAU,CAAC;YAsER,gBAAgB;IA8C9B,OAAO,CAAC,eAAe;IA0BvB;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAMnB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAS3B;;;OAGG;IACH,OAAO,CAAC,4BAA4B;IAepC,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;AAsBD;;;;;;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,CAwC7G"}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
3
|
if (k2 === undefined) k2 = k;
|
|
4
|
-
Object.
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
5
9
|
}) : (function(o, m, k, k2) {
|
|
6
10
|
if (k2 === undefined) k2 = k;
|
|
7
11
|
o[k2] = m[k];
|
|
@@ -18,135 +22,194 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
18
22
|
__setModuleDefault(result, mod);
|
|
19
23
|
return result;
|
|
20
24
|
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
21
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
-
exports.IstanbulInstrumenter = exports.IS_INSTRUMENTED_TOKEN = void 0;
|
|
29
|
+
exports.sourceMapFromCodeComment = exports.loadInputSourceMap = exports.loadSourceMap = exports.IstanbulInstrumenter = exports.IS_INSTRUMENTED_TOKEN = void 0;
|
|
23
30
|
const Task_1 = require("./Task");
|
|
24
31
|
const commons_1 = require("@cqse/commons");
|
|
25
|
-
const
|
|
32
|
+
const source_map_1 = require("source-map");
|
|
33
|
+
const istanbul = __importStar(require("@teamscale/lib-instrument"));
|
|
26
34
|
const fs = __importStar(require("fs"));
|
|
35
|
+
const mkdirp = __importStar(require("mkdirp"));
|
|
27
36
|
const path = __importStar(require("path"));
|
|
28
37
|
const convertSourceMap = __importStar(require("convert-source-map"));
|
|
29
|
-
|
|
38
|
+
const typescript_optional_1 = require("typescript-optional");
|
|
39
|
+
const async_1 = __importDefault(require("async"));
|
|
40
|
+
const WebToolkit_1 = require("./WebToolkit");
|
|
41
|
+
const FileSystem_1 = require("./FileSystem");
|
|
42
|
+
exports.IS_INSTRUMENTED_TOKEN = '$IS_JS_PROFILER_INSTRUMENTED=true';
|
|
43
|
+
function readBundle(bundleContent, taskElement) {
|
|
44
|
+
const baseNameMatch = path.basename(taskElement.fromFile).match('^([a-zA-Z0-9]*).cache.js');
|
|
45
|
+
const directories = path.dirname(taskElement.fromFile).split(path.sep);
|
|
46
|
+
if (baseNameMatch && directories.length > 1) {
|
|
47
|
+
const fragmentId = (0, WebToolkit_1.determineGwtFileUid)(taskElement.fromFile);
|
|
48
|
+
const functionCall = bundleContent.match('^([^(]+)\\((.*)\\);*\\s*$');
|
|
49
|
+
if (fragmentId && functionCall && functionCall.length === 3) {
|
|
50
|
+
const callInfos = (0, WebToolkit_1.extractGwtCallInfos)(bundleContent);
|
|
51
|
+
if (callInfos) {
|
|
52
|
+
return {
|
|
53
|
+
type: 'gwt',
|
|
54
|
+
content: bundleContent,
|
|
55
|
+
fragmentId,
|
|
56
|
+
functionName: callInfos.functionName,
|
|
57
|
+
codeAsArrayArgument: callInfos.codeAsArrayArgument,
|
|
58
|
+
codeArguments: callInfos.codeArguments
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { type: 'javascript', content: bundleContent, codeArguments: [bundleContent] };
|
|
64
|
+
}
|
|
30
65
|
/**
|
|
31
66
|
* An instrumenter based on the IstanbulJs instrumentation and coverage framework.
|
|
32
67
|
*/
|
|
33
68
|
class IstanbulInstrumenter {
|
|
34
|
-
constructor(vaccineFilePath, logger) {
|
|
35
|
-
this.vaccineFilePath = commons_1.Contract.requireNonEmpty(vaccineFilePath);
|
|
69
|
+
constructor(vaccineFilePath, logger, collector) {
|
|
36
70
|
commons_1.Contract.require(fs.existsSync(vaccineFilePath), `The vaccine file to inject "${vaccineFilePath}" must exist!\nCWD:${process.cwd()}`);
|
|
71
|
+
this.vaccineSource = this.loadVaccine(commons_1.Contract.requireNonEmpty(vaccineFilePath), collector);
|
|
37
72
|
this.logger = logger;
|
|
38
73
|
}
|
|
39
74
|
/**
|
|
40
75
|
* {@inheritDoc #IInstrumenter.instrument}
|
|
41
76
|
*/
|
|
42
|
-
instrument(task) {
|
|
43
|
-
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
.
|
|
48
|
-
.
|
|
49
|
-
|
|
77
|
+
async instrument(task) {
|
|
78
|
+
this.clearDumpOriginsFileIfNeeded(task.dumpOriginsFile);
|
|
79
|
+
// We limit the number of instrumentations in parallel to one to
|
|
80
|
+
// not overuse memory (NodeJS has only limited mem to use).
|
|
81
|
+
return async_1.default
|
|
82
|
+
.mapLimit(task.elements, 1, async (taskElement) => {
|
|
83
|
+
return await this.instrumentOne(taskElement, task.excludeFilesPattern, task.originSourcePattern, task.dumpOriginsFile);
|
|
84
|
+
})
|
|
85
|
+
.then(results => {
|
|
86
|
+
return results.reduce((prev, curr) => {
|
|
87
|
+
return prev.withIncrement(curr);
|
|
88
|
+
}, Task_1.TaskResult.neutral());
|
|
89
|
+
});
|
|
50
90
|
}
|
|
51
91
|
/**
|
|
52
92
|
* Perform the instrumentation for one given task element (file to instrument).
|
|
53
93
|
*
|
|
54
94
|
* @param collector - The collector to send the coverage information to.
|
|
55
95
|
* @param taskElement - The task element to perform the instrumentation for.
|
|
96
|
+
* @param excludeBundles - A exclude pattern to restrict which bundles should be instrumented
|
|
56
97
|
* @param sourcePattern - A pattern to restrict the instrumentation to only a fraction of the task element.
|
|
98
|
+
* @param dumpOriginsFile - A file path where all origins from the source map should be dumped in json format, or undefined if no origins should be dumped
|
|
57
99
|
*/
|
|
58
|
-
instrumentOne(
|
|
59
|
-
|
|
60
|
-
|
|
100
|
+
async instrumentOne(taskElement, excludeBundles, sourcePattern, dumpOriginsFile) {
|
|
101
|
+
// Not all file types are supported by the instrumenter
|
|
102
|
+
if (!this.isFileTypeSupported(taskElement.fromFile)) {
|
|
103
|
+
if (!taskElement.isInPlace()) {
|
|
104
|
+
copyToFile(taskElement.toFile, taskElement.fromFile);
|
|
105
|
+
}
|
|
106
|
+
return new Task_1.TaskResult(0, 0, 0, 0, 1, 0, 0);
|
|
107
|
+
}
|
|
108
|
+
// Read the (supported) file
|
|
109
|
+
const bundleContent = fs.readFileSync(taskElement.fromFile, 'utf8');
|
|
110
|
+
const inputBundle = readBundle(bundleContent, taskElement);
|
|
61
111
|
// We skip files that we have already instrumented
|
|
62
|
-
if (
|
|
112
|
+
if (inputBundle.content.substring(0, Math.min(inputBundle.content.length, 100)).includes(exports.IS_INSTRUMENTED_TOKEN)) {
|
|
63
113
|
if (!taskElement.isInPlace()) {
|
|
64
|
-
|
|
114
|
+
writeToFile(taskElement.toFile, inputBundle.content);
|
|
65
115
|
}
|
|
66
|
-
return new Task_1.TaskResult(0, 0, 1, 0, 0, 0);
|
|
116
|
+
return new Task_1.TaskResult(0, 0, 0, 1, 0, 0, 0);
|
|
67
117
|
}
|
|
68
|
-
//
|
|
69
|
-
if (
|
|
70
|
-
|
|
118
|
+
// We might want to skip the instrumentation of the file
|
|
119
|
+
if (excludeBundles.isExcluded(taskElement.fromFile)) {
|
|
120
|
+
if (!taskElement.isInPlace()) {
|
|
121
|
+
copyToFile(taskElement.toFile, taskElement.fromFile);
|
|
122
|
+
}
|
|
123
|
+
return new Task_1.TaskResult(0, 1, 0, 0, 0, 0, 0);
|
|
71
124
|
}
|
|
72
125
|
// Report progress
|
|
73
126
|
this.logger.info(`Instrumenting "${path.basename(taskElement.fromFile)}"`);
|
|
74
|
-
|
|
75
|
-
|
|
127
|
+
// Load the bundles source maps
|
|
128
|
+
const inputSourceMaps = loadInputSourceMaps(taskElement.fromFile, inputBundle, taskElement.externalSourceMapFile);
|
|
129
|
+
if (inputSourceMaps.length === 0) {
|
|
130
|
+
return Task_1.TaskResult.warning(`Failed loading input source map for ${taskElement.fromFile}.`);
|
|
131
|
+
}
|
|
76
132
|
// We try to perform the instrumentation with different
|
|
77
133
|
// alternative configurations of the instrumenter.
|
|
78
134
|
const configurationAlternatives = this.configurationAlternativesFor(taskElement);
|
|
79
135
|
for (let i = 0; i < configurationAlternatives.length; i++) {
|
|
80
|
-
|
|
136
|
+
const configurationAlternative = configurationAlternatives[i];
|
|
81
137
|
try {
|
|
82
|
-
|
|
83
|
-
inputSourceMap = this.loadInputSourceMap(inputFileSource, taskElement);
|
|
84
|
-
// Based on the source maps of the file to instrument, we can now
|
|
85
|
-
// decide if we should NOT write an instrumented version of it
|
|
86
|
-
// and use the original code instead and write it to the target path.
|
|
87
|
-
//
|
|
88
|
-
if (this.shouldExcludeFromInstrumentation(sourcePattern, taskElement.fromFile, (_b = (_a = instrumenter.lastSourceMap()) === null || _a === void 0 ? void 0 : _a.sources) !== null && _b !== void 0 ? _b : [])) {
|
|
89
|
-
fs.writeFileSync(taskElement.toFile, inputFileSource);
|
|
90
|
-
return new Task_1.TaskResult(1, 0, 0, 0, 0, 0);
|
|
91
|
-
}
|
|
92
|
-
// The main instrumentation (adding coverage statements) is performed now:
|
|
93
|
-
instrumentedSource = instrumenter
|
|
94
|
-
.instrumentSync(inputFileSource, taskElement.fromFile, inputSourceMap)
|
|
95
|
-
.replace(/return actualCoverage/g, 'return makeCoverageInterceptor(actualCoverage)')
|
|
96
|
-
.replace(/new Function\("return this"\)\(\)/g, "typeof window === 'object' ? window : this");
|
|
97
|
-
this.logger.debug('Instrumentation source maps to:', (_c = instrumenter.lastSourceMap()) === null || _c === void 0 ? void 0 : _c.sources);
|
|
98
|
-
// The process also can result in a new source map that we will append in the result.
|
|
99
|
-
//
|
|
100
|
-
// `lastSourceMap` === Sourcemap for the last file that was instrumented.
|
|
101
|
-
finalSourceMap = convertSourceMap.fromObject(instrumenter.lastSourceMap()).toComment();
|
|
102
|
-
break;
|
|
138
|
+
return await this.instrumentBundle(taskElement, inputBundle, inputSourceMaps, configurationAlternative, dumpOriginsFile, sourcePattern);
|
|
103
139
|
}
|
|
104
140
|
catch (e) {
|
|
105
141
|
// If also the last configuration alternative failed,
|
|
106
142
|
// we emit a corresponding warning or signal an error.
|
|
107
143
|
if (i === configurationAlternatives.length - 1) {
|
|
108
|
-
|
|
109
|
-
return Task_1.TaskResult.warning(`Failed loading input source map for ${taskElement.fromFile}: ${e.message}`);
|
|
110
|
-
}
|
|
111
|
-
fs.writeFileSync(taskElement.toFile, inputFileSource);
|
|
144
|
+
writeToFile(taskElement.toFile, inputBundle.content);
|
|
112
145
|
return Task_1.TaskResult.error(e);
|
|
113
146
|
}
|
|
114
147
|
}
|
|
115
148
|
}
|
|
116
|
-
|
|
149
|
+
return new Task_1.TaskResult(0, 0, 0, 0, 0, 1, 0);
|
|
150
|
+
}
|
|
151
|
+
async instrumentBundle(taskElement, inputBundle, inputSourceMaps, configurationAlternative, dumpOriginsFile, sourcePattern) {
|
|
152
|
+
var _a;
|
|
153
|
+
// Based on the source maps of the file to instrument, we can now
|
|
154
|
+
// decide if we should NOT write an instrumented version of it
|
|
155
|
+
// and use the original code instead and write it to the target path.
|
|
117
156
|
//
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
157
|
+
for (const inputSourceMap of inputSourceMaps.filter(map => map)) {
|
|
158
|
+
const originSourceFiles = (_a = inputSourceMap === null || inputSourceMap === void 0 ? void 0 : inputSourceMap.sources) !== null && _a !== void 0 ? _a : [];
|
|
159
|
+
if (dumpOriginsFile) {
|
|
160
|
+
this.dumpOrigins(dumpOriginsFile, originSourceFiles);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// The main instrumentation (adding coverage statements) is performed now:
|
|
164
|
+
const instrumenter = istanbul.createInstrumenter(configurationAlternative);
|
|
165
|
+
const instrumentedSources = [];
|
|
166
|
+
for (let i = 0; i < inputBundle.codeArguments.length; i++) {
|
|
167
|
+
const shouldInstrument = (node, originLocation) => {
|
|
168
|
+
if (!originLocation.filename) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
return sourcePattern.isIncluded(originLocation.filename);
|
|
172
|
+
};
|
|
173
|
+
const instrumented = await instrumenter.instrument(inputBundle.codeArguments[i], taskElement.fromFile, inputSourceMaps[i], shouldInstrument);
|
|
174
|
+
instrumentedSources.push(instrumented);
|
|
175
|
+
}
|
|
176
|
+
this.writeBundleFile(taskElement.toFile, inputBundle, instrumentedSources);
|
|
177
|
+
return new Task_1.TaskResult(1, 0, 0, 0, 0, 0, 0);
|
|
178
|
+
}
|
|
179
|
+
writeBundleFile(toFile, inputBundle, instrumentedSources) {
|
|
180
|
+
if (inputBundle.type === 'gwt') {
|
|
181
|
+
commons_1.Contract.require(instrumentedSources.length === 1, 'Assuming only one code fragment to be passed as argument.');
|
|
182
|
+
const processedCodeString = JSON.stringify(`${this.vaccineSource} ${instrumentedSources[0]}`);
|
|
183
|
+
if (inputBundle.codeAsArrayArgument) {
|
|
184
|
+
writeToFile(toFile, `${exports.IS_INSTRUMENTED_TOKEN} ${inputBundle.functionName}([${processedCodeString}]);`);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
writeToFile(toFile, `${exports.IS_INSTRUMENTED_TOKEN} ${inputBundle.functionName}(${processedCodeString});`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
commons_1.Contract.require(instrumentedSources.length === 1, 'Assuming only one code fragment to be passed as argument for JavaScript bundles.');
|
|
192
|
+
writeToFile(toFile, instrumentedSources[0]);
|
|
193
|
+
}
|
|
121
194
|
}
|
|
122
195
|
/**
|
|
123
196
|
* Loads the vaccine from the vaccine file and adjusts some template parameters.
|
|
124
197
|
*
|
|
125
198
|
* @param collector - The collector to send coverage information to.
|
|
126
199
|
*/
|
|
127
|
-
loadVaccine(collector) {
|
|
128
|
-
// We first replace
|
|
200
|
+
loadVaccine(filePath, collector) {
|
|
201
|
+
// We first replace parameters in the file with the
|
|
129
202
|
// actual values, for example, the collector to send the coverage information to.
|
|
130
|
-
return fs
|
|
131
|
-
.readFileSync(this.vaccineFilePath, 'utf8')
|
|
132
|
-
.replace(/\$REPORT_TO_HOST/g, collector.host)
|
|
133
|
-
.replace(/\$REPORT_TO_PORT/g, `${collector.port}`);
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Should the given file be excluded from the instrumentation,
|
|
137
|
-
* based on the source files that have been transpiled into it?
|
|
138
|
-
*
|
|
139
|
-
* @param pattern - The pattern to match the origin source files.
|
|
140
|
-
* @param sourceFile - The bundle file name.
|
|
141
|
-
* @param originSourceFiles - The list of files that were transpiled into the bundle.
|
|
142
|
-
*/
|
|
143
|
-
shouldExcludeFromInstrumentation(pattern, sourceFile, originSourceFiles) {
|
|
144
|
-
return !pattern.isAnyIncluded(originSourceFiles);
|
|
203
|
+
return fs.readFileSync(filePath, 'utf8').replace(/\$REPORT_TO_URL/g, collector.url);
|
|
145
204
|
}
|
|
146
205
|
/**
|
|
147
206
|
* @returns whether the given file is supported for instrumentation.
|
|
148
207
|
*/
|
|
149
208
|
isFileTypeSupported(fileName) {
|
|
209
|
+
if (fileName.endsWith('.devmode.js') || fileName.endsWith('.nocache.js')) {
|
|
210
|
+
// GWT dev mode files.
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
150
213
|
const ext = path.extname(fileName).toLowerCase();
|
|
151
214
|
return ext === '.js' || ext === '.mjs';
|
|
152
215
|
}
|
|
@@ -157,34 +220,82 @@ class IstanbulInstrumenter {
|
|
|
157
220
|
configurationAlternativesFor(taskElement) {
|
|
158
221
|
this.logger.debug(`Determining configuration alternatives for ${taskElement.fromFile}`);
|
|
159
222
|
const baseConfig = {
|
|
160
|
-
|
|
161
|
-
produceSourceMap:
|
|
223
|
+
isInstrumentedToken: exports.IS_INSTRUMENTED_TOKEN,
|
|
224
|
+
produceSourceMap: 'external',
|
|
225
|
+
codeToPrepend: this.vaccineSource
|
|
162
226
|
};
|
|
163
227
|
return [
|
|
164
228
|
{ ...baseConfig, ...{ esModules: true } },
|
|
165
229
|
{ ...baseConfig, ...{ esModules: false } }
|
|
166
230
|
];
|
|
167
231
|
}
|
|
168
|
-
/**
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
232
|
+
/** Appends all origins from the source map to a given file. Creates the file if it does not exist yet. */
|
|
233
|
+
dumpOrigins(dumpOriginsFile, originSourceFiles) {
|
|
234
|
+
const jsonContent = JSON.stringify(originSourceFiles, null, 2);
|
|
235
|
+
fs.writeFile(dumpOriginsFile, jsonContent + '\n', { flag: 'a' }, error => {
|
|
236
|
+
if (error) {
|
|
237
|
+
this.logger.warn('Could not dump origins file');
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
/** Clears the dump origins file if it exists, such that it is now ready to be appended for every instrumented file. */
|
|
242
|
+
clearDumpOriginsFileIfNeeded(dumpOriginsFile) {
|
|
243
|
+
if (dumpOriginsFile && fs.existsSync(dumpOriginsFile)) {
|
|
244
|
+
try {
|
|
245
|
+
fs.unlinkSync(dumpOriginsFile);
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
this.logger.warn('Could not clear origins file: ' + err);
|
|
179
249
|
}
|
|
180
|
-
return sourceMapFromMapFile(sourceMapOrigin.sourceMapFilePath);
|
|
181
|
-
}
|
|
182
|
-
else {
|
|
183
|
-
return sourceMapFromCodeComment(inputSource, taskElement.fromFile);
|
|
184
250
|
}
|
|
185
251
|
}
|
|
186
252
|
}
|
|
187
253
|
exports.IstanbulInstrumenter = IstanbulInstrumenter;
|
|
254
|
+
/**
|
|
255
|
+
* Extract the sourcemap from the given source code.
|
|
256
|
+
*
|
|
257
|
+
* @param instrumentedSource - The source code.
|
|
258
|
+
* @param instrumentedSourceFileName - The file name to assume for the file name.
|
|
259
|
+
*/
|
|
260
|
+
async function loadSourceMap(instrumentedSource, instrumentedSourceFileName) {
|
|
261
|
+
const instrumentedSourceMap = loadInputSourceMap(instrumentedSource, instrumentedSourceFileName, typescript_optional_1.Optional.empty());
|
|
262
|
+
if (instrumentedSourceMap) {
|
|
263
|
+
return await new source_map_1.SourceMapConsumer(instrumentedSourceMap);
|
|
264
|
+
}
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
exports.loadSourceMap = loadSourceMap;
|
|
268
|
+
function loadInputSourceMaps(taskFile, bundleFile, externalSourceMapFile) {
|
|
269
|
+
if ((0, WebToolkit_1.isGwtBundle)(bundleFile)) {
|
|
270
|
+
return (0, WebToolkit_1.loadInputSourceMapsGwt)(taskFile, bundleFile);
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
return loadInputSourceMapsStandard(taskFile, bundleFile, externalSourceMapFile);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function loadInputSourceMapsStandard(taskFile, bundleFile, externalSourceMapFile) {
|
|
277
|
+
return bundleFile.codeArguments.map(code => loadInputSourceMap(code, taskFile, externalSourceMapFile));
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Given a source code file, load the corresponding sourcemap.
|
|
281
|
+
*
|
|
282
|
+
* @param inputSource - The source code that might contain sourcemap comments.
|
|
283
|
+
* @param taskFile - The name of the file the `inputSource` is from.
|
|
284
|
+
* @param externalSourceMapFile - An external source map file to consider.
|
|
285
|
+
*/
|
|
286
|
+
function loadInputSourceMap(inputSource, taskFile, externalSourceMapFile) {
|
|
287
|
+
if (externalSourceMapFile.isPresent()) {
|
|
288
|
+
const sourceMapOrigin = externalSourceMapFile.get();
|
|
289
|
+
if (!(sourceMapOrigin instanceof Task_1.SourceMapFileReference)) {
|
|
290
|
+
throw new commons_1.IllegalArgumentException('Type of source map not yet supported!');
|
|
291
|
+
}
|
|
292
|
+
return (0, FileSystem_1.sourceMapFromMapFile)(sourceMapOrigin.sourceMapFilePath);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
return sourceMapFromCodeComment(inputSource, taskFile);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
exports.loadInputSourceMap = loadInputSourceMap;
|
|
188
299
|
/**
|
|
189
300
|
* Extract a sourcemap for a given code comment.
|
|
190
301
|
*
|
|
@@ -210,7 +321,9 @@ function sourceMapFromCodeComment(sourcecode, sourceFilePath) {
|
|
|
210
321
|
}
|
|
211
322
|
else {
|
|
212
323
|
result = convertSourceMap
|
|
213
|
-
.fromMapFileComment(sourceMapComment,
|
|
324
|
+
.fromMapFileComment(sourceMapComment, function (filename) {
|
|
325
|
+
return fs.readFileSync(path.resolve(path.dirname(sourceFilePath), filename), 'utf-8');
|
|
326
|
+
})
|
|
214
327
|
.toObject();
|
|
215
328
|
}
|
|
216
329
|
}
|
|
@@ -218,11 +331,11 @@ function sourceMapFromCodeComment(sourcecode, sourceFilePath) {
|
|
|
218
331
|
// One JS file can refer to several source map files in its comments.
|
|
219
332
|
failedLoading++;
|
|
220
333
|
}
|
|
221
|
-
if (result) {
|
|
222
|
-
return result;
|
|
223
|
-
}
|
|
224
334
|
}
|
|
225
335
|
} while (matched);
|
|
336
|
+
if (result) {
|
|
337
|
+
return result;
|
|
338
|
+
}
|
|
226
339
|
if (failedLoading > 0) {
|
|
227
340
|
throw new commons_1.IllegalArgumentException('None of the referenced source map files loaded!');
|
|
228
341
|
}
|
|
@@ -230,12 +343,12 @@ function sourceMapFromCodeComment(sourcecode, sourceFilePath) {
|
|
|
230
343
|
return undefined;
|
|
231
344
|
}
|
|
232
345
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
function
|
|
239
|
-
|
|
240
|
-
|
|
346
|
+
exports.sourceMapFromCodeComment = sourceMapFromCodeComment;
|
|
347
|
+
function writeToFile(filePath, fileContent) {
|
|
348
|
+
mkdirp.sync(path.dirname(filePath));
|
|
349
|
+
fs.writeFileSync(filePath, fileContent);
|
|
350
|
+
}
|
|
351
|
+
function copyToFile(targetFilePath, sourceFilePath) {
|
|
352
|
+
mkdirp.sync(path.dirname(targetFilePath));
|
|
353
|
+
fs.copyFileSync(sourceFilePath, targetFilePath);
|
|
241
354
|
}
|