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.
@@ -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
+ }
@@ -12,10 +12,10 @@
12
12
  "profoundui"
13
13
  ],
14
14
  "engines": {
15
- "node": ">=10"
15
+ "node": "^12 || ^14"
16
16
  },
17
17
  "dependencies": {
18
- "profoundjs": "^5.7.1"
18
+ "profoundjs": "^5.8.2"
19
19
  },
20
20
  "scripts": {
21
21
  "start": "node start.js"
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 {