@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.
@@ -0,0 +1,147 @@
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.isGwtBundle = exports.determineGwtFileUid = exports.loadInputSourceMapsGwt = exports.extractGwtCallInfos = exports.determineSymbolMapsDir = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const FileSystem_1 = require("./FileSystem");
9
+ const types_1 = require("@babel/types");
10
+ const parser_1 = require("@babel/parser");
11
+ const commons_1 = require("@cqse/commons");
12
+ /**
13
+ * There are different places where a 'symbolMaps' folder can be:
14
+ * (1) within the `WEB-INF` folder (`WEB-INF/deploy/<module-name>/symbolMaps`)
15
+ * or (2) it can be a sibling of the parent `deferredjs` folder.
16
+ *
17
+ * @param taskFile - Path to the JS bundle file to start searching from.
18
+ */
19
+ function determineSymbolMapsDir(taskFile) {
20
+ const symbolMapDirs = [];
21
+ let webInfDir = null;
22
+ const pathComponents = path_1.default.resolve(taskFile).split(path_1.default.sep);
23
+ for (let i = pathComponents.length - 2; i >= 0; i--) {
24
+ const fullDirPath = pathComponents.slice(0, i).join(path_1.default.sep);
25
+ const webInfDirCandidate = path_1.default.join(fullDirPath, 'WEB-INF');
26
+ if ((0, FileSystem_1.isExistingDirectory)(webInfDirCandidate)) {
27
+ webInfDir = webInfDirCandidate;
28
+ }
29
+ const symbolMapDirCandidate = path_1.default.join(fullDirPath, 'symbolMaps');
30
+ if ((0, FileSystem_1.isExistingDirectory)(symbolMapDirCandidate)) {
31
+ symbolMapDirs.push(symbolMapDirCandidate);
32
+ }
33
+ }
34
+ if (webInfDir != null) {
35
+ (0, FileSystem_1.findSubFolders)(webInfDir, 'symbolMaps').forEach(dir => symbolMapDirs.push(dir));
36
+ }
37
+ return symbolMapDirs;
38
+ }
39
+ exports.determineSymbolMapsDir = determineSymbolMapsDir;
40
+ function expressionToString(expression) {
41
+ if ((0, types_1.isMemberExpression)(expression) && (0, types_1.isExpression)(expression.property)) {
42
+ return `${expressionToString(expression.object)}.${expressionToString(expression.property)}`;
43
+ }
44
+ else if ((0, types_1.isIdentifier)(expression)) {
45
+ return expression.name;
46
+ }
47
+ throw new commons_1.IllegalArgumentException('Type of expression not yet supported.');
48
+ }
49
+ function extractQualifiedFunctionName(call) {
50
+ if ((0, types_1.isMemberExpression)(call.callee) || (0, types_1.isIdentifier)(call.callee)) {
51
+ return expressionToString(call.callee);
52
+ }
53
+ throw new commons_1.IllegalArgumentException('Type of callee not yet supported.');
54
+ }
55
+ /**
56
+ * Extract the GWT function calls from the GWT bundle. These function calls
57
+ * do have the actual application code as arguments.
58
+ *
59
+ * Examples of `bundleContent` (without the double quotes):
60
+ * "showcase.onScriptDownloaded(["var $wnd = ..... __gwtModuleFunction.__moduleStartupDone($gwt.permProps);\n//# sourceURL=showcase-0.js\n"]);"
61
+ * "$wnd.showcase.runAsyncCallback3("function bc(a){Wb((Ze(),Xe),a) ..... nZ5b(El)(3);\n//# sourceURL=showcase-3.js\n")
62
+ */
63
+ function extractGwtCallInfos(bundleContent) {
64
+ const ast = (0, parser_1.parse)(bundleContent);
65
+ if (ast.program.body.length === 0) {
66
+ return null;
67
+ }
68
+ if (!(0, types_1.isExpressionStatement)(ast.program.body[0])) {
69
+ return null;
70
+ }
71
+ const call = ast.program.body[0].expression;
72
+ if (!(0, types_1.isCallExpression)(call)) {
73
+ return null;
74
+ }
75
+ const qualifiedFunctionName = extractQualifiedFunctionName(call);
76
+ const firstArgument = call.arguments[0];
77
+ if ((0, types_1.isArrayExpression)(firstArgument)) {
78
+ const codeArguments = [];
79
+ for (const element of firstArgument.elements) {
80
+ if ((0, types_1.isStringLiteral)(element)) {
81
+ codeArguments.push(element.value);
82
+ }
83
+ else {
84
+ throw new Error(`Did expect only string arguments in the call of "${qualifiedFunctionName}".`);
85
+ }
86
+ }
87
+ return { codeArguments, functionName: qualifiedFunctionName, codeAsArrayArgument: true };
88
+ }
89
+ else if ((0, types_1.isStringLiteral)(firstArgument)) {
90
+ const codeArgument = firstArgument.value;
91
+ return { codeArguments: [codeArgument], functionName: qualifiedFunctionName, codeAsArrayArgument: false };
92
+ }
93
+ return null;
94
+ }
95
+ exports.extractGwtCallInfos = extractGwtCallInfos;
96
+ /**
97
+ * Load the source map for the given GWT bundle.
98
+ */
99
+ function loadInputSourceMapsGwt(taskFile, bundleFile) {
100
+ // taskFile:
101
+ // war/stockwatcher/E2C1FB09E006E0A2420123D036967150.cache.js
102
+ // war/showcase/deferredjs/28F63AD125178AAAB80993C11635D26F/5.cache.js
103
+ const mapDirs = determineSymbolMapsDir(taskFile);
104
+ const fileNumberMatcher = /sourceURL=(.*)-(\d+).js(\\n)*\s*$/;
105
+ const mapModules = bundleFile.codeArguments.map(code => {
106
+ const matches = fileNumberMatcher.exec(code);
107
+ if (!matches) {
108
+ return ['', -1];
109
+ }
110
+ return [matches[1], Number.parseInt(matches[2])];
111
+ });
112
+ const sourceMapFiles = mapModules.map(module => {
113
+ for (const mapDir of mapDirs) {
114
+ const mapFileCandidate = `${mapDir}/${bundleFile.fragmentId}_sourceMap${module[1]}.json`;
115
+ if ((0, FileSystem_1.isExistingFile)(mapFileCandidate)) {
116
+ return mapFileCandidate;
117
+ }
118
+ }
119
+ return undefined;
120
+ });
121
+ return sourceMapFiles.map(file => {
122
+ if (file) {
123
+ return (0, FileSystem_1.sourceMapFromMapFile)(file);
124
+ }
125
+ return undefined;
126
+ });
127
+ }
128
+ exports.loadInputSourceMapsGwt = loadInputSourceMapsGwt;
129
+ /**
130
+ * Determine the ID of the given GWT bundle file.
131
+ */
132
+ function determineGwtFileUid(filename) {
133
+ const fileUidMatcher = /.*([0-9A-Fa-f]{32}).*/;
134
+ const uidMatches = fileUidMatcher.exec(filename);
135
+ if (!uidMatches || uidMatches.length < 2) {
136
+ return undefined;
137
+ }
138
+ return uidMatches[1];
139
+ }
140
+ exports.determineGwtFileUid = determineGwtFileUid;
141
+ /**
142
+ * Is the given bundle a GWT bundle?
143
+ */
144
+ function isGwtBundle(bundle) {
145
+ return bundle.type === 'gwt';
146
+ }
147
+ exports.isGwtBundle = isGwtBundle;
package/dist/src/main.js CHANGED
@@ -7,6 +7,7 @@ App_1.App.run()
7
7
  .then(result => {
8
8
  console.log('Instrumentation finished.');
9
9
  console.log(`\tInstrumented: ${result.translated}`);
10
+ console.log(`\tExcluded: ${result.excluded}`);
10
11
  console.log(`\tInstrumented from cache: ${result.translatedFromCache}`);
11
12
  console.log(`\tAlready instrumented: ${result.alreadyInstrumented}`);
12
13
  console.log(`\tUnsupported: ${result.unsupported}`);
package/dist/vaccine.js CHANGED
@@ -1 +1 @@
1
- (()=>{var x=Object.create;var h=Object.defineProperty;var A=Object.getOwnPropertyDescriptor;var y=Object.getOwnPropertyNames;var $=Object.getPrototypeOf,I=Object.prototype.hasOwnProperty;var j=e=>h(e,"__esModule",{value:!0});var g=(e,n)=>()=>(n||e((n={exports:{}}).exports,n),n.exports);var G=(e,n,t)=>{if(n&&typeof n=="object"||typeof n=="function")for(let o of y(n))!I.call(e,o)&&o!=="default"&&h(e,o,{get:()=>n[o],enumerable:!(t=A(n,o))||t.enumerable});return e},b=e=>G(j(h(e!=null?x($(e)):{},"default",e&&e.__esModule&&"default"in e?{get:()=>e.default,enumerable:!0}:{value:e,enumerable:!0})),e);var k=g((K,C)=>{C.exports=!1});var O=g(()=>{});function f(e){let n=new Blob([e],{type:"text/javascript"}),t=URL.createObjectURL(n),o=new Worker(t);return URL.revokeObjectURL(t),o}function p(){return f('var n=class{constructor(t){this.cachedMessages=[];this.url=t,this.socket=this.createSocket()}createSocket(){let t=new WebSocket(this.url);return t.onopen=()=>this.onopen(),t.onclose=()=>this.onclose(),t}onclose(){this.socket=this.createSocket()}onopen(){this.cachedMessages.forEach(t=>this.socket.send(t)),this.cachedMessages=[]}send(t){this.socket.readyState===WebSocket.OPEN?this.socket.send(t):this.cachedMessages.push(t)}};var o;(function(e){e.MESSAGE_TYPE_SOURCEMAP="s",e.MESSAGE_TYPE_COVERAGE="c"})(o||(o={}));var C=20,p=1e3,a=class{constructor(t,e){this.milliseconds=t;this.onCountedToZero=e;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)}},r=class{constructor(t){this.socket=t,this.cachedCoveredPositions=new Map,this.numberOfCachedPositions=0,this.flushCountdown=new a(p,()=>this.flush())}add(t){let e=t.split(":");if(e.length!==3)return;let[c,u,l]=e,i=this.cachedCoveredPositions.get(c);i||(i=new Set,this.cachedCoveredPositions.set(c,i)),i.add(`${u}:${l}`),this.numberOfCachedPositions+=1,this.flushCountdown.restartCountdown(),this.numberOfCachedPositions>=C&&this.flush()}flush(){this.numberOfCachedPositions!==0&&(this.flushCountdown.stopCountdown(),this.cachedCoveredPositions.forEach((t,e)=>{this.socket.send(`${o.MESSAGE_TYPE_COVERAGE} ${e} ${Array.from(t).join(" ")}`)}),this.cachedCoveredPositions=new Map,this.numberOfCachedPositions=0)}};console.log("Starting coverage forwarding worker.");var h=new n("ws://$REPORT_TO_HOST:$REPORT_TO_PORT/socket"),d=new r(h);onmessage=s=>{let t=s.data;t.startsWith(o.MESSAGE_TYPE_SOURCEMAP)?h.send(t):t==="unload"?d.flush():d.add(t)};\n')}function u(){return c()}function E(){return typeof window!="undefined"}function c(){return window}function m(e,n){let t=u()[e];return t||(t=n,u()[e]=t),t}var L="s",S=class{constructor(n,t){this.coverageObj=n;this.path=t}get(n,t,o){let r=n[t];return r!==Object(r)?r:w(this.coverageObj,r,[...this.path,t])}set(n,t,o){let r=[...this.path,t];if(r[0]===L){let i=this.coverageObj.hash,s=this.coverageObj.statementMap[r[1]].start;u()._$Bc(i,s.line,s.column)}return!0}};function w(e,n,t){return new Proxy(n,new S(e,t))}var _=b(k());function U(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 M={add:U};var P=b(O()),D=_.default?P.default:M,d=new Set,T=!1;function H(){T||(T=!0,D.add(B))}function W(e){if(H(),typeof e!="function")throw new Error("Listener is no function");d.add(e);var n={remove:function(){return d.delete(e)},run:function(){return d.delete(e),e()}};return n}function B(){var e=[];return d.forEach(function(n){e.push(n()),d.delete(n)}),Promise.all(e)}var l;(function(t){t.MESSAGE_TYPE_SOURCEMAP="s",t.MESSAGE_TYPE_COVERAGE="c"})(l||(l={}));var R=m("__TS_AGENT",{});function v(){return R._$BcWorker}function Y(e){return R._$BcWorker=e,e}u().makeCoverageInterceptor=function(e){let n=e.hash;if(!v()){let t=Y(new p);(function(){let r=function(i){let s=c()[i];c()[i]=function(...a){if(t.postMessage("unload"),s)return s.apply(this,a)},E()&&Object.defineProperty(c(),i,{get:function(){return s},set:function(a){s=a}})};r("onunload"),r("onbeforeunload"),W(()=>t.postMessage("unload"))})()}return function(){let o=m("sentMaps",new Set);e.inputSourceMap&&(o.has(e.path)||(v().postMessage(`${l.MESSAGE_TYPE_SOURCEMAP} ${n}:${JSON.stringify(e.inputSourceMap)}`),o.add(e.path)))}(),function(){let o=new Set;u()._$Bc=(r,i,s)=>{let a=`${r}:${i}:${s}`;o.has(a)||(v().postMessage(a),o.add(a))}}(),w(e,e,[])};})();
1
+ "use strict";(()=>{function c(t){let n=new Blob([t],{type:"text/javascript"}),e=URL.createObjectURL(n),o=new Worker(e);return URL.revokeObjectURL(e),o}function l(){return c('var r=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 i=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)}};var d=20,l=1e3,u="c",a=class{constructor(e){this.socket=e,this.cachedCoveredRanges=new Map,this.numberOfCachedPositions=0,this.flushCountdown=new i(l,()=>this.flush())}addRanges(e,t){let s=this.cachedCoveredRanges.get(e);s||(s={lines:[]},this.cachedCoveredRanges.set(e,s)),t.lines.forEach(n=>s.lines.push(n)),this.numberOfCachedPositions+=1,this.flushCountdown.restartCountdown(),this.numberOfCachedPositions>=d&&this.flush()}arrayToLineCov(e){if(e.length%2!==0)throw new Error("Unexpected length of input data.");let t=[];for(let s=0;s<e.length;s=s+2){let n=e[s],c=e[s+1];n===c?t.push(`${n}`):t.push(`${n}-${c}`)}return t.join(";")}flush(){if(this.numberOfCachedPositions===0)return;this.flushCountdown.stopCountdown();let e=[];this.cachedCoveredRanges.forEach((t,s)=>{e.push(`@${s}`),e.push(this.arrayToLineCov(t.lines))}),this.socket.send(`${u} ${e.join(";")}`),this.cachedCoveredRanges.clear(),this.numberOfCachedPositions=0}};console.log("Starting coverage forwarding worker.");var g=new r("$REPORT_TO_URL/socket"),h=new a(g);onmessage=o=>{if(Array.isArray(o.data)){let[e,t]=o.data;h.addRanges(e,t)}else o.data==="unload"?h.flush():console.error(`No handler for message: ${o.data}`)};\n')}function u(){return h()}function h(){return window}function d(t,n){let e=u()[t];return e||(e=n,u()[t]=e),e}function f(t,n){let e=new Map;function o(r){let s=e.get(r);return s||(s={lines:[]},e.set(r,s),s)}function i(r,s,C){o(r).lines.push(s,C)}function a(){n(e),e.clear()}return setInterval(()=>a(),t),{putLineCoverage:i,flush:a}}var g=d("__TS_AGENT",{});function p(){return g._$BcWorker}function w(t){return g._$BcWorker=t,t}var v=f(250,t=>{for(let n of t.entries())p().postMessage(n)});u()._$l=v.putLineCoverage;if(!p()){let t=w(new l);(function(){let e=()=>{v.flush(),t.postMessage("unload")},o=function(a,r){r&&r.addEventListener(a,e,{capture:!0})},i=h();o("blur",i),o("unload",i),o("visibilitychange",i),o("beforeunload",i)})()}})();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@teamscale/javascript-instrumenter",
3
- "version": "0.0.1-beta.9",
4
- "description": "Istanbul-based coverage instrumenter with coverage forwarding via WebSockets",
3
+ "version": "0.1.0-beta.3",
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",
@@ -11,57 +11,63 @@
11
11
  "type": "git",
12
12
  "url": "https://github.com/cqse/teamscale-javascript-profiler.git"
13
13
  },
14
- "scripts": {
15
- "clean": "rimraf dist tsconfig.tsbuildinfo",
16
- "build": "tsc --project tsconfig.json && node esbuild.mjs",
17
- "instrumenter": "node dist/src/main.js",
18
- "test": "yarn build && NODE_OPTIONS='--experimental-vm-modules' jest --forceExit --coverage --silent=true --detectOpenHandles"
19
- },
20
14
  "files": [
21
15
  "dist/**/*"
22
16
  ],
23
17
  "devDependencies": {
24
- "@babel/core": "^7.14.0",
25
- "@babel/plugin-transform-modules-commonjs": "^7.15.4",
26
- "@babel/preset-env": "^7.14.1",
27
- "@types/async": "^3.2.6",
28
- "@types/convert-source-map": "^1.5.1",
29
- "@types/glob": "^7.1.3",
30
- "@types/istanbul-lib-instrument": "^1.7.4",
31
- "@types/jest": "^27.0.1",
32
- "@types/mkdirp": "^1.0.1",
33
- "@types/node": "^15.0.1",
34
- "@types/source-map": "^0.5.7",
35
- "@types/ws": "^7.4.4",
36
- "babel-jest": "^27.2.0",
37
- "esbuild": "^0.13.3",
18
+ "@babel/core": "^7.23.7",
19
+ "@babel/plugin-transform-modules-commonjs": "^7.23.3",
20
+ "@babel/preset-env": "^7.23.8",
21
+ "@types/argparse": "^2.0.14",
22
+ "@types/async": "^3.2.24",
23
+ "@types/babel__generator": "^7.6.8",
24
+ "@types/babel__traverse": "^7.20.5",
25
+ "@types/bunyan": "^1.8.11",
26
+ "@types/convert-source-map": "^2.0.3",
27
+ "@types/glob": "^8.1.0",
28
+ "@types/istanbul-lib-instrument": "^1.7.7",
29
+ "@types/jest": "^29.5.11",
30
+ "@types/node": "^20.11.0",
31
+ "@types/ws": "^8.5.10",
32
+ "babel-jest": "^29.7.0",
33
+ "esbuild": "^0.19.11",
38
34
  "esbuild-plugin-inline-worker": "^0.1.1",
39
- "jest": "^27.2.0",
40
- "rimraf": "^3.0.2",
41
- "ts-jest": "^27.0.5",
42
- "ts-node": "^10.2.1",
43
- "tslib": "^2.2.0",
44
- "typescript": "^4.4.3"
35
+ "jest": "^29.7.0",
36
+ "rimraf": "^5.0.5",
37
+ "ts-jest": "^29.1.1",
38
+ "ts-node": "^10.9.2",
39
+ "tslib": "^2.6.2",
40
+ "typescript": "^5.3.3"
45
41
  },
46
42
  "dependencies": {
47
- "@cqse/commons": "^0.0.1-beta.1",
48
- "@types/micromatch": "^4.0.2",
43
+ "@cqse/commons": "0.1.0-beta.2",
44
+ "@babel/generator": "^7.23.6",
45
+ "@babel/parser": "^7.23.6",
46
+ "@babel/traverse": "^7.23.7",
47
+ "@babel/types": "^7.23.6",
48
+ "@types/micromatch": "^4.0.6",
49
49
  "argparse": "^2.0.1",
50
- "async": "^3.2.0",
51
- "convert-source-map": "^1.7.0",
52
- "foreground-child": "^2.0.0",
53
- "glob": "^7.1.7",
54
- "istanbul-lib-instrument": "^4.0.0",
55
- "micromatch": "4.0.4",
56
- "mkdirp": "^1.0.4",
57
- "nyc": "^15.1.0",
58
- "source-map": "0.6.1",
50
+ "async": "^3.2.5",
51
+ "bunyan": "^1.8.15",
52
+ "convert-source-map": "^2.0.0",
53
+ "foreground-child": "^3.1.1",
54
+ "glob": "^10.3.10",
55
+ "micromatch": "4.0.5",
56
+ "mkdirp": "^3.0.1",
57
+ "source-map": "0.7.4",
59
58
  "typescript-optional": "^2.0.1",
60
- "unload": "^2.2.0",
61
- "web-worker": "^1.0.0",
62
- "winston": "^3.3.3"
59
+ "unload": "^2.4.1",
60
+ "web-worker": "^1.3.0",
61
+ "@teamscale/lib-instrument": "0.1.0-beta.3"
63
62
  },
64
63
  "publishConfig": {
65
64
  "access": "public"
65
+ },
66
+ "scripts": {
67
+ "clean": "rimraf dist tsconfig.tsbuildinfo",
68
+ "build": "tsc --project tsconfig.json && pnpm buildVaccine",
69
+ "buildVaccine": "node esbuild.mjs",
70
+ "instrumenter": "node dist/src/main.js",
71
+ "test": "pnpm build && NODE_OPTIONS='--experimental-vm-modules --max-old-space-size=8192' jest --forceExit --coverage --silent=true --detectOpenHandles"
66
72
  }
67
- }
73
+ }