profoundjs 5.7.1 → 5.8.2
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/package.json +7 -3
- package/profound.jse +1 -1
- package/setup/convertStartJS.js +404 -0
- package/setup/package.json +2 -2
- package/setup/pjsdist.savf +0 -0
- package/setup/setup.js +8 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const acorn = require("acorn");
|
|
5
|
+
const estraverse = require("estraverse");
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const moment = require("moment");
|
|
8
|
+
const profound = require("profoundjs");
|
|
9
|
+
|
|
10
|
+
module.exports = function(file) {
|
|
11
|
+
|
|
12
|
+
let content = fs.readFileSync(file, "utf8");
|
|
13
|
+
const ast = acorn.parse(content, {
|
|
14
|
+
ecmaVersion: "latest",
|
|
15
|
+
sourceType: "script",
|
|
16
|
+
allowReturnOutsideFunction: true,
|
|
17
|
+
allowHashBang: true
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
let callAPIs = [];
|
|
21
|
+
const scopeStack = [];
|
|
22
|
+
const fnStack = [];
|
|
23
|
+
const classStack = [];
|
|
24
|
+
const scopeMap = new Map();
|
|
25
|
+
const fnMap = new Map();
|
|
26
|
+
const callMap = new Map();
|
|
27
|
+
const needsAwait = new Map();
|
|
28
|
+
const needsAsync = new Map();
|
|
29
|
+
const insertions = [];
|
|
30
|
+
|
|
31
|
+
let pjsPrefix;
|
|
32
|
+
|
|
33
|
+
// First pass:
|
|
34
|
+
// Sets up variable scopes/names.
|
|
35
|
+
// * This requires a first pass because variables in an outer scope can be declared after contained scopes.
|
|
36
|
+
// * The JavaScript runtime 'hoists' these definitions to the top of their scope, so they are visible to any contained scopes.
|
|
37
|
+
// * To deal with this, we'll copy any names down to contained scopes when exiting each scope.
|
|
38
|
+
// Sets up function map.
|
|
39
|
+
// Gets Profound.js prefix from require() call.
|
|
40
|
+
estraverse.traverse(ast, {
|
|
41
|
+
enter: (node, parent) => {
|
|
42
|
+
if (node.type === "Program" || isFunction(node) || (node.type === "BlockStatement" && !isFunction(parent))) {
|
|
43
|
+
// Set up new scope.
|
|
44
|
+
const scope = {
|
|
45
|
+
node: node,
|
|
46
|
+
objects: new Map(),
|
|
47
|
+
scopes: new Map()
|
|
48
|
+
};
|
|
49
|
+
// Keep track of contained scopes.
|
|
50
|
+
const containingScope = scopeMap.get(scopeStack[scopeStack.length - 1]);
|
|
51
|
+
if (containingScope)
|
|
52
|
+
containingScope.scopes.set(node, scope);
|
|
53
|
+
// Add function parameter names to scope.
|
|
54
|
+
if (isFunction(node)) {
|
|
55
|
+
node.params.forEach(param => {
|
|
56
|
+
if (param.type === "AssignmentPattern")
|
|
57
|
+
param = param.left;
|
|
58
|
+
if (param.type === "Identifier")
|
|
59
|
+
scope.objects.set(param.name, { scope: scope });
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
scopeMap.set(node, scope);
|
|
63
|
+
scopeStack.push(node);
|
|
64
|
+
// Setup function map.
|
|
65
|
+
if (node.type !== "BlockStatement") {
|
|
66
|
+
let useStrict;
|
|
67
|
+
if (node.type === "Program") {
|
|
68
|
+
useStrict = node.body.length >= 1 && node.body[0].directive === "use strict";
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
const containingFn = fnStack[fnStack.length - 1];
|
|
72
|
+
const containingFnInfo = fnMap.get(containingFn);
|
|
73
|
+
if (classStack.length > 0) {
|
|
74
|
+
useStrict = true;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
useStrict = containingFnInfo.useStrict;
|
|
78
|
+
if (node.body.type === "BlockStatement" && node.body.body.length > 0 && node.body.body[0].directive === "use strict")
|
|
79
|
+
useStrict = true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
fnMap.set(node, {
|
|
83
|
+
useStrict,
|
|
84
|
+
parent
|
|
85
|
+
});
|
|
86
|
+
fnStack.push(node);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else if (node.type === "ClassDeclaration" || node.type === "ClassExpression") {
|
|
90
|
+
classStack.push(node);
|
|
91
|
+
}
|
|
92
|
+
else if (node.type === "VariableDeclaration") {
|
|
93
|
+
// Add variable names to scope.
|
|
94
|
+
const fn = fnStack[fnStack.length - 1];
|
|
95
|
+
node.declarations.forEach(variableDeclarator => {
|
|
96
|
+
if (variableDeclarator.id.type === "Identifier") {
|
|
97
|
+
const scope = node.kind === "var" ? scopeMap.get(fn) : scopeMap.get(scopeStack[scopeStack.length - 1]);
|
|
98
|
+
scope.objects.set(variableDeclarator.id.name, { scope: scope });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
else if (node.type === "CallExpression") {
|
|
103
|
+
let calleeName = getQualifiedName(node.callee);
|
|
104
|
+
if (calleeName) {
|
|
105
|
+
calleeName = getCallName(calleeName);
|
|
106
|
+
if (calleeName === "require") {
|
|
107
|
+
const arg = node.arguments[0];
|
|
108
|
+
if (arg && arg.type === "Literal" && arg.value === "profoundjs") {
|
|
109
|
+
let name;
|
|
110
|
+
if (parent.type === "VariableDeclarator" && parent.id.type === "Identifier") {
|
|
111
|
+
name = parent.id.name;
|
|
112
|
+
}
|
|
113
|
+
else if (parent.type === "AssignmentExpression" && parent.right === node) {
|
|
114
|
+
name = getQualifiedName(parent.left);
|
|
115
|
+
}
|
|
116
|
+
if (name)
|
|
117
|
+
pjsPrefix = name;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
leave: (node, parent) => {
|
|
124
|
+
if (node.type === "Program" || isFunction(node) || (node.type === "BlockStatement" && !isFunction(parent))) {
|
|
125
|
+
const scope = scopeMap.get(scopeStack.pop());
|
|
126
|
+
copyScopeObjects(scope); // Copy any names down to contained scopes.
|
|
127
|
+
if (node.type !== "BlockStatement") {
|
|
128
|
+
fnStack.pop();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else if (node.type === "ClassDeclaration" || node.type === "ClassExpression") {
|
|
132
|
+
classStack.pop();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
if (!pjsPrefix) {
|
|
138
|
+
throw new Error("Unable to locate require(\"profoundjs\")");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
callAPIs = [pjsPrefix + ".call", pjsPrefix + ".fiber.call"];
|
|
142
|
+
|
|
143
|
+
estraverse.traverse(ast, {
|
|
144
|
+
enter: (node, parent) => {
|
|
145
|
+
if (node.type === "Program" || isFunction(node) || (node.type === "BlockStatement" && !isFunction(parent))) {
|
|
146
|
+
scopeStack.push(node);
|
|
147
|
+
if (node.type !== "BlockStatement") {
|
|
148
|
+
fnStack.push(node);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const fn = fnStack[fnStack.length - 1];
|
|
152
|
+
const scope = scopeMap.get(scopeStack[scopeStack.length - 1]);
|
|
153
|
+
if (isFunction(node)) {
|
|
154
|
+
// Update function map entries with function name and name scope.
|
|
155
|
+
const fnInfo = fnMap.get(node);
|
|
156
|
+
const containingScope = scopeMap.get(scopeStack[scopeStack.length - 2]);
|
|
157
|
+
const containingFn = fnStack[fnStack.length - 2];
|
|
158
|
+
if (node.type === "FunctionDeclaration") {
|
|
159
|
+
if (node.id.name)
|
|
160
|
+
fnInfo.name = node.id.name;
|
|
161
|
+
const containingFnInfo = fnMap.get(containingFn);
|
|
162
|
+
fnInfo.scope = (containingFnInfo.useStrict && containingScope.node.type === "BlockStatement") ? containingScope.node : containingFn;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
let name;
|
|
166
|
+
if (parent.type === "VariableDeclarator") {
|
|
167
|
+
name = getQualifiedName(parent.id);
|
|
168
|
+
}
|
|
169
|
+
else if (parent.type === "AssignmentExpression" && parent.right === node) {
|
|
170
|
+
name = getQualifiedName(parent.left);
|
|
171
|
+
}
|
|
172
|
+
if (name) {
|
|
173
|
+
fnInfo.name = name;
|
|
174
|
+
const baseName = name.split(".")[0];
|
|
175
|
+
const entry = containingScope.objects.get(baseName);
|
|
176
|
+
fnInfo.scope = entry ? entry.scope.node : ast;
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
fnInfo.scope = containingFn;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else if (node.type === "CallExpression") {
|
|
184
|
+
callMap.set(node, {
|
|
185
|
+
containingFn: fn,
|
|
186
|
+
parent: parent
|
|
187
|
+
});
|
|
188
|
+
const callee = node.callee;
|
|
189
|
+
let calleeName = getQualifiedName(callee);
|
|
190
|
+
if (calleeName && calleeName.startsWith(pjsPrefix + ".")) {
|
|
191
|
+
const internalName = "profound" + getCallName(calleeName.substring(pjsPrefix.length));
|
|
192
|
+
let fn;
|
|
193
|
+
try {
|
|
194
|
+
eval("fn = " + internalName);
|
|
195
|
+
}
|
|
196
|
+
catch (error) {}
|
|
197
|
+
if (fn && typeof(fn) === "function" && fn.constructor.name === "AsyncFunction") {
|
|
198
|
+
needsAwait.set(node, true);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
leave: (node, parent) => {
|
|
204
|
+
if (node.type === "Program" || isFunction(node) || (node.type === "BlockStatement" && !isFunction(parent))) {
|
|
205
|
+
scopeStack.pop();
|
|
206
|
+
if (node.type !== "BlockStatement") {
|
|
207
|
+
fnStack.pop();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
findAsyncCalls(needsAwait, needsAsync, callMap, fnMap);
|
|
214
|
+
|
|
215
|
+
let changed = false;
|
|
216
|
+
|
|
217
|
+
needsAwait.forEach((value, callExpression) => {
|
|
218
|
+
const callInfo = callMap.get(callExpression);
|
|
219
|
+
if (callInfo.parent.type !== "AwaitExpression") {
|
|
220
|
+
insertions.push(
|
|
221
|
+
{
|
|
222
|
+
pos: callExpression.start,
|
|
223
|
+
code: "(await"
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
pos: callExpression.end,
|
|
227
|
+
code: ")"
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
let topLevelAwait = false;
|
|
234
|
+
needsAsync.forEach((value, fn) => {
|
|
235
|
+
if (fn.type === "Program") {
|
|
236
|
+
topLevelAwait = true;
|
|
237
|
+
}
|
|
238
|
+
else if (!fn.async) {
|
|
239
|
+
const fnInfo = fnMap.get(fn);
|
|
240
|
+
const parent = fnInfo.parent;
|
|
241
|
+
if (parent && parent.type === "MethodDefinition" && (parent.kind !== "method" || !parent.key))
|
|
242
|
+
return;
|
|
243
|
+
const start = parent.type === "MethodDefinition" ? parent.key.start : fn.start;
|
|
244
|
+
insertions.push({
|
|
245
|
+
pos: start,
|
|
246
|
+
code: "async"
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
insertions.sort(function (a, b) {
|
|
252
|
+
if (a.pos === b.pos) return (a.atEnd || 0) - (b.atEnd || 0);
|
|
253
|
+
return a.pos - b.pos;
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const newCode = [];
|
|
257
|
+
if (insertions.length) {
|
|
258
|
+
// For performance -- Instead of string concat use an array to hold the parts and pieces -- then join them together
|
|
259
|
+
let lastPos = 0;
|
|
260
|
+
for (let i = 0; i < insertions.length; i++) {
|
|
261
|
+
const insertion = insertions[i];
|
|
262
|
+
if (lastPos < insertion.pos) newCode.push(content.substring(lastPos, insertion.pos));
|
|
263
|
+
newCode.push(insertion.code);
|
|
264
|
+
lastPos = insertion.pos;
|
|
265
|
+
}
|
|
266
|
+
if (lastPos < content.length) newCode.push(content.substring(lastPos));
|
|
267
|
+
content = newCode.join(" ");
|
|
268
|
+
changed = true;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (topLevelAwait) {
|
|
272
|
+
// Detect EOL by counting CRLFs and LFs.
|
|
273
|
+
let EOL = "\n";
|
|
274
|
+
const match = content.match(/\r\n|\n/g);
|
|
275
|
+
if (match) {
|
|
276
|
+
const lfs = match.filter(el => el === "\n").length;
|
|
277
|
+
const crlfs = match.length - lfs;
|
|
278
|
+
if (lfs > crlfs)
|
|
279
|
+
EOL = "\n";
|
|
280
|
+
else if (crlfs > lfs)
|
|
281
|
+
EOL = "\r\n";
|
|
282
|
+
}
|
|
283
|
+
const lines = content.split(EOL);
|
|
284
|
+
let index = 0;
|
|
285
|
+
if (lines[0].startsWith("#!")) {
|
|
286
|
+
index = 1;
|
|
287
|
+
}
|
|
288
|
+
lines.splice(index, 0, "async function startPJS() { ");
|
|
289
|
+
for (let i = index + 1; i < lines.length; i++) {
|
|
290
|
+
lines[i] = " " + lines[i];
|
|
291
|
+
}
|
|
292
|
+
lines.push("}", "startPJS();");
|
|
293
|
+
content = lines.join(EOL);
|
|
294
|
+
changed = true;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (changed) {
|
|
298
|
+
const backupFile = file + ".orig." + moment(new Date()).format("YYYYMMDDHHmmss");
|
|
299
|
+
fs.copyFileSync(file, backupFile);
|
|
300
|
+
fs.writeFileSync(file, content, "utf8");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return changed;
|
|
304
|
+
|
|
305
|
+
function getCallName(name) {
|
|
306
|
+
if (callAPIs.includes(name))
|
|
307
|
+
return name;
|
|
308
|
+
else
|
|
309
|
+
return name.replace(/\.(call|apply)$/, "");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Given a list of CallExpressions needing await (needsAwait):
|
|
313
|
+
// Find all functions that need to be made async (needsAsync) and all functions/calls that need async/await as a result.
|
|
314
|
+
// Functions that contain await calls need to be made async, and any caller of the containing function needs to await the result.
|
|
315
|
+
// So then the caller of the containing function needs to be made aysnc, and so on...
|
|
316
|
+
function findAsyncCalls(needsAwait, needsAsync, callMap, fnMap) {
|
|
317
|
+
|
|
318
|
+
find(new Map(needsAwait));
|
|
319
|
+
|
|
320
|
+
function find(calls) {
|
|
321
|
+
calls.forEach((value, callExpression) => {
|
|
322
|
+
const callInfo = callMap.get(callExpression);
|
|
323
|
+
const containingFn = callInfo.containingFn;
|
|
324
|
+
needsAsync.set(containingFn, true);
|
|
325
|
+
if (containingFn.type === "Program")
|
|
326
|
+
return;
|
|
327
|
+
const containingFnInfo = fnMap.get(containingFn);
|
|
328
|
+
if (containingFnInfo.scope && containingFnInfo.name && containingFnInfo.name !== "startPJS") {
|
|
329
|
+
const newCalls = new Map();
|
|
330
|
+
estraverse.traverse(containingFnInfo.scope, {
|
|
331
|
+
enter: (node, parent) => {
|
|
332
|
+
if (node.type === "CallExpression") {
|
|
333
|
+
let name = getQualifiedName(node.callee);
|
|
334
|
+
if (name) {
|
|
335
|
+
name = getCallName(name);
|
|
336
|
+
if (name === containingFnInfo.name) {
|
|
337
|
+
newCalls.set(node, true);
|
|
338
|
+
needsAwait.set(node, true);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
find(newCalls);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Given an Identifier or MemberExpression, tries to return a fully qualified name.
|
|
354
|
+
// Returns undefined if the name can't be determined due to dynamic parts.
|
|
355
|
+
function getQualifiedName(node) {
|
|
356
|
+
|
|
357
|
+
if (node.type !== "Identifier" && node.type !== "MemberExpression")
|
|
358
|
+
return;
|
|
359
|
+
|
|
360
|
+
const parts = process(node);
|
|
361
|
+
if (parts.indexOf(undefined) === -1)
|
|
362
|
+
return parts.join(".");
|
|
363
|
+
|
|
364
|
+
function process(node) {
|
|
365
|
+
const parts = [];
|
|
366
|
+
if (node.type === "Identifier") {
|
|
367
|
+
parts.push(node.name);
|
|
368
|
+
}
|
|
369
|
+
else if (node.type === "Literal" && typeof node.value === "string") {
|
|
370
|
+
parts.push(node.value);
|
|
371
|
+
}
|
|
372
|
+
else if (node.type === "MemberExpression") {
|
|
373
|
+
parts.push(...process(node.object));
|
|
374
|
+
if (node.computed === true && node.property.type !== "Literal")
|
|
375
|
+
parts.push("__COMPUTED__");
|
|
376
|
+
else
|
|
377
|
+
parts.push(...process(node.property));
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
parts.push(undefined);
|
|
381
|
+
}
|
|
382
|
+
return parts;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function copyScopeObjects(scope, objects) {
|
|
388
|
+
if (!objects) {
|
|
389
|
+
objects = Object.fromEntries(scope.objects);
|
|
390
|
+
}
|
|
391
|
+
scope.scopes.forEach(scope => {
|
|
392
|
+
for (const name in objects) {
|
|
393
|
+
const object = objects[name];
|
|
394
|
+
if (!scope.objects.has(name)) {
|
|
395
|
+
scope.objects.set(name, object);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
copyScopeObjects(scope, objects);
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function isFunction(node) {
|
|
403
|
+
return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
|
|
404
|
+
}
|
package/setup/package.json
CHANGED
package/setup/pjsdist.savf
CHANGED
|
Binary file
|
package/setup/setup.js
CHANGED
|
@@ -175,6 +175,14 @@ function createPackageFile() {
|
|
|
175
175
|
function createStart(callback) {
|
|
176
176
|
if (fileExists(deployDir + dirSep + "start.js")) {
|
|
177
177
|
console.log("start.js file exists.");
|
|
178
|
+
const profound = require("profoundjs");
|
|
179
|
+
if (profound.PROMISES_MODE) {
|
|
180
|
+
const convertStartJS = require("./convertStartJS.js");
|
|
181
|
+
const changed = convertStartJS(deployDir + dirSep + "start.js");
|
|
182
|
+
if (changed) {
|
|
183
|
+
console.log("start.js file converted to Promises.");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
178
186
|
callback();
|
|
179
187
|
}
|
|
180
188
|
else {
|