@angular-wave/angular.ts 0.0.49 → 0.0.51

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.
@@ -1,73 +1,86 @@
1
- import {
2
- assignableAST,
3
- findConstantAndWatchExpressions,
4
- getInputs,
5
- getStringValue,
6
- plusFn,
7
- } from "./shared";
8
- import { forEach, isDefined } from "../../shared/utils";
1
+ import { isDefined } from "../../shared/utils";
9
2
  import { ASTType } from "./ast-type";
10
3
 
11
- export function ASTInterpreter($filter) {
12
- this.$filter = $filter;
13
- }
4
+ export const PURITY_ABSOLUTE = 1;
5
+ export const PURITY_RELATIVE = 2;
6
+
7
+ export class ASTInterpreter {
8
+ /**
9
+ * @param {function(any):any} $filter
10
+ */
11
+ constructor($filter) {
12
+ this.$filter = $filter;
13
+ }
14
14
 
15
- ASTInterpreter.prototype = {
15
+ /**
16
+ * Compiles the AST into a function.
17
+ * @param {import("./ast").ASTNode} ast - The AST to compile.
18
+ * @returns {import("./parse").CompiledExpression}
19
+ */
16
20
  compile(ast) {
17
- const self = this;
18
- findConstantAndWatchExpressions(ast, self.$filter);
21
+ let decoratedNode = findConstantAndWatchExpressions(ast, this.$filter);
22
+ /** @type {import("./ast").ASTNode} */
19
23
  let assignable;
24
+ /** @type {import("./parse").CompiledExpression} */
20
25
  let assign;
21
- if ((assignable = assignableAST(ast))) {
22
- assign = this.recurse(assignable);
26
+ if ((assignable = assignableAST(decoratedNode))) {
27
+ assign = /** @type {import("./parse").CompiledExpression} */ (
28
+ this.recurse(assignable)
29
+ );
23
30
  }
24
- const toWatch = getInputs(ast.body);
31
+ const toWatch = getInputs(decoratedNode.body);
25
32
  let inputs;
26
33
  if (toWatch) {
27
34
  inputs = [];
28
- forEach(toWatch, (watch, key) => {
29
- const input = self.recurse(watch);
35
+ for (const [key, watch] of Object.entries(toWatch)) {
36
+ const input = /** @type {import("./parse").CompiledExpression} */ (
37
+ this.recurse(watch)
38
+ );
30
39
  input.isPure = watch.isPure;
31
40
  watch.input = input;
32
41
  inputs.push(input);
33
42
  watch.watchId = key;
34
- });
43
+ }
35
44
  }
36
45
  const expressions = [];
37
- forEach(ast.body, (expression) => {
38
- expressions.push(self.recurse(expression.expression));
46
+ decoratedNode.body.forEach((expression) => {
47
+ expressions.push(this.recurse(expression.expression));
39
48
  });
49
+
50
+ /** @type {import("./parse").CompiledExpression} */
40
51
  const fn =
41
- ast.body.length === 0
52
+ decoratedNode.body.length === 0
42
53
  ? () => {}
43
- : ast.body.length === 1
54
+ : decoratedNode.body.length === 1
44
55
  ? expressions[0]
45
56
  : function (scope, locals) {
46
57
  let lastValue;
47
- forEach(expressions, (exp) => {
58
+ expressions.forEach((exp) => {
48
59
  lastValue = exp(scope, locals);
49
60
  });
50
61
  return lastValue;
51
62
  };
52
63
  if (assign) {
53
- fn.assign = function (scope, value, locals) {
54
- return assign(scope, locals, value);
55
- };
64
+ fn.assign = (scope, value, locals) => assign(scope, locals, value);
56
65
  }
57
66
  if (inputs) {
58
67
  fn.inputs = inputs;
59
68
  }
60
69
  return fn;
61
- },
70
+ }
62
71
 
72
+ /**
73
+ * Recurses the AST nodes.
74
+ * @param {import("./ast").ASTNode} ast - The AST node.
75
+ * @param {Object} [context] - The context.
76
+ * @param {boolean|1} [create] - The create flag.
77
+ * @returns {import("./parse").CompiledExpressionFunction} The recursive function.
78
+ */
63
79
  recurse(ast, context, create) {
64
80
  let left;
65
81
  let right;
66
82
  const self = this;
67
83
  let args;
68
- if (ast.input) {
69
- return this.inputs(ast.input, ast.watchId);
70
- }
71
84
  switch (ast.type) {
72
85
  case ASTType.Literal:
73
86
  return this.value(ast.value, context);
@@ -98,33 +111,43 @@ ASTInterpreter.prototype = {
98
111
  }
99
112
  if (ast.computed) right = this.recurse(ast.property);
100
113
  return ast.computed
101
- ? this.computedMember(left, right, context, create)
102
- : this.nonComputedMember(left, right, context, create);
114
+ ? this.computedMember(
115
+ left,
116
+ /** @type {function } */ (right),
117
+ context,
118
+ create,
119
+ )
120
+ : this.nonComputedMember(
121
+ left,
122
+ /** @type {string } */ (right),
123
+ context,
124
+ create,
125
+ );
103
126
  case ASTType.CallExpression:
104
127
  args = [];
105
- forEach(ast.arguments, (expr) => {
128
+ ast.arguments.forEach((expr) => {
106
129
  args.push(self.recurse(expr));
107
130
  });
108
131
  if (ast.filter) right = this.$filter(ast.callee.name);
109
132
  if (!ast.filter) right = this.recurse(ast.callee, true);
110
133
  return ast.filter
111
- ? function (scope, locals, assign, inputs) {
134
+ ? function (scope, locals, assign) {
112
135
  const values = [];
113
136
  for (let i = 0; i < args.length; ++i) {
114
- values.push(args[i](scope, locals, assign, inputs));
137
+ values.push(args[i](scope, locals, assign));
115
138
  }
116
- const value = right.apply(undefined, values, inputs);
139
+ const value = right.apply(undefined, values);
117
140
  return context
118
141
  ? { context: undefined, name: undefined, value }
119
142
  : value;
120
143
  }
121
- : function (scope, locals, assign, inputs) {
122
- const rhs = right(scope, locals, assign, inputs);
144
+ : function (scope, locals, assign) {
145
+ const rhs = right(scope, locals, assign);
123
146
  let value;
124
147
  if (rhs.value != null) {
125
148
  const values = [];
126
149
  for (let i = 0; i < args.length; ++i) {
127
- values.push(args[i](scope, locals, assign, inputs));
150
+ values.push(args[i](scope, locals, assign));
128
151
  }
129
152
  value = rhs.value.apply(rhs.context, values);
130
153
  }
@@ -133,27 +156,27 @@ ASTInterpreter.prototype = {
133
156
  case ASTType.AssignmentExpression:
134
157
  left = this.recurse(ast.left, true, 1);
135
158
  right = this.recurse(ast.right);
136
- return function (scope, locals, assign, inputs) {
137
- const lhs = left(scope, locals, assign, inputs);
138
- const rhs = right(scope, locals, assign, inputs);
159
+ return function (scope, locals, assign) {
160
+ const lhs = left(scope, locals, assign);
161
+ const rhs = right(scope, locals, assign);
139
162
  lhs.context[lhs.name] = rhs;
140
163
  return context ? { value: rhs } : rhs;
141
164
  };
142
165
  case ASTType.ArrayExpression:
143
166
  args = [];
144
- forEach(ast.elements, (expr) => {
167
+ ast.elements.forEach((expr) => {
145
168
  args.push(self.recurse(expr));
146
169
  });
147
- return function (scope, locals, assign, inputs) {
170
+ return function (scope, locals, assign) {
148
171
  const value = [];
149
172
  for (let i = 0; i < args.length; ++i) {
150
- value.push(args[i](scope, locals, assign, inputs));
173
+ value.push(args[i](scope, locals, assign));
151
174
  }
152
175
  return context ? { value } : value;
153
176
  };
154
177
  case ASTType.ObjectExpression:
155
178
  args = [];
156
- forEach(ast.properties, (property) => {
179
+ ast.properties.forEach((property) => {
157
180
  if (property.computed) {
158
181
  args.push({
159
182
  key: self.recurse(property.key),
@@ -171,18 +194,17 @@ ASTInterpreter.prototype = {
171
194
  });
172
195
  }
173
196
  });
174
- return function (scope, locals, assign, inputs) {
197
+ return function (scope, locals, assign) {
175
198
  const value = {};
176
199
  for (let i = 0; i < args.length; ++i) {
177
200
  if (args[i].computed) {
178
- value[args[i].key(scope, locals, assign, inputs)] = args[i].value(
201
+ value[args[i].key(scope, locals, assign)] = args[i].value(
179
202
  scope,
180
203
  locals,
181
204
  assign,
182
- inputs,
183
205
  );
184
206
  } else {
185
- value[args[i].key] = args[i].value(scope, locals, assign, inputs);
207
+ value[args[i].key] = args[i].value(scope, locals, assign);
186
208
  }
187
209
  }
188
210
  return context ? { value } : value;
@@ -200,11 +222,17 @@ ASTInterpreter.prototype = {
200
222
  return context ? { value: assign } : assign;
201
223
  };
202
224
  }
203
- },
225
+ }
204
226
 
205
- "unary+": function (argument, context) {
206
- return function (scope, locals, assign, inputs) {
207
- let arg = argument(scope, locals, assign, inputs);
227
+ /**
228
+ * Unary plus operation.
229
+ * @param {function} argument - The argument function.
230
+ * @param {Object} [context] - The context.
231
+ * @returns {function} The unary plus function.
232
+ */
233
+ "unary+"(argument, context) {
234
+ return function (scope, locals, assign) {
235
+ let arg = argument(scope, locals, assign);
208
236
  if (isDefined(arg)) {
209
237
  arg = +arg;
210
238
  } else {
@@ -212,11 +240,17 @@ ASTInterpreter.prototype = {
212
240
  }
213
241
  return context ? { value: arg } : arg;
214
242
  };
215
- },
243
+ }
216
244
 
217
- "unary-": function (argument, context) {
218
- return function (scope, locals, assign, inputs) {
219
- let arg = argument(scope, locals, assign, inputs);
245
+ /**
246
+ * Unary minus operation.
247
+ * @param {function} argument - The argument function.
248
+ * @param {Object} [context] - The context.
249
+ * @returns {function} The unary minus function.
250
+ */
251
+ "unary-"(argument, context) {
252
+ return function (scope, locals, assign) {
253
+ let arg = argument(scope, locals, assign);
220
254
  if (isDefined(arg)) {
221
255
  arg = -arg;
222
256
  } else {
@@ -224,146 +258,263 @@ ASTInterpreter.prototype = {
224
258
  }
225
259
  return context ? { value: arg } : arg;
226
260
  };
227
- },
228
- "unary!": function (argument, context) {
229
- return function (scope, locals, assign, inputs) {
230
- const arg = !argument(scope, locals, assign, inputs);
261
+ }
262
+
263
+ /**
264
+ * Unary negation operation.
265
+ * @param {function} argument - The argument function.
266
+ * @param {Object} [context] - The context.
267
+ * @returns {function} The unary negation function.
268
+ */
269
+ "unary!"(argument, context) {
270
+ return function (scope, locals, assign) {
271
+ const arg = !argument(scope, locals, assign);
231
272
  return context ? { value: arg } : arg;
232
273
  };
233
- },
234
- "binary+": function (left, right, context) {
235
- return function (scope, locals, assign, inputs) {
236
- const lhs = left(scope, locals, assign, inputs);
237
- const rhs = right(scope, locals, assign, inputs);
274
+ }
275
+
276
+ /**
277
+ * Binary plus operation.
278
+ * @param {function} left - The left operand function.
279
+ * @param {function} right - The right operand function.
280
+ * @param {Object} [context] - The context.
281
+ * @returns {function} The binary plus function.
282
+ */
283
+ "binary+"(left, right, context) {
284
+ return function (scope, locals, assign) {
285
+ const lhs = left(scope, locals, assign);
286
+ const rhs = right(scope, locals, assign);
238
287
  const arg = plusFn(lhs, rhs);
239
288
  return context ? { value: arg } : arg;
240
289
  };
241
- },
242
- "binary-": function (left, right, context) {
243
- return function (scope, locals, assign, inputs) {
244
- const lhs = left(scope, locals, assign, inputs);
245
- const rhs = right(scope, locals, assign, inputs);
290
+ }
291
+
292
+ /**
293
+ * Binary minus operation.
294
+ * @param {function} left - The left operand function.
295
+ * @param {function} right - The right operand function.
296
+ * @param {Object} [context] - The context.
297
+ * @returns {function} The binary minus function.
298
+ */
299
+ "binary-"(left, right, context) {
300
+ return function (scope, locals, assign) {
301
+ const lhs = left(scope, locals, assign);
302
+ const rhs = right(scope, locals, assign);
246
303
  const arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0);
247
304
  return context ? { value: arg } : arg;
248
305
  };
249
- },
250
- "binary*": function (left, right, context) {
251
- return function (scope, locals, assign, inputs) {
252
- const arg =
253
- left(scope, locals, assign, inputs) *
254
- right(scope, locals, assign, inputs);
306
+ }
307
+
308
+ /**
309
+ * Binary multiplication operation.
310
+ * @param {function} left - The left operand function.
311
+ * @param {function} right - The right operand function.
312
+ * @param {Object} [context] - The context.
313
+ * @returns {function} The binary multiplication function.
314
+ */
315
+ "binary*"(left, right, context) {
316
+ return function (scope, locals, assign) {
317
+ const arg = left(scope, locals, assign) * right(scope, locals, assign);
255
318
  return context ? { value: arg } : arg;
256
319
  };
257
- },
258
- "binary/": function (left, right, context) {
259
- return function (scope, locals, assign, inputs) {
260
- const arg =
261
- left(scope, locals, assign, inputs) /
262
- right(scope, locals, assign, inputs);
320
+ }
321
+
322
+ "binary/"(left, right, context) {
323
+ return function (scope, locals, assign) {
324
+ const arg = left(scope, locals, assign) / right(scope, locals, assign);
263
325
  return context ? { value: arg } : arg;
264
326
  };
265
- },
266
- "binary%": function (left, right, context) {
267
- return function (scope, locals, assign, inputs) {
268
- const arg =
269
- left(scope, locals, assign, inputs) %
270
- right(scope, locals, assign, inputs);
327
+ }
328
+
329
+ /**
330
+ * Binary division operation.
331
+ * @param {function} left - The left operand function.
332
+ * @param {function} right - The right operand function.
333
+ * @param {Object} [context] - The context.
334
+ * @returns {function} The binary division function.
335
+ */
336
+ "binary%"(left, right, context) {
337
+ return function (scope, locals, assign) {
338
+ const arg = left(scope, locals, assign) % right(scope, locals, assign);
271
339
  return context ? { value: arg } : arg;
272
340
  };
273
- },
274
- "binary===": function (left, right, context) {
275
- return function (scope, locals, assign, inputs) {
276
- const arg =
277
- left(scope, locals, assign, inputs) ===
278
- right(scope, locals, assign, inputs);
341
+ }
342
+
343
+ /**
344
+ * Binary strict equality operation.
345
+ * @param {function} left - The left operand function.
346
+ * @param {function} right - The right operand function.
347
+ * @param {Object} [context] - The context.
348
+ * @returns {function} The binary strict equality function.
349
+ */
350
+ "binary==="(left, right, context) {
351
+ return function (scope, locals, assign) {
352
+ const arg = left(scope, locals, assign) === right(scope, locals, assign);
279
353
  return context ? { value: arg } : arg;
280
354
  };
281
- },
282
- "binary!==": function (left, right, context) {
283
- return function (scope, locals, assign, inputs) {
284
- const arg =
285
- left(scope, locals, assign, inputs) !==
286
- right(scope, locals, assign, inputs);
355
+ }
356
+
357
+ /**
358
+ * Binary strict inequality operation.
359
+ * @param {function} left - The left operand function.
360
+ * @param {function} right - The right operand function.
361
+ * @param {Object} [context] - The context.
362
+ * @returns {function} The binary strict inequality function.
363
+ */
364
+ "binary!=="(left, right, context) {
365
+ return function (scope, locals, assign) {
366
+ const arg = left(scope, locals, assign) !== right(scope, locals, assign);
287
367
  return context ? { value: arg } : arg;
288
368
  };
289
- },
290
- "binary==": function (left, right, context) {
291
- return function (scope, locals, assign, inputs) {
292
- const arg =
293
- left(scope, locals, assign, inputs) ==
294
- right(scope, locals, assign, inputs);
369
+ }
370
+
371
+ /**
372
+ * Binary equality operation.
373
+ * @param {function} left - The left operand function.
374
+ * @param {function} right - The right operand function.
375
+ * @param {Object} [context] - The context.
376
+ * @returns {function} The binary equality function.
377
+ */
378
+ "binary=="(left, right, context) {
379
+ return function (scope, locals, assign) {
380
+ const arg = left(scope, locals, assign) == right(scope, locals, assign);
295
381
  return context ? { value: arg } : arg;
296
382
  };
297
- },
298
- "binary!=": function (left, right, context) {
299
- return function (scope, locals, assign, inputs) {
300
- const arg =
301
- left(scope, locals, assign, inputs) !=
302
- right(scope, locals, assign, inputs);
383
+ }
384
+
385
+ /**
386
+ * Binary inequality operation.
387
+ * @param {function} left - The left operand function.
388
+ * @param {function} right - The right operand function.
389
+ * @param {Object} [context] - The context.
390
+ * @returns {function} The binary inequality function.
391
+ */
392
+ "binary!="(left, right, context) {
393
+ return function (scope, locals, assign) {
394
+ const arg = left(scope, locals, assign) != right(scope, locals, assign);
303
395
  return context ? { value: arg } : arg;
304
396
  };
305
- },
306
- "binary<": function (left, right, context) {
307
- return function (scope, locals, assign, inputs) {
308
- const arg =
309
- left(scope, locals, assign, inputs) <
310
- right(scope, locals, assign, inputs);
397
+ }
398
+
399
+ /**
400
+ * Binary less-than operation.
401
+ * @param {function} left - The left operand function.
402
+ * @param {function} right - The right operand function.
403
+ * @param {Object} [context] - The context.
404
+ * @returns {function} The binary less-than function.
405
+ */
406
+ "binary<"(left, right, context) {
407
+ return function (scope, locals, assign) {
408
+ const arg = left(scope, locals, assign) < right(scope, locals, assign);
311
409
  return context ? { value: arg } : arg;
312
410
  };
313
- },
314
- "binary>": function (left, right, context) {
315
- return function (scope, locals, assign, inputs) {
316
- const arg =
317
- left(scope, locals, assign, inputs) >
318
- right(scope, locals, assign, inputs);
411
+ }
412
+
413
+ /**
414
+ * Binary greater-than operation.
415
+ * @param {function} left - The left operand function.
416
+ * @param {function} right - The right operand function.
417
+ * @param {Object} [context] - The context.
418
+ * @returns {function} The binary greater-than function.
419
+ */
420
+ "binary>"(left, right, context) {
421
+ return function (scope, locals, assign) {
422
+ const arg = left(scope, locals, assign) > right(scope, locals, assign);
319
423
  return context ? { value: arg } : arg;
320
424
  };
321
- },
322
- "binary<=": function (left, right, context) {
323
- return function (scope, locals, assign, inputs) {
324
- const arg =
325
- left(scope, locals, assign, inputs) <=
326
- right(scope, locals, assign, inputs);
425
+ }
426
+
427
+ /**
428
+ * Binary less-than-or-equal-to operation.
429
+ * @param {function} left - The left operand function.
430
+ * @param {function} right - The right operand function.
431
+ * @param {Object} [context] - The context.
432
+ * @returns {function} The binary less-than-or-equal-to function.
433
+ */
434
+ "binary<="(left, right, context) {
435
+ return function (scope, locals, assign) {
436
+ const arg = left(scope, locals, assign) <= right(scope, locals, assign);
327
437
  return context ? { value: arg } : arg;
328
438
  };
329
- },
330
- "binary>=": function (left, right, context) {
331
- return function (scope, locals, assign, inputs) {
332
- const arg =
333
- left(scope, locals, assign, inputs) >=
334
- right(scope, locals, assign, inputs);
439
+ }
440
+
441
+ /**
442
+ * Binary greater-than-or-equal-to operation.
443
+ * @param {function} left - The left operand function.
444
+ * @param {function} right - The right operand function.
445
+ * @param {Object} [context] - The context.
446
+ * @returns {function} The binary greater-than-or-equal-to function.
447
+ */
448
+ "binary>="(left, right, context) {
449
+ return function (scope, locals, assign) {
450
+ const arg = left(scope, locals, assign) >= right(scope, locals, assign);
335
451
  return context ? { value: arg } : arg;
336
452
  };
337
- },
338
- "binary&&": function (left, right, context) {
339
- return function (scope, locals, assign, inputs) {
340
- const arg =
341
- left(scope, locals, assign, inputs) &&
342
- right(scope, locals, assign, inputs);
453
+ }
454
+ /**
455
+ * Binary logical AND operation.
456
+ * @param {function} left - The left operand function.
457
+ * @param {function} right - The right operand function.
458
+ * @param {Object} [context] - The context.
459
+ * @returns {function} The binary logical AND function.
460
+ */
461
+ "binary&&"(left, right, context) {
462
+ return function (scope, locals, assign) {
463
+ const arg = left(scope, locals, assign) && right(scope, locals, assign);
343
464
  return context ? { value: arg } : arg;
344
465
  };
345
- },
346
- "binary||": function (left, right, context) {
347
- return function (scope, locals, assign, inputs) {
348
- const arg =
349
- left(scope, locals, assign, inputs) ||
350
- right(scope, locals, assign, inputs);
466
+ }
467
+
468
+ /**
469
+ * Binary logical OR operation.
470
+ * @param {function} left - The left operand function.
471
+ * @param {function} right - The right operand function.
472
+ * @param {Object} [context] - The context.
473
+ * @returns {function} The binary logical OR function.
474
+ */
475
+ "binary||"(left, right, context) {
476
+ return function (scope, locals, assign) {
477
+ const arg = left(scope, locals, assign) || right(scope, locals, assign);
351
478
  return context ? { value: arg } : arg;
352
479
  };
353
- },
354
- "ternary?:": function (test, alternate, consequent, context) {
355
- return function (scope, locals, assign, inputs) {
356
- const arg = test(scope, locals, assign, inputs)
357
- ? alternate(scope, locals, assign, inputs)
358
- : consequent(scope, locals, assign, inputs);
480
+ }
481
+
482
+ /**
483
+ * Ternary conditional operation.
484
+ * @param {function} test - The test function.
485
+ * @param {function} alternate - The alternate function.
486
+ * @param {function} consequent - The consequent function.
487
+ * @param {Object} [context] - The context.
488
+ * @returns {function} The ternary conditional function.
489
+ */
490
+ "ternary?:"(test, alternate, consequent, context) {
491
+ return function (scope, locals, assign) {
492
+ const arg = test(scope, locals, assign)
493
+ ? alternate(scope, locals, assign)
494
+ : consequent(scope, locals, assign);
359
495
  return context ? { value: arg } : arg;
360
496
  };
361
- },
497
+ }
498
+
499
+ /**
500
+ * Returns the value of a literal.
501
+ * @param {*} value - The literal value.
502
+ * @param {Object} [context] - The context.
503
+ * @returns {function} The function returning the literal value.
504
+ */
362
505
  value(value, context) {
363
506
  return function () {
364
507
  return context ? { context: undefined, name: undefined, value } : value;
365
508
  };
366
- },
509
+ }
510
+
511
+ /**
512
+ * Returns the value of an identifier.
513
+ * @param {string} name - The identifier name.
514
+ * @param {Object} [context] - The context.
515
+ * @param {boolean|1} [create] - Whether to create the identifier if it does not exist.
516
+ * @returns {function} The function returning the identifier value.
517
+ */
367
518
  identifier(name, context, create) {
368
519
  return function (scope, locals) {
369
520
  const base = locals && name in locals ? locals : scope;
@@ -376,14 +527,23 @@ ASTInterpreter.prototype = {
376
527
  }
377
528
  return value;
378
529
  };
379
- },
530
+ }
531
+
532
+ /**
533
+ * Returns the value of a computed member expression.
534
+ * @param {function} left - The left operand function.
535
+ * @param {function} right - The right operand function.
536
+ * @param {Object} [context] - The context.
537
+ * @param {boolean|1} [create] - Whether to create the member if it does not exist.
538
+ * @returns {function} The function returning the computed member value.
539
+ */
380
540
  computedMember(left, right, context, create) {
381
- return function (scope, locals, assign, inputs) {
382
- const lhs = left(scope, locals, assign, inputs);
541
+ return function (scope, locals, assign) {
542
+ const lhs = left(scope, locals, assign);
383
543
  let rhs;
384
544
  let value;
385
545
  if (lhs != null) {
386
- rhs = right(scope, locals, assign, inputs);
546
+ rhs = right(scope, locals, assign);
387
547
  rhs = getStringValue(rhs);
388
548
  if (create && create !== 1) {
389
549
  if (lhs && !lhs[rhs]) {
@@ -397,10 +557,19 @@ ASTInterpreter.prototype = {
397
557
  }
398
558
  return value;
399
559
  };
400
- },
560
+ }
561
+
562
+ /**
563
+ * Returns the value of a non-computed member expression.
564
+ * @param {function} left - The left operand function.
565
+ * @param {string} right - The right operand function.
566
+ * @param {Object} [context] - The context.
567
+ * @param {boolean|1} [create] - Whether to create the member if it does not exist.
568
+ * @returns {function} The function returning the non-computed member value.
569
+ */
401
570
  nonComputedMember(left, right, context, create) {
402
- return function (scope, locals, assign, inputs) {
403
- const lhs = left(scope, locals, assign, inputs);
571
+ return function (scope, locals, assign) {
572
+ const lhs = left(scope, locals, assign);
404
573
  if (create && create !== 1) {
405
574
  if (lhs && lhs[right] == null) {
406
575
  lhs[right] = {};
@@ -412,11 +581,307 @@ ASTInterpreter.prototype = {
412
581
  }
413
582
  return value;
414
583
  };
415
- },
416
- inputs(input, watchId) {
417
- return function (scope, value, locals, inputs) {
418
- if (inputs) return inputs[watchId];
419
- return input(scope, value, locals);
584
+ }
585
+ }
586
+
587
+ /**
588
+ * @typedef {import("./ast").ASTNode & {
589
+ * isPure: boolean|number,
590
+ * constant: boolean,
591
+ * toWatch: Array,
592
+ * }} DecoratedASTNode
593
+ */
594
+
595
+ /**
596
+ * Decorates AST with constant, toWatch, and isPure properties
597
+ * @param {import("./ast").ASTNode} ast
598
+ * @param {function(any):any} $filter
599
+ * @param {boolean|1|2} [parentIsPure]
600
+ * @returns {DecoratedASTNode}
601
+ */
602
+ function findConstantAndWatchExpressions(ast, $filter, parentIsPure) {
603
+ let allConstants;
604
+ let argsToWatch;
605
+ let isStatelessFilter;
606
+ let decoratedNode = /** @type {DecoratedASTNode} */ (ast);
607
+ let decoratedLeft,
608
+ decoratedRight,
609
+ decoratedTest,
610
+ decoratedAlternate,
611
+ decoratedConsequent,
612
+ decoratedObject,
613
+ decoratedProperty,
614
+ decoratedKey;
615
+ const astIsPure = (decoratedNode.isPure = isPure(ast, parentIsPure));
616
+
617
+ switch (ast.type) {
618
+ case ASTType.Program:
619
+ allConstants = true;
620
+ decoratedNode.body.forEach((expr) => {
621
+ let decorated = findConstantAndWatchExpressions(
622
+ expr.expression,
623
+ $filter,
624
+ astIsPure,
625
+ );
626
+ allConstants = allConstants && decorated.constant;
627
+ });
628
+ decoratedNode.constant = allConstants;
629
+ return decoratedNode;
630
+ case ASTType.Literal:
631
+ decoratedNode.constant = true;
632
+ decoratedNode.toWatch = [];
633
+ return decoratedNode;
634
+ case ASTType.UnaryExpression:
635
+ var decorated = findConstantAndWatchExpressions(
636
+ decoratedNode.argument,
637
+ $filter,
638
+ astIsPure,
639
+ );
640
+ decoratedNode.constant = decorated.constant;
641
+ decoratedNode.toWatch = decorated.toWatch;
642
+ return decoratedNode;
643
+ case ASTType.BinaryExpression:
644
+ decoratedLeft = findConstantAndWatchExpressions(
645
+ decoratedNode.left,
646
+ $filter,
647
+ astIsPure,
648
+ );
649
+ decoratedRight = findConstantAndWatchExpressions(
650
+ decoratedNode.right,
651
+ $filter,
652
+ astIsPure,
653
+ );
654
+ decoratedNode.constant =
655
+ decoratedLeft.constant && decoratedRight.constant;
656
+ decoratedNode.toWatch = decoratedLeft.toWatch.concat(
657
+ decoratedRight.toWatch,
658
+ );
659
+ return decoratedNode;
660
+ case ASTType.LogicalExpression:
661
+ decoratedLeft = findConstantAndWatchExpressions(
662
+ decoratedNode.left,
663
+ $filter,
664
+ astIsPure,
665
+ );
666
+ decoratedRight = findConstantAndWatchExpressions(
667
+ decoratedNode.right,
668
+ $filter,
669
+ astIsPure,
670
+ );
671
+ decoratedNode.constant =
672
+ decoratedLeft.constant && decoratedRight.constant;
673
+ decoratedNode.toWatch = decoratedNode.constant ? [] : [ast];
674
+ return decoratedNode;
675
+ case ASTType.ConditionalExpression:
676
+ decoratedTest = findConstantAndWatchExpressions(
677
+ ast.test,
678
+ $filter,
679
+ astIsPure,
680
+ );
681
+ decoratedAlternate = findConstantAndWatchExpressions(
682
+ ast.alternate,
683
+ $filter,
684
+ astIsPure,
685
+ );
686
+ decoratedConsequent = findConstantAndWatchExpressions(
687
+ ast.consequent,
688
+ $filter,
689
+ astIsPure,
690
+ );
691
+ decoratedNode.constant =
692
+ decoratedTest.constant &&
693
+ decoratedAlternate.constant &&
694
+ decoratedConsequent.constant;
695
+ decoratedNode.toWatch = decoratedNode.constant ? [] : [ast];
696
+ return decoratedNode;
697
+ case ASTType.Identifier:
698
+ decoratedNode.constant = false;
699
+ decoratedNode.toWatch = [ast];
700
+ return decoratedNode;
701
+ case ASTType.MemberExpression:
702
+ decoratedObject = findConstantAndWatchExpressions(
703
+ ast.object,
704
+ $filter,
705
+ astIsPure,
706
+ );
707
+ if (ast.computed) {
708
+ decoratedProperty = findConstantAndWatchExpressions(
709
+ ast.property,
710
+ $filter,
711
+ astIsPure,
712
+ );
713
+ }
714
+ decoratedNode.constant =
715
+ decoratedObject.constant &&
716
+ (!decoratedNode.computed || decoratedProperty.constant);
717
+ decoratedNode.toWatch = decoratedNode.constant ? [] : [ast];
718
+ return decoratedNode;
719
+ case ASTType.CallExpression:
720
+ isStatelessFilter = ast.filter
721
+ ? isStateless($filter, ast.callee.name)
722
+ : false;
723
+ allConstants = isStatelessFilter;
724
+ argsToWatch = [];
725
+ ast.arguments.forEach((expr) => {
726
+ decorated = findConstantAndWatchExpressions(expr, $filter, astIsPure);
727
+ allConstants = allConstants && decorated.constant;
728
+ argsToWatch.push.apply(argsToWatch, decorated.toWatch);
729
+ });
730
+ decoratedNode.constant = allConstants;
731
+ decoratedNode.toWatch = isStatelessFilter ? argsToWatch : [decoratedNode];
732
+ return decoratedNode;
733
+ case ASTType.AssignmentExpression:
734
+ decoratedLeft = findConstantAndWatchExpressions(
735
+ ast.left,
736
+ $filter,
737
+ astIsPure,
738
+ );
739
+ decoratedRight = findConstantAndWatchExpressions(
740
+ ast.right,
741
+ $filter,
742
+ astIsPure,
743
+ );
744
+ decoratedNode.constant =
745
+ decoratedLeft.constant && decoratedRight.constant;
746
+ decoratedNode.toWatch = [decoratedNode];
747
+ return decoratedNode;
748
+ case ASTType.ArrayExpression:
749
+ allConstants = true;
750
+ argsToWatch = [];
751
+ ast.elements.forEach((expr) => {
752
+ decorated = findConstantAndWatchExpressions(expr, $filter, astIsPure);
753
+ allConstants = allConstants && decorated.constant;
754
+ argsToWatch.push.apply(argsToWatch, decorated.toWatch);
755
+ });
756
+ decoratedNode.constant = allConstants;
757
+ decoratedNode.toWatch = argsToWatch;
758
+ return decoratedNode;
759
+ case ASTType.ObjectExpression:
760
+ allConstants = true;
761
+ argsToWatch = [];
762
+ ast.properties.forEach((property) => {
763
+ decorated = findConstantAndWatchExpressions(
764
+ property.value,
765
+ $filter,
766
+ astIsPure,
767
+ );
768
+ allConstants = allConstants && decorated.constant;
769
+ argsToWatch.push.apply(argsToWatch, decorated.toWatch);
770
+ if (property.computed) {
771
+ // `{[key]: value}` implicitly does `key.toString()` which may be non-pure
772
+ decoratedKey = findConstantAndWatchExpressions(
773
+ property.key,
774
+ $filter,
775
+ false,
776
+ );
777
+ allConstants = allConstants && decoratedKey.constant;
778
+ argsToWatch.push.apply(argsToWatch, decoratedKey.toWatch);
779
+ }
780
+ });
781
+ decoratedNode.constant = allConstants;
782
+ decoratedNode.toWatch = argsToWatch;
783
+ return decoratedNode;
784
+ case ASTType.ThisExpression:
785
+ decoratedNode.constant = false;
786
+ decoratedNode.toWatch = [];
787
+ return decoratedNode;
788
+ case ASTType.LocalsExpression:
789
+ decoratedNode.constant = false;
790
+ decoratedNode.toWatch = [];
791
+ return decoratedNode;
792
+ }
793
+ }
794
+
795
+ /**
796
+ * Converts a single expression AST node into an assignment expression if the expression is assignable.
797
+ *
798
+ * @param {import("./ast").ASTNode} ast
799
+ * @returns {import("./ast").ASTNode}
800
+ */
801
+ function assignableAST(ast) {
802
+ if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) {
803
+ return {
804
+ type: ASTType.AssignmentExpression,
805
+ left: ast.body[0].expression,
806
+ right: { type: ASTType.NGValueParameter },
807
+ operator: "=",
420
808
  };
421
- },
422
- };
809
+ }
810
+ }
811
+
812
+ function plusFn(l, r) {
813
+ if (typeof l === "undefined") return r;
814
+ if (typeof r === "undefined") return l;
815
+ return l + r;
816
+ }
817
+
818
+ /**
819
+ *
820
+ * @param {import("./ast").ASTNode[]} body
821
+ * @returns {any}
822
+ */
823
+ function getInputs(body) {
824
+ if (body.length !== 1) return;
825
+ const lastExpression = /** @type {DecoratedASTNode} */ (body[0].expression);
826
+ const candidate = lastExpression.toWatch;
827
+ if (candidate.length !== 1) return candidate;
828
+ return candidate[0] !== lastExpression ? candidate : undefined;
829
+ }
830
+
831
+ /**
832
+ * Detect nodes which could depend on non-shallow state of objects
833
+ * @param {import("./ast").ASTNode} node
834
+ * @param {boolean|PURITY_ABSOLUTE|PURITY_RELATIVE} parentIsPure
835
+ * @returns {boolean|PURITY_ABSOLUTE|PURITY_RELATIVE}
836
+ */
837
+ function isPure(node, parentIsPure) {
838
+ switch (node.type) {
839
+ // Computed members might invoke a stateful toString()
840
+ case ASTType.MemberExpression:
841
+ if (node.computed) {
842
+ return false;
843
+ }
844
+ break;
845
+
846
+ // Unary always convert to primitive
847
+ case ASTType.UnaryExpression:
848
+ return PURITY_ABSOLUTE;
849
+
850
+ // The binary + operator can invoke a stateful toString().
851
+ case ASTType.BinaryExpression:
852
+ return node.operator !== "+" ? PURITY_ABSOLUTE : false;
853
+
854
+ // Functions / filters probably read state from within objects
855
+ case ASTType.CallExpression:
856
+ return false;
857
+ }
858
+
859
+ return undefined === parentIsPure ? PURITY_RELATIVE : parentIsPure;
860
+ }
861
+
862
+ function isStateless($filter, filterName) {
863
+ const fn = $filter(filterName);
864
+ return !fn.$stateful;
865
+ }
866
+
867
+ /**
868
+ * Converts parameter to strings property name for use as keys in an object.
869
+ * Any non-string object, including a number, is typecasted into a string via the toString method.
870
+ * {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Property_accessors#Property_names}
871
+ *
872
+ * @param {!any} name
873
+ * @returns {string}
874
+ */
875
+ function getStringValue(name) {
876
+ return `${name}`;
877
+ }
878
+
879
+ /**
880
+ * @param {import("./ast").ASTNode} ast
881
+ * @returns {boolean}
882
+ */
883
+ export function isAssignable(ast) {
884
+ return (
885
+ ast.type === ASTType.Identifier || ast.type === ASTType.MemberExpression
886
+ );
887
+ }