@angular-wave/angular.ts 0.0.1
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/.eslintignore +1 -0
- package/.eslintrc.cjs +29 -0
- package/.github/workflows/playwright.yml +27 -0
- package/CHANGELOG.md +17974 -0
- package/CODE_OF_CONDUCT.md +3 -0
- package/CONTRIBUTING.md +246 -0
- package/DEVELOPERS.md +488 -0
- package/LICENSE +22 -0
- package/Makefile +31 -0
- package/README.md +115 -0
- package/RELEASE.md +98 -0
- package/SECURITY.md +16 -0
- package/TRIAGING.md +135 -0
- package/css/angular.css +22 -0
- package/dist/angular-ts.cjs.js +36843 -0
- package/dist/angular-ts.esm.js +36841 -0
- package/dist/angular-ts.umd.js +36848 -0
- package/dist/build/angular-animate.js +4272 -0
- package/dist/build/angular-aria.js +426 -0
- package/dist/build/angular-message-format.js +1072 -0
- package/dist/build/angular-messages.js +829 -0
- package/dist/build/angular-mocks.js +3757 -0
- package/dist/build/angular-parse-ext.js +1275 -0
- package/dist/build/angular-resource.js +911 -0
- package/dist/build/angular-route.js +1266 -0
- package/dist/build/angular-sanitize.js +891 -0
- package/dist/build/angular-touch.js +368 -0
- package/dist/build/angular.js +36600 -0
- package/e2e/unit.spec.ts +15 -0
- package/images/android-chrome-192x192.png +0 -0
- package/images/android-chrome-512x512.png +0 -0
- package/images/apple-touch-icon.png +0 -0
- package/images/favicon-16x16.png +0 -0
- package/images/favicon-32x32.png +0 -0
- package/images/favicon.ico +0 -0
- package/images/site.webmanifest +1 -0
- package/index.html +104 -0
- package/package.json +47 -0
- package/playwright.config.ts +78 -0
- package/public/circle.html +1 -0
- package/public/my_child_directive.html +1 -0
- package/public/my_directive.html +1 -0
- package/public/my_other_directive.html +1 -0
- package/public/test.html +1 -0
- package/rollup.config.js +31 -0
- package/src/animations/animateCache.js +55 -0
- package/src/animations/animateChildrenDirective.js +105 -0
- package/src/animations/animateCss.js +1139 -0
- package/src/animations/animateCssDriver.js +291 -0
- package/src/animations/animateJs.js +367 -0
- package/src/animations/animateJsDriver.js +67 -0
- package/src/animations/animateQueue.js +851 -0
- package/src/animations/animation.js +506 -0
- package/src/animations/module.js +779 -0
- package/src/animations/ngAnimateSwap.js +119 -0
- package/src/animations/rafScheduler.js +50 -0
- package/src/animations/shared.js +378 -0
- package/src/constants.js +20 -0
- package/src/core/animate.js +845 -0
- package/src/core/animateCss.js +73 -0
- package/src/core/animateRunner.js +195 -0
- package/src/core/attributes.js +199 -0
- package/src/core/cache.js +45 -0
- package/src/core/compile.js +4727 -0
- package/src/core/controller.js +225 -0
- package/src/core/exceptionHandler.js +63 -0
- package/src/core/filter.js +146 -0
- package/src/core/interpolate.js +442 -0
- package/src/core/interval.js +188 -0
- package/src/core/intervalFactory.js +57 -0
- package/src/core/location.js +1086 -0
- package/src/core/parser/parse.js +2562 -0
- package/src/core/parser/parse.md +13 -0
- package/src/core/q.js +746 -0
- package/src/core/rootScope.js +1596 -0
- package/src/core/sanitizeUri.js +85 -0
- package/src/core/sce.js +1161 -0
- package/src/core/taskTrackerFactory.js +125 -0
- package/src/core/timeout.js +121 -0
- package/src/core/urlUtils.js +187 -0
- package/src/core/utils.js +1349 -0
- package/src/directive/a.js +37 -0
- package/src/directive/attrs.js +283 -0
- package/src/directive/bind.js +51 -0
- package/src/directive/bind.md +142 -0
- package/src/directive/change.js +12 -0
- package/src/directive/change.md +25 -0
- package/src/directive/cloak.js +12 -0
- package/src/directive/cloak.md +24 -0
- package/src/directive/events.js +75 -0
- package/src/directive/events.md +166 -0
- package/src/directive/form.js +725 -0
- package/src/directive/init.js +15 -0
- package/src/directive/init.md +41 -0
- package/src/directive/input.js +1783 -0
- package/src/directive/list.js +46 -0
- package/src/directive/list.md +22 -0
- package/src/directive/ngClass.js +249 -0
- package/src/directive/ngController.js +64 -0
- package/src/directive/ngCsp.js +82 -0
- package/src/directive/ngIf.js +134 -0
- package/src/directive/ngInclude.js +217 -0
- package/src/directive/ngModel.js +1356 -0
- package/src/directive/ngModelOptions.js +509 -0
- package/src/directive/ngOptions.js +670 -0
- package/src/directive/ngRef.js +90 -0
- package/src/directive/ngRepeat.js +650 -0
- package/src/directive/ngShowHide.js +255 -0
- package/src/directive/ngSwitch.js +178 -0
- package/src/directive/ngTransclude.js +98 -0
- package/src/directive/non-bindable.js +11 -0
- package/src/directive/non-bindable.md +17 -0
- package/src/directive/script.js +30 -0
- package/src/directive/select.js +624 -0
- package/src/directive/style.js +25 -0
- package/src/directive/style.md +23 -0
- package/src/directive/validators.js +329 -0
- package/src/exts/aria.js +544 -0
- package/src/exts/messages.js +852 -0
- package/src/filters/filter.js +207 -0
- package/src/filters/filter.md +69 -0
- package/src/filters/filters.js +239 -0
- package/src/filters/json.md +16 -0
- package/src/filters/limit-to.js +43 -0
- package/src/filters/limit-to.md +19 -0
- package/src/filters/order-by.js +183 -0
- package/src/filters/order-by.md +83 -0
- package/src/index.js +13 -0
- package/src/injector.js +1034 -0
- package/src/jqLite.js +1117 -0
- package/src/loader.js +1320 -0
- package/src/public.js +215 -0
- package/src/routeToRegExp.js +41 -0
- package/src/services/anchorScroll.js +135 -0
- package/src/services/browser.js +321 -0
- package/src/services/cacheFactory.js +398 -0
- package/src/services/cookieReader.js +72 -0
- package/src/services/document.js +64 -0
- package/src/services/http.js +1537 -0
- package/src/services/httpBackend.js +206 -0
- package/src/services/log.js +160 -0
- package/src/services/templateRequest.js +139 -0
- package/test/angular.spec.js +2153 -0
- package/test/aria/aria.spec.js +1245 -0
- package/test/binding.spec.js +504 -0
- package/test/build-test.html +14 -0
- package/test/injector.spec.js +2327 -0
- package/test/jasmine/jasmine-5.1.2/boot0.js +65 -0
- package/test/jasmine/jasmine-5.1.2/boot1.js +133 -0
- package/test/jasmine/jasmine-5.1.2/jasmine-html.js +963 -0
- package/test/jasmine/jasmine-5.1.2/jasmine.css +320 -0
- package/test/jasmine/jasmine-5.1.2/jasmine.js +10824 -0
- package/test/jasmine/jasmine-5.1.2/jasmine_favicon.png +0 -0
- package/test/jasmine/jasmine-browser.json +17 -0
- package/test/jasmine/jasmine.json +9 -0
- package/test/jqlite.spec.js +2133 -0
- package/test/loader.spec.js +219 -0
- package/test/messages/messages.spec.js +1146 -0
- package/test/min-err.spec.js +174 -0
- package/test/mock-test.html +13 -0
- package/test/module-test.html +15 -0
- package/test/ng/anomate.spec.js +606 -0
- package/test/ng/cache-factor.spec.js +334 -0
- package/test/ng/compile.spec.js +17956 -0
- package/test/ng/controller-provider.spec.js +227 -0
- package/test/ng/cookie-reader.spec.js +98 -0
- package/test/ng/directive/a.spec.js +192 -0
- package/test/ng/directive/bind.spec.js +334 -0
- package/test/ng/directive/boolean.spec.js +136 -0
- package/test/ng/directive/change.spec.js +71 -0
- package/test/ng/directive/class.spec.js +858 -0
- package/test/ng/directive/click.spec.js +38 -0
- package/test/ng/directive/cloak.spec.js +44 -0
- package/test/ng/directive/constoller.spec.js +194 -0
- package/test/ng/directive/element-style.spec.js +92 -0
- package/test/ng/directive/event.spec.js +282 -0
- package/test/ng/directive/form.spec.js +1518 -0
- package/test/ng/directive/href.spec.js +143 -0
- package/test/ng/directive/if.spec.js +402 -0
- package/test/ng/directive/include.spec.js +828 -0
- package/test/ng/directive/init.spec.js +68 -0
- package/test/ng/directive/input.spec.js +3810 -0
- package/test/ng/directive/list.spec.js +170 -0
- package/test/ng/directive/model-options.spec.js +1008 -0
- package/test/ng/directive/model.spec.js +1905 -0
- package/test/ng/directive/non-bindable.spec.js +55 -0
- package/test/ng/directive/options.spec.js +3583 -0
- package/test/ng/directive/ref.spec.js +575 -0
- package/test/ng/directive/repeat.spec.js +1675 -0
- package/test/ng/directive/script.spec.js +52 -0
- package/test/ng/directive/scrset.spec.js +67 -0
- package/test/ng/directive/select.spec.js +2541 -0
- package/test/ng/directive/show-hide.spec.js +253 -0
- package/test/ng/directive/src.spec.js +157 -0
- package/test/ng/directive/style.spec.js +178 -0
- package/test/ng/directive/switch.spec.js +647 -0
- package/test/ng/directive/validators.spec.js +717 -0
- package/test/ng/document.spec.js +52 -0
- package/test/ng/filter/filter.spec.js +714 -0
- package/test/ng/filter/filters.spec.js +35 -0
- package/test/ng/filter/limit-to.spec.js +251 -0
- package/test/ng/filter/order-by.spec.js +891 -0
- package/test/ng/filter.spec.js +149 -0
- package/test/ng/http-backend.spec.js +398 -0
- package/test/ng/http.spec.js +4071 -0
- package/test/ng/interpolate.spec.js +642 -0
- package/test/ng/interval.spec.js +343 -0
- package/test/ng/location.spec.js +3488 -0
- package/test/ng/on.spec.js +229 -0
- package/test/ng/parse.spec.js +4655 -0
- package/test/ng/prop.spec.js +805 -0
- package/test/ng/q.spec.js +2904 -0
- package/test/ng/root-element.spec.js +16 -0
- package/test/ng/sanitize-uri.spec.js +249 -0
- package/test/ng/sce.spec.js +660 -0
- package/test/ng/scope.spec.js +3442 -0
- package/test/ng/template-request.spec.js +236 -0
- package/test/ng/timeout.spec.js +351 -0
- package/test/ng/url-utils.spec.js +156 -0
- package/test/ng/utils.spec.js +144 -0
- package/test/original-test.html +21 -0
- package/test/public.spec.js +34 -0
- package/test/sanitize/bing-html.spec.js +36 -0
- package/test/server/express.js +158 -0
- package/test/test-utils.js +11 -0
- package/tsconfig.json +17 -0
- package/types/angular.d.ts +138 -0
- package/types/global.d.ts +9 -0
- package/types/index.d.ts +2357 -0
- package/types/jqlite.d.ts +558 -0
- package/vite.config.js +14 -0
|
@@ -0,0 +1,2562 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createMap,
|
|
3
|
+
csp,
|
|
4
|
+
forEach,
|
|
5
|
+
identity,
|
|
6
|
+
isDefined,
|
|
7
|
+
isFunction,
|
|
8
|
+
minErr,
|
|
9
|
+
isString,
|
|
10
|
+
lowercase,
|
|
11
|
+
isNumber,
|
|
12
|
+
} from "../utils";
|
|
13
|
+
|
|
14
|
+
const $parseMinErr = minErr("$parse");
|
|
15
|
+
|
|
16
|
+
const objectValueOf = {}.constructor.prototype.valueOf;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Converts parameter to strings property name for use as keys in an object.
|
|
20
|
+
* Any non-string object, including a number, is typecasted into a string via the toString method.
|
|
21
|
+
* {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Property_accessors#Property_names}
|
|
22
|
+
*
|
|
23
|
+
* @param {!any} name
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
function getStringValue(name) {
|
|
27
|
+
return `${name}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const OPERATORS = createMap();
|
|
31
|
+
|
|
32
|
+
"+ - * / % === !== == != < > <= >= && || ! = |"
|
|
33
|
+
.split(" ")
|
|
34
|
+
.forEach((operator) => (OPERATORS[operator] = true));
|
|
35
|
+
|
|
36
|
+
const ESCAPE = {
|
|
37
|
+
n: "\n",
|
|
38
|
+
f: "\f",
|
|
39
|
+
r: "\r",
|
|
40
|
+
t: "\t",
|
|
41
|
+
v: "\v",
|
|
42
|
+
"'": "'",
|
|
43
|
+
'"': '"',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/// //////////////////////////////////////
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @constructor
|
|
50
|
+
*/
|
|
51
|
+
export const Lexer = function Lexer(options) {
|
|
52
|
+
this.options = options;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
Lexer.prototype = {
|
|
56
|
+
constructor: Lexer,
|
|
57
|
+
|
|
58
|
+
lex(text) {
|
|
59
|
+
this.text = text;
|
|
60
|
+
this.index = 0;
|
|
61
|
+
this.tokens = [];
|
|
62
|
+
|
|
63
|
+
while (this.index < this.text.length) {
|
|
64
|
+
const ch = this.text.charAt(this.index);
|
|
65
|
+
if (ch === '"' || ch === "'") {
|
|
66
|
+
this.readString(ch);
|
|
67
|
+
} else if (
|
|
68
|
+
this.isNumber(ch) ||
|
|
69
|
+
(ch === "." && this.isNumber(this.peek()))
|
|
70
|
+
) {
|
|
71
|
+
this.readNumber();
|
|
72
|
+
} else if (this.isIdentifierStart(this.peekMultichar())) {
|
|
73
|
+
this.readIdent();
|
|
74
|
+
} else if (this.is(ch, "(){}[].,;:?")) {
|
|
75
|
+
this.tokens.push({ index: this.index, text: ch });
|
|
76
|
+
this.index++;
|
|
77
|
+
} else if (this.isWhitespace(ch)) {
|
|
78
|
+
this.index++;
|
|
79
|
+
} else {
|
|
80
|
+
const ch2 = ch + this.peek();
|
|
81
|
+
const ch3 = ch2 + this.peek(2);
|
|
82
|
+
const op1 = OPERATORS[ch];
|
|
83
|
+
const op2 = OPERATORS[ch2];
|
|
84
|
+
const op3 = OPERATORS[ch3];
|
|
85
|
+
if (op1 || op2 || op3) {
|
|
86
|
+
const token = op3 ? ch3 : op2 ? ch2 : ch;
|
|
87
|
+
this.tokens.push({ index: this.index, text: token, operator: true });
|
|
88
|
+
this.index += token.length;
|
|
89
|
+
} else {
|
|
90
|
+
this.throwError(
|
|
91
|
+
"Unexpected next character ",
|
|
92
|
+
this.index,
|
|
93
|
+
this.index + 1,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return this.tokens;
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
is(ch, chars) {
|
|
102
|
+
return chars.indexOf(ch) !== -1;
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
peek(i) {
|
|
106
|
+
const num = i || 1;
|
|
107
|
+
return this.index + num < this.text.length
|
|
108
|
+
? this.text.charAt(this.index + num)
|
|
109
|
+
: false;
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
isNumber(ch) {
|
|
113
|
+
return ch >= "0" && ch <= "9" && typeof ch === "string";
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
isWhitespace(ch) {
|
|
117
|
+
// IE treats non-breaking space as \u00A0
|
|
118
|
+
return (
|
|
119
|
+
ch === " " ||
|
|
120
|
+
ch === "\r" ||
|
|
121
|
+
ch === "\t" ||
|
|
122
|
+
ch === "\n" ||
|
|
123
|
+
ch === "\v" ||
|
|
124
|
+
ch === "\u00A0"
|
|
125
|
+
);
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
isIdentifierStart(ch) {
|
|
129
|
+
return this.options.isIdentifierStart
|
|
130
|
+
? this.options.isIdentifierStart(ch, this.codePointAt(ch))
|
|
131
|
+
: this.isValidIdentifierStart(ch);
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
isValidIdentifierStart(ch) {
|
|
135
|
+
return (
|
|
136
|
+
(ch >= "a" && ch <= "z") ||
|
|
137
|
+
(ch >= "A" && ch <= "Z") ||
|
|
138
|
+
ch === "_" ||
|
|
139
|
+
ch === "$"
|
|
140
|
+
);
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
isIdentifierContinue(ch) {
|
|
144
|
+
return this.options.isIdentifierContinue
|
|
145
|
+
? this.options.isIdentifierContinue(ch, this.codePointAt(ch))
|
|
146
|
+
: this.isValidIdentifierContinue(ch);
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
isValidIdentifierContinue(ch) {
|
|
150
|
+
return this.isValidIdentifierStart(ch) || this.isNumber(ch);
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
codePointAt(ch) {
|
|
154
|
+
if (ch.length === 1) return ch.charCodeAt(0);
|
|
155
|
+
// eslint-disable-next-line no-bitwise
|
|
156
|
+
return (ch.charCodeAt(0) << 10) + ch.charCodeAt(1) - 0x35fdc00;
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
peekMultichar() {
|
|
160
|
+
const ch = this.text.charAt(this.index);
|
|
161
|
+
const peek = this.peek();
|
|
162
|
+
if (!peek) {
|
|
163
|
+
return ch;
|
|
164
|
+
}
|
|
165
|
+
const cp1 = ch.charCodeAt(0);
|
|
166
|
+
const cp2 = peek.charCodeAt(0);
|
|
167
|
+
if (cp1 >= 0xd800 && cp1 <= 0xdbff && cp2 >= 0xdc00 && cp2 <= 0xdfff) {
|
|
168
|
+
return ch + peek;
|
|
169
|
+
}
|
|
170
|
+
return ch;
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
isExpOperator(ch) {
|
|
174
|
+
return ch === "-" || ch === "+" || this.isNumber(ch);
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
throwError(error, start, end) {
|
|
178
|
+
end = end || this.index;
|
|
179
|
+
const colStr = isDefined(start)
|
|
180
|
+
? `s ${start}-${this.index} [${this.text.substring(start, end)}]`
|
|
181
|
+
: ` ${end}`;
|
|
182
|
+
throw $parseMinErr(
|
|
183
|
+
"lexerr",
|
|
184
|
+
"Lexer Error: {0} at column{1} in expression [{2}].",
|
|
185
|
+
error,
|
|
186
|
+
colStr,
|
|
187
|
+
this.text,
|
|
188
|
+
);
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
readNumber() {
|
|
192
|
+
let number = "";
|
|
193
|
+
const start = this.index;
|
|
194
|
+
while (this.index < this.text.length) {
|
|
195
|
+
const ch = lowercase(this.text.charAt(this.index));
|
|
196
|
+
if (ch === "." || this.isNumber(ch)) {
|
|
197
|
+
number += ch;
|
|
198
|
+
} else {
|
|
199
|
+
const peekCh = this.peek();
|
|
200
|
+
if (ch === "e" && this.isExpOperator(peekCh)) {
|
|
201
|
+
number += ch;
|
|
202
|
+
} else if (
|
|
203
|
+
this.isExpOperator(ch) &&
|
|
204
|
+
peekCh &&
|
|
205
|
+
this.isNumber(peekCh) &&
|
|
206
|
+
number.charAt(number.length - 1) === "e"
|
|
207
|
+
) {
|
|
208
|
+
number += ch;
|
|
209
|
+
} else if (
|
|
210
|
+
this.isExpOperator(ch) &&
|
|
211
|
+
(!peekCh || !this.isNumber(peekCh)) &&
|
|
212
|
+
number.charAt(number.length - 1) === "e"
|
|
213
|
+
) {
|
|
214
|
+
this.throwError("Invalid exponent");
|
|
215
|
+
} else {
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
this.index++;
|
|
220
|
+
}
|
|
221
|
+
this.tokens.push({
|
|
222
|
+
index: start,
|
|
223
|
+
text: number,
|
|
224
|
+
constant: true,
|
|
225
|
+
value: Number(number),
|
|
226
|
+
});
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
readIdent() {
|
|
230
|
+
const start = this.index;
|
|
231
|
+
this.index += this.peekMultichar().length;
|
|
232
|
+
while (this.index < this.text.length) {
|
|
233
|
+
const ch = this.peekMultichar();
|
|
234
|
+
if (!this.isIdentifierContinue(ch)) {
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
this.index += ch.length;
|
|
238
|
+
}
|
|
239
|
+
this.tokens.push({
|
|
240
|
+
index: start,
|
|
241
|
+
text: this.text.slice(start, this.index),
|
|
242
|
+
identifier: true,
|
|
243
|
+
});
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
readString(quote) {
|
|
247
|
+
const start = this.index;
|
|
248
|
+
this.index++;
|
|
249
|
+
let string = "";
|
|
250
|
+
let rawString = quote;
|
|
251
|
+
let escape = false;
|
|
252
|
+
while (this.index < this.text.length) {
|
|
253
|
+
const ch = this.text.charAt(this.index);
|
|
254
|
+
rawString += ch;
|
|
255
|
+
if (escape) {
|
|
256
|
+
if (ch === "u") {
|
|
257
|
+
const hex = this.text.substring(this.index + 1, this.index + 5);
|
|
258
|
+
if (!hex.match(/[\da-f]{4}/i)) {
|
|
259
|
+
this.throwError(`Invalid unicode escape [\\u${hex}]`);
|
|
260
|
+
}
|
|
261
|
+
this.index += 4;
|
|
262
|
+
string += String.fromCharCode(parseInt(hex, 16));
|
|
263
|
+
} else {
|
|
264
|
+
const rep = ESCAPE[ch];
|
|
265
|
+
string += rep || ch;
|
|
266
|
+
}
|
|
267
|
+
escape = false;
|
|
268
|
+
} else if (ch === "\\") {
|
|
269
|
+
escape = true;
|
|
270
|
+
} else if (ch === quote) {
|
|
271
|
+
this.index++;
|
|
272
|
+
this.tokens.push({
|
|
273
|
+
index: start,
|
|
274
|
+
text: rawString,
|
|
275
|
+
constant: true,
|
|
276
|
+
value: string,
|
|
277
|
+
});
|
|
278
|
+
return;
|
|
279
|
+
} else {
|
|
280
|
+
string += ch;
|
|
281
|
+
}
|
|
282
|
+
this.index++;
|
|
283
|
+
}
|
|
284
|
+
this.throwError("Unterminated quote", start);
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* @typedef {("Program"|"ExpressionStatement"|"AssignmentExpression"|"ConditionalExpression"|"LogicalExpression"|"BinaryExpression"|"UnaryExpression"|"CallExpression"|"MemberExpression"|"Identifier"|"Literal"|"ArrayExpression"|"Property"|"ObjectExpression"|"ThisExpression"|"LocalsExpression"|"NGValueParameter")} ASTType
|
|
290
|
+
*/
|
|
291
|
+
const ASTType = {
|
|
292
|
+
Program: "Program",
|
|
293
|
+
ExpressionStatement: "ExpressionStatement",
|
|
294
|
+
AssignmentExpression: "AssignmentExpression",
|
|
295
|
+
ConditionalExpression: "ConditionalExpression",
|
|
296
|
+
LogicalExpression: "LogicalExpression",
|
|
297
|
+
BinaryExpression: "BinaryExpression",
|
|
298
|
+
UnaryExpression: "UnaryExpression",
|
|
299
|
+
CallExpression: "CallExpression",
|
|
300
|
+
MemberExpression: "MemberExpression",
|
|
301
|
+
Identifier: "Identifier",
|
|
302
|
+
Literal: "Literal",
|
|
303
|
+
ArrayExpression: "ArrayExpression",
|
|
304
|
+
Property: "Property",
|
|
305
|
+
ObjectExpression: "ObjectExpression",
|
|
306
|
+
ThisExpression: "ThisExpression",
|
|
307
|
+
LocalsExpression: "LocalsExpression",
|
|
308
|
+
NGValueParameter: "NGValueParameter",
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
export function AST(lexer, options) {
|
|
312
|
+
this.lexer = lexer;
|
|
313
|
+
this.options = options;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
AST.prototype = {
|
|
317
|
+
ast(text) {
|
|
318
|
+
this.text = text;
|
|
319
|
+
this.tokens = this.lexer.lex(text);
|
|
320
|
+
|
|
321
|
+
const value = this.program();
|
|
322
|
+
|
|
323
|
+
if (this.tokens.length !== 0) {
|
|
324
|
+
this.throwError("is an unexpected token", this.tokens[0]);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return value;
|
|
328
|
+
},
|
|
329
|
+
|
|
330
|
+
program() {
|
|
331
|
+
const body = [];
|
|
332
|
+
let hasMore = true;
|
|
333
|
+
while (hasMore) {
|
|
334
|
+
if (this.tokens.length > 0 && !this.peek("}", ")", ";", "]"))
|
|
335
|
+
body.push(this.expressionStatement());
|
|
336
|
+
if (!this.expect(";")) {
|
|
337
|
+
hasMore = false;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return { type: ASTType.Program, body };
|
|
341
|
+
},
|
|
342
|
+
|
|
343
|
+
expressionStatement() {
|
|
344
|
+
return {
|
|
345
|
+
type: ASTType.ExpressionStatement,
|
|
346
|
+
expression: this.filterChain(),
|
|
347
|
+
};
|
|
348
|
+
},
|
|
349
|
+
|
|
350
|
+
filterChain() {
|
|
351
|
+
let left = this.expression();
|
|
352
|
+
while (this.expect("|")) {
|
|
353
|
+
left = this.filter(left);
|
|
354
|
+
}
|
|
355
|
+
return left;
|
|
356
|
+
},
|
|
357
|
+
|
|
358
|
+
expression() {
|
|
359
|
+
return this.assignment();
|
|
360
|
+
},
|
|
361
|
+
|
|
362
|
+
assignment() {
|
|
363
|
+
let result = this.ternary();
|
|
364
|
+
if (this.expect("=")) {
|
|
365
|
+
if (!isAssignable(result)) {
|
|
366
|
+
throw $parseMinErr("lval", "Trying to assign a value to a non l-value");
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
result = {
|
|
370
|
+
type: ASTType.AssignmentExpression,
|
|
371
|
+
left: result,
|
|
372
|
+
right: this.assignment(),
|
|
373
|
+
operator: "=",
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
return result;
|
|
377
|
+
},
|
|
378
|
+
|
|
379
|
+
ternary() {
|
|
380
|
+
const test = this.logicalOR();
|
|
381
|
+
let alternate;
|
|
382
|
+
let consequent;
|
|
383
|
+
if (this.expect("?")) {
|
|
384
|
+
alternate = this.expression();
|
|
385
|
+
if (this.consume(":")) {
|
|
386
|
+
consequent = this.expression();
|
|
387
|
+
return {
|
|
388
|
+
type: ASTType.ConditionalExpression,
|
|
389
|
+
test,
|
|
390
|
+
alternate,
|
|
391
|
+
consequent,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return test;
|
|
396
|
+
},
|
|
397
|
+
|
|
398
|
+
logicalOR() {
|
|
399
|
+
let left = this.logicalAND();
|
|
400
|
+
while (this.expect("||")) {
|
|
401
|
+
left = {
|
|
402
|
+
type: ASTType.LogicalExpression,
|
|
403
|
+
operator: "||",
|
|
404
|
+
left,
|
|
405
|
+
right: this.logicalAND(),
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
return left;
|
|
409
|
+
},
|
|
410
|
+
|
|
411
|
+
logicalAND() {
|
|
412
|
+
let left = this.equality();
|
|
413
|
+
while (this.expect("&&")) {
|
|
414
|
+
left = {
|
|
415
|
+
type: ASTType.LogicalExpression,
|
|
416
|
+
operator: "&&",
|
|
417
|
+
left,
|
|
418
|
+
right: this.equality(),
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
return left;
|
|
422
|
+
},
|
|
423
|
+
|
|
424
|
+
equality() {
|
|
425
|
+
let left = this.relational();
|
|
426
|
+
let token;
|
|
427
|
+
while ((token = this.expect("==", "!=", "===", "!=="))) {
|
|
428
|
+
left = {
|
|
429
|
+
type: ASTType.BinaryExpression,
|
|
430
|
+
operator: token.text,
|
|
431
|
+
left,
|
|
432
|
+
right: this.relational(),
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
return left;
|
|
436
|
+
},
|
|
437
|
+
|
|
438
|
+
relational() {
|
|
439
|
+
let left = this.additive();
|
|
440
|
+
let token;
|
|
441
|
+
while ((token = this.expect("<", ">", "<=", ">="))) {
|
|
442
|
+
left = {
|
|
443
|
+
type: ASTType.BinaryExpression,
|
|
444
|
+
operator: token.text,
|
|
445
|
+
left,
|
|
446
|
+
right: this.additive(),
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
return left;
|
|
450
|
+
},
|
|
451
|
+
|
|
452
|
+
additive() {
|
|
453
|
+
let left = this.multiplicative();
|
|
454
|
+
let token;
|
|
455
|
+
while ((token = this.expect("+", "-"))) {
|
|
456
|
+
left = {
|
|
457
|
+
type: ASTType.BinaryExpression,
|
|
458
|
+
operator: token.text,
|
|
459
|
+
left,
|
|
460
|
+
right: this.multiplicative(),
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
return left;
|
|
464
|
+
},
|
|
465
|
+
|
|
466
|
+
multiplicative() {
|
|
467
|
+
let left = this.unary();
|
|
468
|
+
let token;
|
|
469
|
+
while ((token = this.expect("*", "/", "%"))) {
|
|
470
|
+
left = {
|
|
471
|
+
type: ASTType.BinaryExpression,
|
|
472
|
+
operator: token.text,
|
|
473
|
+
left,
|
|
474
|
+
right: this.unary(),
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
return left;
|
|
478
|
+
},
|
|
479
|
+
|
|
480
|
+
unary() {
|
|
481
|
+
let token;
|
|
482
|
+
if ((token = this.expect("+", "-", "!"))) {
|
|
483
|
+
return {
|
|
484
|
+
type: ASTType.UnaryExpression,
|
|
485
|
+
operator: token.text,
|
|
486
|
+
prefix: true,
|
|
487
|
+
argument: this.unary(),
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
return this.primary();
|
|
491
|
+
},
|
|
492
|
+
|
|
493
|
+
primary() {
|
|
494
|
+
let primary;
|
|
495
|
+
if (this.expect("(")) {
|
|
496
|
+
primary = this.filterChain();
|
|
497
|
+
this.consume(")");
|
|
498
|
+
} else if (this.expect("[")) {
|
|
499
|
+
primary = this.arrayDeclaration();
|
|
500
|
+
} else if (this.expect("{")) {
|
|
501
|
+
primary = this.object();
|
|
502
|
+
} else if (
|
|
503
|
+
Object.prototype.hasOwnProperty.call(
|
|
504
|
+
this.selfReferential,
|
|
505
|
+
this.peek().text,
|
|
506
|
+
)
|
|
507
|
+
) {
|
|
508
|
+
primary = structuredClone(this.selfReferential[this.consume().text]);
|
|
509
|
+
} else if (
|
|
510
|
+
Object.prototype.hasOwnProperty.call(
|
|
511
|
+
this.options.literals,
|
|
512
|
+
this.peek().text,
|
|
513
|
+
)
|
|
514
|
+
) {
|
|
515
|
+
primary = {
|
|
516
|
+
type: ASTType.Literal,
|
|
517
|
+
value: this.options.literals[this.consume().text],
|
|
518
|
+
};
|
|
519
|
+
} else if (this.peek().identifier) {
|
|
520
|
+
primary = this.identifier();
|
|
521
|
+
} else if (this.peek().constant) {
|
|
522
|
+
primary = this.constant();
|
|
523
|
+
} else {
|
|
524
|
+
this.throwError("not a primary expression", this.peek());
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
let next;
|
|
528
|
+
while ((next = this.expect("(", "[", "."))) {
|
|
529
|
+
if (next.text === "(") {
|
|
530
|
+
primary = {
|
|
531
|
+
type: ASTType.CallExpression,
|
|
532
|
+
callee: primary,
|
|
533
|
+
arguments: this.parseArguments(),
|
|
534
|
+
};
|
|
535
|
+
this.consume(")");
|
|
536
|
+
} else if (next.text === "[") {
|
|
537
|
+
primary = {
|
|
538
|
+
type: ASTType.MemberExpression,
|
|
539
|
+
object: primary,
|
|
540
|
+
property: this.expression(),
|
|
541
|
+
computed: true,
|
|
542
|
+
};
|
|
543
|
+
this.consume("]");
|
|
544
|
+
} else if (next.text === ".") {
|
|
545
|
+
primary = {
|
|
546
|
+
type: ASTType.MemberExpression,
|
|
547
|
+
object: primary,
|
|
548
|
+
property: this.identifier(),
|
|
549
|
+
computed: false,
|
|
550
|
+
};
|
|
551
|
+
} else {
|
|
552
|
+
this.throwError("IMPOSSIBLE");
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return primary;
|
|
556
|
+
},
|
|
557
|
+
|
|
558
|
+
filter(baseExpression) {
|
|
559
|
+
const args = [baseExpression];
|
|
560
|
+
const result = {
|
|
561
|
+
type: ASTType.CallExpression,
|
|
562
|
+
callee: this.identifier(),
|
|
563
|
+
arguments: args,
|
|
564
|
+
filter: true,
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
while (this.expect(":")) {
|
|
568
|
+
args.push(this.expression());
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
return result;
|
|
572
|
+
},
|
|
573
|
+
|
|
574
|
+
parseArguments() {
|
|
575
|
+
const args = [];
|
|
576
|
+
if (this.peekToken().text !== ")") {
|
|
577
|
+
do {
|
|
578
|
+
args.push(this.filterChain());
|
|
579
|
+
} while (this.expect(","));
|
|
580
|
+
}
|
|
581
|
+
return args;
|
|
582
|
+
},
|
|
583
|
+
|
|
584
|
+
identifier() {
|
|
585
|
+
const token = this.consume();
|
|
586
|
+
if (!token.identifier) {
|
|
587
|
+
this.throwError("is not a valid identifier", token);
|
|
588
|
+
}
|
|
589
|
+
return { type: ASTType.Identifier, name: token.text };
|
|
590
|
+
},
|
|
591
|
+
|
|
592
|
+
constant() {
|
|
593
|
+
// TODO check that it is a constant
|
|
594
|
+
return { type: ASTType.Literal, value: this.consume().value };
|
|
595
|
+
},
|
|
596
|
+
|
|
597
|
+
arrayDeclaration() {
|
|
598
|
+
const elements = [];
|
|
599
|
+
if (this.peekToken().text !== "]") {
|
|
600
|
+
do {
|
|
601
|
+
if (this.peek("]")) {
|
|
602
|
+
// Support trailing commas per ES5.1.
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
elements.push(this.expression());
|
|
606
|
+
} while (this.expect(","));
|
|
607
|
+
}
|
|
608
|
+
this.consume("]");
|
|
609
|
+
|
|
610
|
+
return { type: ASTType.ArrayExpression, elements };
|
|
611
|
+
},
|
|
612
|
+
|
|
613
|
+
object() {
|
|
614
|
+
const properties = [];
|
|
615
|
+
let property;
|
|
616
|
+
if (this.peekToken().text !== "}") {
|
|
617
|
+
do {
|
|
618
|
+
if (this.peek("}")) {
|
|
619
|
+
// Support trailing commas per ES5.1.
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
622
|
+
property = { type: ASTType.Property, kind: "init" };
|
|
623
|
+
if (this.peek().constant) {
|
|
624
|
+
property.key = this.constant();
|
|
625
|
+
property.computed = false;
|
|
626
|
+
this.consume(":");
|
|
627
|
+
property.value = this.expression();
|
|
628
|
+
} else if (this.peek().identifier) {
|
|
629
|
+
property.key = this.identifier();
|
|
630
|
+
property.computed = false;
|
|
631
|
+
if (this.peek(":")) {
|
|
632
|
+
this.consume(":");
|
|
633
|
+
property.value = this.expression();
|
|
634
|
+
} else {
|
|
635
|
+
property.value = property.key;
|
|
636
|
+
}
|
|
637
|
+
} else if (this.peek("[")) {
|
|
638
|
+
this.consume("[");
|
|
639
|
+
property.key = this.expression();
|
|
640
|
+
this.consume("]");
|
|
641
|
+
property.computed = true;
|
|
642
|
+
this.consume(":");
|
|
643
|
+
property.value = this.expression();
|
|
644
|
+
} else {
|
|
645
|
+
this.throwError("invalid key", this.peek());
|
|
646
|
+
}
|
|
647
|
+
properties.push(property);
|
|
648
|
+
} while (this.expect(","));
|
|
649
|
+
}
|
|
650
|
+
this.consume("}");
|
|
651
|
+
|
|
652
|
+
return { type: ASTType.ObjectExpression, properties };
|
|
653
|
+
},
|
|
654
|
+
|
|
655
|
+
throwError(msg, token) {
|
|
656
|
+
throw $parseMinErr(
|
|
657
|
+
"syntax",
|
|
658
|
+
"Syntax Error: Token '{0}' {1} at column {2} of the expression [{3}] starting at [{4}].",
|
|
659
|
+
token.text,
|
|
660
|
+
msg,
|
|
661
|
+
token.index + 1,
|
|
662
|
+
this.text,
|
|
663
|
+
this.text.substring(token.index),
|
|
664
|
+
);
|
|
665
|
+
},
|
|
666
|
+
|
|
667
|
+
consume(e1) {
|
|
668
|
+
if (this.tokens.length === 0) {
|
|
669
|
+
throw $parseMinErr(
|
|
670
|
+
"ueoe",
|
|
671
|
+
"Unexpected end of expression: {0}",
|
|
672
|
+
this.text,
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const token = this.expect(e1);
|
|
677
|
+
if (!token) {
|
|
678
|
+
this.throwError(`is unexpected, expecting [${e1}]`, this.peek());
|
|
679
|
+
}
|
|
680
|
+
return token;
|
|
681
|
+
},
|
|
682
|
+
|
|
683
|
+
peekToken() {
|
|
684
|
+
if (this.tokens.length === 0) {
|
|
685
|
+
throw $parseMinErr(
|
|
686
|
+
"ueoe",
|
|
687
|
+
"Unexpected end of expression: {0}",
|
|
688
|
+
this.text,
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
return this.tokens[0];
|
|
692
|
+
},
|
|
693
|
+
|
|
694
|
+
peek(e1, e2, e3, e4) {
|
|
695
|
+
return this.peekAhead(0, e1, e2, e3, e4);
|
|
696
|
+
},
|
|
697
|
+
|
|
698
|
+
peekAhead(i, e1, e2, e3, e4) {
|
|
699
|
+
if (this.tokens.length > i) {
|
|
700
|
+
const token = this.tokens[i];
|
|
701
|
+
const t = token.text;
|
|
702
|
+
if (
|
|
703
|
+
t === e1 ||
|
|
704
|
+
t === e2 ||
|
|
705
|
+
t === e3 ||
|
|
706
|
+
t === e4 ||
|
|
707
|
+
(!e1 && !e2 && !e3 && !e4)
|
|
708
|
+
) {
|
|
709
|
+
return token;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return false;
|
|
713
|
+
},
|
|
714
|
+
|
|
715
|
+
expect(e1, e2, e3, e4) {
|
|
716
|
+
const token = this.peek(e1, e2, e3, e4);
|
|
717
|
+
if (token) {
|
|
718
|
+
this.tokens.shift();
|
|
719
|
+
return token;
|
|
720
|
+
}
|
|
721
|
+
return false;
|
|
722
|
+
},
|
|
723
|
+
|
|
724
|
+
selfReferential: {
|
|
725
|
+
this: { type: ASTType.ThisExpression },
|
|
726
|
+
$locals: { type: ASTType.LocalsExpression },
|
|
727
|
+
},
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
function ifDefined(v, d) {
|
|
731
|
+
return typeof v !== "undefined" ? v : d;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function plusFn(l, r) {
|
|
735
|
+
if (typeof l === "undefined") return r;
|
|
736
|
+
if (typeof r === "undefined") return l;
|
|
737
|
+
return l + r;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function isStateless($filter, filterName) {
|
|
741
|
+
const fn = $filter(filterName);
|
|
742
|
+
return !fn.$stateful;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const PURITY_ABSOLUTE = 1;
|
|
746
|
+
const PURITY_RELATIVE = 2;
|
|
747
|
+
|
|
748
|
+
// Detect nodes which could depend on non-shallow state of objects
|
|
749
|
+
function isPure(node, parentIsPure) {
|
|
750
|
+
switch (node.type) {
|
|
751
|
+
// Computed members might invoke a stateful toString()
|
|
752
|
+
case ASTType.MemberExpression:
|
|
753
|
+
if (node.computed) {
|
|
754
|
+
return false;
|
|
755
|
+
}
|
|
756
|
+
break;
|
|
757
|
+
|
|
758
|
+
// Unary always convert to primative
|
|
759
|
+
case ASTType.UnaryExpression:
|
|
760
|
+
return PURITY_ABSOLUTE;
|
|
761
|
+
|
|
762
|
+
// The binary + operator can invoke a stateful toString().
|
|
763
|
+
case ASTType.BinaryExpression:
|
|
764
|
+
return node.operator !== "+" ? PURITY_ABSOLUTE : false;
|
|
765
|
+
|
|
766
|
+
// Functions / filters probably read state from within objects
|
|
767
|
+
case ASTType.CallExpression:
|
|
768
|
+
return false;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
return undefined === parentIsPure ? PURITY_RELATIVE : parentIsPure;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function findConstantAndWatchExpressions(ast, $filter, parentIsPure) {
|
|
775
|
+
let allConstants;
|
|
776
|
+
let argsToWatch;
|
|
777
|
+
let isStatelessFilter;
|
|
778
|
+
|
|
779
|
+
const astIsPure = (ast.isPure = isPure(ast, parentIsPure));
|
|
780
|
+
|
|
781
|
+
switch (ast.type) {
|
|
782
|
+
case ASTType.Program:
|
|
783
|
+
allConstants = true;
|
|
784
|
+
forEach(ast.body, (expr) => {
|
|
785
|
+
findConstantAndWatchExpressions(expr.expression, $filter, astIsPure);
|
|
786
|
+
allConstants = allConstants && expr.expression.constant;
|
|
787
|
+
});
|
|
788
|
+
ast.constant = allConstants;
|
|
789
|
+
break;
|
|
790
|
+
case ASTType.Literal:
|
|
791
|
+
ast.constant = true;
|
|
792
|
+
ast.toWatch = [];
|
|
793
|
+
break;
|
|
794
|
+
case ASTType.UnaryExpression:
|
|
795
|
+
findConstantAndWatchExpressions(ast.argument, $filter, astIsPure);
|
|
796
|
+
ast.constant = ast.argument.constant;
|
|
797
|
+
ast.toWatch = ast.argument.toWatch;
|
|
798
|
+
break;
|
|
799
|
+
case ASTType.BinaryExpression:
|
|
800
|
+
findConstantAndWatchExpressions(ast.left, $filter, astIsPure);
|
|
801
|
+
findConstantAndWatchExpressions(ast.right, $filter, astIsPure);
|
|
802
|
+
ast.constant = ast.left.constant && ast.right.constant;
|
|
803
|
+
ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch);
|
|
804
|
+
break;
|
|
805
|
+
case ASTType.LogicalExpression:
|
|
806
|
+
findConstantAndWatchExpressions(ast.left, $filter, astIsPure);
|
|
807
|
+
findConstantAndWatchExpressions(ast.right, $filter, astIsPure);
|
|
808
|
+
ast.constant = ast.left.constant && ast.right.constant;
|
|
809
|
+
ast.toWatch = ast.constant ? [] : [ast];
|
|
810
|
+
break;
|
|
811
|
+
case ASTType.ConditionalExpression:
|
|
812
|
+
findConstantAndWatchExpressions(ast.test, $filter, astIsPure);
|
|
813
|
+
findConstantAndWatchExpressions(ast.alternate, $filter, astIsPure);
|
|
814
|
+
findConstantAndWatchExpressions(ast.consequent, $filter, astIsPure);
|
|
815
|
+
ast.constant =
|
|
816
|
+
ast.test.constant && ast.alternate.constant && ast.consequent.constant;
|
|
817
|
+
ast.toWatch = ast.constant ? [] : [ast];
|
|
818
|
+
break;
|
|
819
|
+
case ASTType.Identifier:
|
|
820
|
+
ast.constant = false;
|
|
821
|
+
ast.toWatch = [ast];
|
|
822
|
+
break;
|
|
823
|
+
case ASTType.MemberExpression:
|
|
824
|
+
findConstantAndWatchExpressions(ast.object, $filter, astIsPure);
|
|
825
|
+
if (ast.computed) {
|
|
826
|
+
findConstantAndWatchExpressions(ast.property, $filter, astIsPure);
|
|
827
|
+
}
|
|
828
|
+
ast.constant =
|
|
829
|
+
ast.object.constant && (!ast.computed || ast.property.constant);
|
|
830
|
+
ast.toWatch = ast.constant ? [] : [ast];
|
|
831
|
+
break;
|
|
832
|
+
case ASTType.CallExpression:
|
|
833
|
+
isStatelessFilter = ast.filter
|
|
834
|
+
? isStateless($filter, ast.callee.name)
|
|
835
|
+
: false;
|
|
836
|
+
allConstants = isStatelessFilter;
|
|
837
|
+
argsToWatch = [];
|
|
838
|
+
forEach(ast.arguments, (expr) => {
|
|
839
|
+
findConstantAndWatchExpressions(expr, $filter, astIsPure);
|
|
840
|
+
allConstants = allConstants && expr.constant;
|
|
841
|
+
argsToWatch.push.apply(argsToWatch, expr.toWatch);
|
|
842
|
+
});
|
|
843
|
+
ast.constant = allConstants;
|
|
844
|
+
ast.toWatch = isStatelessFilter ? argsToWatch : [ast];
|
|
845
|
+
break;
|
|
846
|
+
case ASTType.AssignmentExpression:
|
|
847
|
+
findConstantAndWatchExpressions(ast.left, $filter, astIsPure);
|
|
848
|
+
findConstantAndWatchExpressions(ast.right, $filter, astIsPure);
|
|
849
|
+
ast.constant = ast.left.constant && ast.right.constant;
|
|
850
|
+
ast.toWatch = [ast];
|
|
851
|
+
break;
|
|
852
|
+
case ASTType.ArrayExpression:
|
|
853
|
+
allConstants = true;
|
|
854
|
+
argsToWatch = [];
|
|
855
|
+
forEach(ast.elements, (expr) => {
|
|
856
|
+
findConstantAndWatchExpressions(expr, $filter, astIsPure);
|
|
857
|
+
allConstants = allConstants && expr.constant;
|
|
858
|
+
argsToWatch.push.apply(argsToWatch, expr.toWatch);
|
|
859
|
+
});
|
|
860
|
+
ast.constant = allConstants;
|
|
861
|
+
ast.toWatch = argsToWatch;
|
|
862
|
+
break;
|
|
863
|
+
case ASTType.ObjectExpression:
|
|
864
|
+
allConstants = true;
|
|
865
|
+
argsToWatch = [];
|
|
866
|
+
forEach(ast.properties, (property) => {
|
|
867
|
+
findConstantAndWatchExpressions(property.value, $filter, astIsPure);
|
|
868
|
+
allConstants = allConstants && property.value.constant;
|
|
869
|
+
argsToWatch.push.apply(argsToWatch, property.value.toWatch);
|
|
870
|
+
if (property.computed) {
|
|
871
|
+
// `{[key]: value}` implicitly does `key.toString()` which may be non-pure
|
|
872
|
+
findConstantAndWatchExpressions(
|
|
873
|
+
property.key,
|
|
874
|
+
$filter,
|
|
875
|
+
/* parentIsPure= */ false,
|
|
876
|
+
);
|
|
877
|
+
allConstants = allConstants && property.key.constant;
|
|
878
|
+
argsToWatch.push.apply(argsToWatch, property.key.toWatch);
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
ast.constant = allConstants;
|
|
882
|
+
ast.toWatch = argsToWatch;
|
|
883
|
+
break;
|
|
884
|
+
case ASTType.ThisExpression:
|
|
885
|
+
ast.constant = false;
|
|
886
|
+
ast.toWatch = [];
|
|
887
|
+
break;
|
|
888
|
+
case ASTType.LocalsExpression:
|
|
889
|
+
ast.constant = false;
|
|
890
|
+
ast.toWatch = [];
|
|
891
|
+
break;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function getInputs(body) {
|
|
896
|
+
if (body.length !== 1) return;
|
|
897
|
+
const lastExpression = body[0].expression;
|
|
898
|
+
const candidate = lastExpression.toWatch;
|
|
899
|
+
if (candidate.length !== 1) return candidate;
|
|
900
|
+
return candidate[0] !== lastExpression ? candidate : undefined;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
function isAssignable(ast) {
|
|
904
|
+
return (
|
|
905
|
+
ast.type === ASTType.Identifier || ast.type === ASTType.MemberExpression
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
function assignableAST(ast) {
|
|
910
|
+
if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) {
|
|
911
|
+
return {
|
|
912
|
+
type: ASTType.AssignmentExpression,
|
|
913
|
+
left: ast.body[0].expression,
|
|
914
|
+
right: { type: ASTType.NGValueParameter },
|
|
915
|
+
operator: "=",
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
function isLiteral(ast) {
|
|
921
|
+
return (
|
|
922
|
+
ast.body.length === 0 ||
|
|
923
|
+
(ast.body.length === 1 &&
|
|
924
|
+
(ast.body[0].expression.type === ASTType.Literal ||
|
|
925
|
+
ast.body[0].expression.type === ASTType.ArrayExpression ||
|
|
926
|
+
ast.body[0].expression.type === ASTType.ObjectExpression))
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function isConstant(ast) {
|
|
931
|
+
return ast.constant;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
function ASTCompiler($filter) {
|
|
935
|
+
this.$filter = $filter;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
ASTCompiler.prototype = {
|
|
939
|
+
compile(ast) {
|
|
940
|
+
const self = this;
|
|
941
|
+
this.state = {
|
|
942
|
+
nextId: 0,
|
|
943
|
+
filters: {},
|
|
944
|
+
fn: { vars: [], body: [], own: {} },
|
|
945
|
+
assign: { vars: [], body: [], own: {} },
|
|
946
|
+
inputs: [],
|
|
947
|
+
};
|
|
948
|
+
findConstantAndWatchExpressions(ast, self.$filter);
|
|
949
|
+
let extra = "";
|
|
950
|
+
let assignable;
|
|
951
|
+
this.stage = "assign";
|
|
952
|
+
if ((assignable = assignableAST(ast))) {
|
|
953
|
+
this.state.computing = "assign";
|
|
954
|
+
const result = this.nextId();
|
|
955
|
+
this.recurse(assignable, result);
|
|
956
|
+
this.return_(result);
|
|
957
|
+
extra = `fn.assign=${this.generateFunction("assign", "s,v,l")}`;
|
|
958
|
+
}
|
|
959
|
+
const toWatch = getInputs(ast.body);
|
|
960
|
+
self.stage = "inputs";
|
|
961
|
+
forEach(toWatch, (watch, key) => {
|
|
962
|
+
const fnKey = `fn${key}`;
|
|
963
|
+
self.state[fnKey] = { vars: [], body: [], own: {} };
|
|
964
|
+
self.state.computing = fnKey;
|
|
965
|
+
const intoId = self.nextId();
|
|
966
|
+
self.recurse(watch, intoId);
|
|
967
|
+
self.return_(intoId);
|
|
968
|
+
self.state.inputs.push({ name: fnKey, isPure: watch.isPure });
|
|
969
|
+
watch.watchId = key;
|
|
970
|
+
});
|
|
971
|
+
this.state.computing = "fn";
|
|
972
|
+
this.stage = "main";
|
|
973
|
+
this.recurse(ast);
|
|
974
|
+
const fnString = `\n${this.filterPrefix()}let fn=${this.generateFunction(
|
|
975
|
+
"fn",
|
|
976
|
+
"s,l,a,i",
|
|
977
|
+
)}${extra}${this.watchFns()}return fn;`;
|
|
978
|
+
|
|
979
|
+
// eslint-disable-next-line no-new-func
|
|
980
|
+
const fn = new Function(
|
|
981
|
+
"$filter",
|
|
982
|
+
"getStringValue",
|
|
983
|
+
"ifDefined",
|
|
984
|
+
"plus",
|
|
985
|
+
fnString,
|
|
986
|
+
)(this.$filter, getStringValue, ifDefined, plusFn);
|
|
987
|
+
this.state = this.stage = undefined;
|
|
988
|
+
return fn;
|
|
989
|
+
},
|
|
990
|
+
|
|
991
|
+
watchFns() {
|
|
992
|
+
const result = [];
|
|
993
|
+
const { inputs } = this.state;
|
|
994
|
+
const self = this;
|
|
995
|
+
forEach(inputs, (input) => {
|
|
996
|
+
result.push(
|
|
997
|
+
`let ${input.name}=${self.generateFunction(input.name, "s")}`,
|
|
998
|
+
);
|
|
999
|
+
if (input.isPure) {
|
|
1000
|
+
result.push(input.name, `.isPure=${JSON.stringify(input.isPure)};`);
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
if (inputs.length) {
|
|
1004
|
+
result.push(`fn.inputs=[${inputs.map((i) => i.name).join(",")}];`);
|
|
1005
|
+
}
|
|
1006
|
+
return result.join("");
|
|
1007
|
+
},
|
|
1008
|
+
|
|
1009
|
+
generateFunction(name, params) {
|
|
1010
|
+
return `function(${params}){${this.varsPrefix(name)}${this.body(name)}};`;
|
|
1011
|
+
},
|
|
1012
|
+
|
|
1013
|
+
filterPrefix() {
|
|
1014
|
+
const parts = [];
|
|
1015
|
+
const self = this;
|
|
1016
|
+
forEach(this.state.filters, (id, filter) => {
|
|
1017
|
+
parts.push(`${id}=$filter(${self.escape(filter)})`);
|
|
1018
|
+
});
|
|
1019
|
+
if (parts.length) return `let ${parts.join(",")};`;
|
|
1020
|
+
return "";
|
|
1021
|
+
},
|
|
1022
|
+
|
|
1023
|
+
varsPrefix(section) {
|
|
1024
|
+
return this.state[section].vars.length
|
|
1025
|
+
? `let ${this.state[section].vars.join(",")};`
|
|
1026
|
+
: "";
|
|
1027
|
+
},
|
|
1028
|
+
|
|
1029
|
+
body(section) {
|
|
1030
|
+
return this.state[section].body.join("");
|
|
1031
|
+
},
|
|
1032
|
+
|
|
1033
|
+
recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
|
|
1034
|
+
let left;
|
|
1035
|
+
let right;
|
|
1036
|
+
const self = this;
|
|
1037
|
+
let args;
|
|
1038
|
+
let expression;
|
|
1039
|
+
let computed;
|
|
1040
|
+
recursionFn = recursionFn || (() => {});
|
|
1041
|
+
if (!skipWatchIdCheck && isDefined(ast.watchId)) {
|
|
1042
|
+
intoId = intoId || this.nextId();
|
|
1043
|
+
this.if_(
|
|
1044
|
+
"i",
|
|
1045
|
+
this.lazyAssign(intoId, this.computedMember("i", ast.watchId)),
|
|
1046
|
+
this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true),
|
|
1047
|
+
);
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
switch (ast.type) {
|
|
1051
|
+
case ASTType.Program:
|
|
1052
|
+
forEach(ast.body, (expression, pos) => {
|
|
1053
|
+
self.recurse(expression.expression, undefined, undefined, (expr) => {
|
|
1054
|
+
right = expr;
|
|
1055
|
+
});
|
|
1056
|
+
if (pos !== ast.body.length - 1) {
|
|
1057
|
+
self.current().body.push(right, ";");
|
|
1058
|
+
} else {
|
|
1059
|
+
self.return_(right);
|
|
1060
|
+
}
|
|
1061
|
+
});
|
|
1062
|
+
break;
|
|
1063
|
+
case ASTType.Literal:
|
|
1064
|
+
expression = this.escape(ast.value);
|
|
1065
|
+
this.assign(intoId, expression);
|
|
1066
|
+
recursionFn(intoId || expression);
|
|
1067
|
+
break;
|
|
1068
|
+
case ASTType.UnaryExpression:
|
|
1069
|
+
this.recurse(ast.argument, undefined, undefined, (expr) => {
|
|
1070
|
+
right = expr;
|
|
1071
|
+
});
|
|
1072
|
+
expression = `${ast.operator}(${this.ifDefined(right, 0)})`;
|
|
1073
|
+
this.assign(intoId, expression);
|
|
1074
|
+
recursionFn(expression);
|
|
1075
|
+
break;
|
|
1076
|
+
case ASTType.BinaryExpression:
|
|
1077
|
+
this.recurse(ast.left, undefined, undefined, (expr) => {
|
|
1078
|
+
left = expr;
|
|
1079
|
+
});
|
|
1080
|
+
this.recurse(ast.right, undefined, undefined, (expr) => {
|
|
1081
|
+
right = expr;
|
|
1082
|
+
});
|
|
1083
|
+
if (ast.operator === "+") {
|
|
1084
|
+
expression = this.plus(left, right);
|
|
1085
|
+
} else if (ast.operator === "-") {
|
|
1086
|
+
expression =
|
|
1087
|
+
this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0);
|
|
1088
|
+
} else {
|
|
1089
|
+
expression = `(${left})${ast.operator}(${right})`;
|
|
1090
|
+
}
|
|
1091
|
+
this.assign(intoId, expression);
|
|
1092
|
+
recursionFn(expression);
|
|
1093
|
+
break;
|
|
1094
|
+
case ASTType.LogicalExpression:
|
|
1095
|
+
intoId = intoId || this.nextId();
|
|
1096
|
+
self.recurse(ast.left, intoId);
|
|
1097
|
+
self.if_(
|
|
1098
|
+
ast.operator === "&&" ? intoId : self.not(intoId),
|
|
1099
|
+
self.lazyRecurse(ast.right, intoId),
|
|
1100
|
+
);
|
|
1101
|
+
recursionFn(intoId);
|
|
1102
|
+
break;
|
|
1103
|
+
case ASTType.ConditionalExpression:
|
|
1104
|
+
intoId = intoId || this.nextId();
|
|
1105
|
+
self.recurse(ast.test, intoId);
|
|
1106
|
+
self.if_(
|
|
1107
|
+
intoId,
|
|
1108
|
+
self.lazyRecurse(ast.alternate, intoId),
|
|
1109
|
+
self.lazyRecurse(ast.consequent, intoId),
|
|
1110
|
+
);
|
|
1111
|
+
recursionFn(intoId);
|
|
1112
|
+
break;
|
|
1113
|
+
case ASTType.Identifier:
|
|
1114
|
+
intoId = intoId || this.nextId();
|
|
1115
|
+
if (nameId) {
|
|
1116
|
+
nameId.context =
|
|
1117
|
+
self.stage === "inputs"
|
|
1118
|
+
? "s"
|
|
1119
|
+
: this.assign(
|
|
1120
|
+
this.nextId(),
|
|
1121
|
+
`${this.getHasOwnProperty("l", ast.name)}?l:s`,
|
|
1122
|
+
);
|
|
1123
|
+
nameId.computed = false;
|
|
1124
|
+
nameId.name = ast.name;
|
|
1125
|
+
}
|
|
1126
|
+
self.if_(
|
|
1127
|
+
self.stage === "inputs" ||
|
|
1128
|
+
self.not(self.getHasOwnProperty("l", ast.name)),
|
|
1129
|
+
() => {
|
|
1130
|
+
self.if_(self.stage === "inputs" || "s", () => {
|
|
1131
|
+
if (create && create !== 1) {
|
|
1132
|
+
self.if_(
|
|
1133
|
+
self.isNull(self.nonComputedMember("s", ast.name)),
|
|
1134
|
+
self.lazyAssign(self.nonComputedMember("s", ast.name), "{}"),
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
self.assign(intoId, self.nonComputedMember("s", ast.name));
|
|
1138
|
+
});
|
|
1139
|
+
},
|
|
1140
|
+
intoId &&
|
|
1141
|
+
self.lazyAssign(intoId, self.nonComputedMember("l", ast.name)),
|
|
1142
|
+
);
|
|
1143
|
+
recursionFn(intoId);
|
|
1144
|
+
break;
|
|
1145
|
+
case ASTType.MemberExpression:
|
|
1146
|
+
left = (nameId && (nameId.context = this.nextId())) || this.nextId();
|
|
1147
|
+
intoId = intoId || this.nextId();
|
|
1148
|
+
self.recurse(
|
|
1149
|
+
ast.object,
|
|
1150
|
+
left,
|
|
1151
|
+
undefined,
|
|
1152
|
+
() => {
|
|
1153
|
+
self.if_(
|
|
1154
|
+
self.notNull(left),
|
|
1155
|
+
() => {
|
|
1156
|
+
if (ast.computed) {
|
|
1157
|
+
right = self.nextId();
|
|
1158
|
+
self.recurse(ast.property, right);
|
|
1159
|
+
self.getStringValue(right);
|
|
1160
|
+
if (create && create !== 1) {
|
|
1161
|
+
self.if_(
|
|
1162
|
+
self.not(self.computedMember(left, right)),
|
|
1163
|
+
self.lazyAssign(self.computedMember(left, right), "{}"),
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
expression = self.computedMember(left, right);
|
|
1167
|
+
self.assign(intoId, expression);
|
|
1168
|
+
if (nameId) {
|
|
1169
|
+
nameId.computed = true;
|
|
1170
|
+
nameId.name = right;
|
|
1171
|
+
}
|
|
1172
|
+
} else {
|
|
1173
|
+
if (create && create !== 1) {
|
|
1174
|
+
self.if_(
|
|
1175
|
+
self.isNull(
|
|
1176
|
+
self.nonComputedMember(left, ast.property.name),
|
|
1177
|
+
),
|
|
1178
|
+
self.lazyAssign(
|
|
1179
|
+
self.nonComputedMember(left, ast.property.name),
|
|
1180
|
+
"{}",
|
|
1181
|
+
),
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
expression = self.nonComputedMember(left, ast.property.name);
|
|
1185
|
+
self.assign(intoId, expression);
|
|
1186
|
+
if (nameId) {
|
|
1187
|
+
nameId.computed = false;
|
|
1188
|
+
nameId.name = ast.property.name;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
},
|
|
1192
|
+
() => {
|
|
1193
|
+
self.assign(intoId, "undefined");
|
|
1194
|
+
},
|
|
1195
|
+
);
|
|
1196
|
+
recursionFn(intoId);
|
|
1197
|
+
},
|
|
1198
|
+
!!create,
|
|
1199
|
+
);
|
|
1200
|
+
break;
|
|
1201
|
+
case ASTType.CallExpression:
|
|
1202
|
+
intoId = intoId || this.nextId();
|
|
1203
|
+
if (ast.filter) {
|
|
1204
|
+
right = self.filter(ast.callee.name);
|
|
1205
|
+
args = [];
|
|
1206
|
+
forEach(ast.arguments, (expr) => {
|
|
1207
|
+
const argument = self.nextId();
|
|
1208
|
+
self.recurse(expr, argument);
|
|
1209
|
+
args.push(argument);
|
|
1210
|
+
});
|
|
1211
|
+
expression = `${right}(${args.join(",")})`;
|
|
1212
|
+
self.assign(intoId, expression);
|
|
1213
|
+
recursionFn(intoId);
|
|
1214
|
+
} else {
|
|
1215
|
+
right = self.nextId();
|
|
1216
|
+
left = {};
|
|
1217
|
+
args = [];
|
|
1218
|
+
self.recurse(ast.callee, right, left, () => {
|
|
1219
|
+
self.if_(
|
|
1220
|
+
self.notNull(right),
|
|
1221
|
+
() => {
|
|
1222
|
+
forEach(ast.arguments, (expr) => {
|
|
1223
|
+
self.recurse(
|
|
1224
|
+
expr,
|
|
1225
|
+
ast.constant ? undefined : self.nextId(),
|
|
1226
|
+
undefined,
|
|
1227
|
+
(argument) => {
|
|
1228
|
+
args.push(argument);
|
|
1229
|
+
},
|
|
1230
|
+
);
|
|
1231
|
+
});
|
|
1232
|
+
if (left.name) {
|
|
1233
|
+
expression = `${self.member(
|
|
1234
|
+
left.context,
|
|
1235
|
+
left.name,
|
|
1236
|
+
left.computed,
|
|
1237
|
+
)}(${args.join(",")})`;
|
|
1238
|
+
} else {
|
|
1239
|
+
expression = `${right}(${args.join(",")})`;
|
|
1240
|
+
}
|
|
1241
|
+
self.assign(intoId, expression);
|
|
1242
|
+
},
|
|
1243
|
+
() => {
|
|
1244
|
+
self.assign(intoId, "undefined");
|
|
1245
|
+
},
|
|
1246
|
+
);
|
|
1247
|
+
recursionFn(intoId);
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
break;
|
|
1251
|
+
case ASTType.AssignmentExpression:
|
|
1252
|
+
right = this.nextId();
|
|
1253
|
+
left = {};
|
|
1254
|
+
this.recurse(
|
|
1255
|
+
ast.left,
|
|
1256
|
+
undefined,
|
|
1257
|
+
left,
|
|
1258
|
+
() => {
|
|
1259
|
+
self.if_(self.notNull(left.context), () => {
|
|
1260
|
+
self.recurse(ast.right, right);
|
|
1261
|
+
expression =
|
|
1262
|
+
self.member(left.context, left.name, left.computed) +
|
|
1263
|
+
ast.operator +
|
|
1264
|
+
right;
|
|
1265
|
+
self.assign(intoId, expression);
|
|
1266
|
+
recursionFn(intoId || expression);
|
|
1267
|
+
});
|
|
1268
|
+
},
|
|
1269
|
+
1,
|
|
1270
|
+
);
|
|
1271
|
+
break;
|
|
1272
|
+
case ASTType.ArrayExpression:
|
|
1273
|
+
args = [];
|
|
1274
|
+
forEach(ast.elements, (expr) => {
|
|
1275
|
+
self.recurse(
|
|
1276
|
+
expr,
|
|
1277
|
+
ast.constant ? undefined : self.nextId(),
|
|
1278
|
+
undefined,
|
|
1279
|
+
(argument) => {
|
|
1280
|
+
args.push(argument);
|
|
1281
|
+
},
|
|
1282
|
+
);
|
|
1283
|
+
});
|
|
1284
|
+
expression = `[${args.join(",")}]`;
|
|
1285
|
+
this.assign(intoId, expression);
|
|
1286
|
+
recursionFn(intoId || expression);
|
|
1287
|
+
break;
|
|
1288
|
+
case ASTType.ObjectExpression:
|
|
1289
|
+
args = [];
|
|
1290
|
+
computed = false;
|
|
1291
|
+
forEach(ast.properties, (property) => {
|
|
1292
|
+
if (property.computed) {
|
|
1293
|
+
computed = true;
|
|
1294
|
+
}
|
|
1295
|
+
});
|
|
1296
|
+
if (computed) {
|
|
1297
|
+
intoId = intoId || this.nextId();
|
|
1298
|
+
this.assign(intoId, "{}");
|
|
1299
|
+
forEach(ast.properties, (property) => {
|
|
1300
|
+
if (property.computed) {
|
|
1301
|
+
left = self.nextId();
|
|
1302
|
+
self.recurse(property.key, left);
|
|
1303
|
+
} else {
|
|
1304
|
+
left =
|
|
1305
|
+
property.key.type === ASTType.Identifier
|
|
1306
|
+
? property.key.name
|
|
1307
|
+
: `${property.key.value}`;
|
|
1308
|
+
}
|
|
1309
|
+
right = self.nextId();
|
|
1310
|
+
self.recurse(property.value, right);
|
|
1311
|
+
self.assign(self.member(intoId, left, property.computed), right);
|
|
1312
|
+
});
|
|
1313
|
+
} else {
|
|
1314
|
+
forEach(ast.properties, (property) => {
|
|
1315
|
+
self.recurse(
|
|
1316
|
+
property.value,
|
|
1317
|
+
ast.constant ? undefined : self.nextId(),
|
|
1318
|
+
undefined,
|
|
1319
|
+
(expr) => {
|
|
1320
|
+
args.push(
|
|
1321
|
+
`${self.escape(
|
|
1322
|
+
property.key.type === ASTType.Identifier
|
|
1323
|
+
? property.key.name
|
|
1324
|
+
: `${property.key.value}`,
|
|
1325
|
+
)}:${expr}`,
|
|
1326
|
+
);
|
|
1327
|
+
},
|
|
1328
|
+
);
|
|
1329
|
+
});
|
|
1330
|
+
expression = `{${args.join(",")}}`;
|
|
1331
|
+
this.assign(intoId, expression);
|
|
1332
|
+
}
|
|
1333
|
+
recursionFn(intoId || expression);
|
|
1334
|
+
break;
|
|
1335
|
+
case ASTType.ThisExpression:
|
|
1336
|
+
this.assign(intoId, "s");
|
|
1337
|
+
recursionFn(intoId || "s");
|
|
1338
|
+
break;
|
|
1339
|
+
case ASTType.LocalsExpression:
|
|
1340
|
+
this.assign(intoId, "l");
|
|
1341
|
+
recursionFn(intoId || "l");
|
|
1342
|
+
break;
|
|
1343
|
+
case ASTType.NGValueParameter:
|
|
1344
|
+
this.assign(intoId, "v");
|
|
1345
|
+
recursionFn(intoId || "v");
|
|
1346
|
+
break;
|
|
1347
|
+
}
|
|
1348
|
+
},
|
|
1349
|
+
|
|
1350
|
+
getHasOwnProperty(element, property) {
|
|
1351
|
+
const key = `${element}.${property}`;
|
|
1352
|
+
const { own } = this.current();
|
|
1353
|
+
if (!Object.prototype.hasOwnProperty.call(own, key)) {
|
|
1354
|
+
own[key] = this.nextId(
|
|
1355
|
+
false,
|
|
1356
|
+
`${element}&&(${this.escape(property)} in ${element})`,
|
|
1357
|
+
);
|
|
1358
|
+
}
|
|
1359
|
+
return own[key];
|
|
1360
|
+
},
|
|
1361
|
+
|
|
1362
|
+
assign(id, value) {
|
|
1363
|
+
if (!id) return;
|
|
1364
|
+
this.current().body.push(id, "=", value, ";");
|
|
1365
|
+
return id;
|
|
1366
|
+
},
|
|
1367
|
+
|
|
1368
|
+
filter(filterName) {
|
|
1369
|
+
if (!Object.prototype.hasOwnProperty.call(this.state.filters, filterName)) {
|
|
1370
|
+
this.state.filters[filterName] = this.nextId(true);
|
|
1371
|
+
}
|
|
1372
|
+
return this.state.filters[filterName];
|
|
1373
|
+
},
|
|
1374
|
+
|
|
1375
|
+
ifDefined(id, defaultValue) {
|
|
1376
|
+
return `ifDefined(${id},${this.escape(defaultValue)})`;
|
|
1377
|
+
},
|
|
1378
|
+
|
|
1379
|
+
plus(left, right) {
|
|
1380
|
+
return `plus(${left},${right})`;
|
|
1381
|
+
},
|
|
1382
|
+
|
|
1383
|
+
return_(id) {
|
|
1384
|
+
this.current().body.push("return ", id, ";");
|
|
1385
|
+
},
|
|
1386
|
+
|
|
1387
|
+
if_(test, alternate, consequent) {
|
|
1388
|
+
if (test === true) {
|
|
1389
|
+
alternate();
|
|
1390
|
+
} else {
|
|
1391
|
+
const { body } = this.current();
|
|
1392
|
+
body.push("if(", test, "){");
|
|
1393
|
+
alternate();
|
|
1394
|
+
body.push("}");
|
|
1395
|
+
if (consequent) {
|
|
1396
|
+
body.push("else{");
|
|
1397
|
+
consequent();
|
|
1398
|
+
body.push("}");
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
},
|
|
1402
|
+
|
|
1403
|
+
not(expression) {
|
|
1404
|
+
return `!(${expression})`;
|
|
1405
|
+
},
|
|
1406
|
+
|
|
1407
|
+
isNull(expression) {
|
|
1408
|
+
return `${expression}==null`;
|
|
1409
|
+
},
|
|
1410
|
+
|
|
1411
|
+
notNull(expression) {
|
|
1412
|
+
return `${expression}!=null`;
|
|
1413
|
+
},
|
|
1414
|
+
|
|
1415
|
+
nonComputedMember(left, right) {
|
|
1416
|
+
const SAFE_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/;
|
|
1417
|
+
const UNSAFE_CHARACTERS = /[^$_a-zA-Z0-9]/g;
|
|
1418
|
+
if (SAFE_IDENTIFIER.test(right)) {
|
|
1419
|
+
return `${left}.${right}`;
|
|
1420
|
+
}
|
|
1421
|
+
return `${left}["${right.replace(
|
|
1422
|
+
UNSAFE_CHARACTERS,
|
|
1423
|
+
this.stringEscapeFn,
|
|
1424
|
+
)}"]`;
|
|
1425
|
+
},
|
|
1426
|
+
|
|
1427
|
+
computedMember(left, right) {
|
|
1428
|
+
return `${left}[${right}]`;
|
|
1429
|
+
},
|
|
1430
|
+
|
|
1431
|
+
member(left, right, computed) {
|
|
1432
|
+
if (computed) return this.computedMember(left, right);
|
|
1433
|
+
return this.nonComputedMember(left, right);
|
|
1434
|
+
},
|
|
1435
|
+
|
|
1436
|
+
getStringValue(item) {
|
|
1437
|
+
this.assign(item, `getStringValue(${item})`);
|
|
1438
|
+
},
|
|
1439
|
+
|
|
1440
|
+
lazyRecurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
|
|
1441
|
+
const self = this;
|
|
1442
|
+
return function () {
|
|
1443
|
+
self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck);
|
|
1444
|
+
};
|
|
1445
|
+
},
|
|
1446
|
+
|
|
1447
|
+
lazyAssign(id, value) {
|
|
1448
|
+
const self = this;
|
|
1449
|
+
return function () {
|
|
1450
|
+
self.assign(id, value);
|
|
1451
|
+
};
|
|
1452
|
+
},
|
|
1453
|
+
|
|
1454
|
+
stringEscapeRegex: /[^ a-zA-Z0-9]/g,
|
|
1455
|
+
|
|
1456
|
+
stringEscapeFn(c) {
|
|
1457
|
+
return `\\u${`0000${c.charCodeAt(0).toString(16)}`.slice(-4)}`;
|
|
1458
|
+
},
|
|
1459
|
+
|
|
1460
|
+
escape(value) {
|
|
1461
|
+
if (isString(value))
|
|
1462
|
+
return `'${value.replace(this.stringEscapeRegex, this.stringEscapeFn)}'`;
|
|
1463
|
+
if (isNumber(value)) return value.toString();
|
|
1464
|
+
if (value === true) return "true";
|
|
1465
|
+
if (value === false) return "false";
|
|
1466
|
+
if (value === null) return "null";
|
|
1467
|
+
if (typeof value === "undefined") return "undefined";
|
|
1468
|
+
|
|
1469
|
+
throw $parseMinErr("esc", "IMPOSSIBLE");
|
|
1470
|
+
},
|
|
1471
|
+
|
|
1472
|
+
nextId(skip, init) {
|
|
1473
|
+
const id = `v${this.state.nextId++}`;
|
|
1474
|
+
if (!skip) {
|
|
1475
|
+
this.current().vars.push(id + (init ? `=${init}` : ""));
|
|
1476
|
+
}
|
|
1477
|
+
return id;
|
|
1478
|
+
},
|
|
1479
|
+
|
|
1480
|
+
current() {
|
|
1481
|
+
return this.state[this.state.computing];
|
|
1482
|
+
},
|
|
1483
|
+
};
|
|
1484
|
+
|
|
1485
|
+
function ASTInterpreter($filter) {
|
|
1486
|
+
this.$filter = $filter;
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
ASTInterpreter.prototype = {
|
|
1490
|
+
compile(ast) {
|
|
1491
|
+
const self = this;
|
|
1492
|
+
findConstantAndWatchExpressions(ast, self.$filter);
|
|
1493
|
+
let assignable;
|
|
1494
|
+
let assign;
|
|
1495
|
+
if ((assignable = assignableAST(ast))) {
|
|
1496
|
+
assign = this.recurse(assignable);
|
|
1497
|
+
}
|
|
1498
|
+
const toWatch = getInputs(ast.body);
|
|
1499
|
+
let inputs;
|
|
1500
|
+
if (toWatch) {
|
|
1501
|
+
inputs = [];
|
|
1502
|
+
forEach(toWatch, (watch, key) => {
|
|
1503
|
+
const input = self.recurse(watch);
|
|
1504
|
+
input.isPure = watch.isPure;
|
|
1505
|
+
watch.input = input;
|
|
1506
|
+
inputs.push(input);
|
|
1507
|
+
watch.watchId = key;
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
const expressions = [];
|
|
1511
|
+
forEach(ast.body, (expression) => {
|
|
1512
|
+
expressions.push(self.recurse(expression.expression));
|
|
1513
|
+
});
|
|
1514
|
+
const fn =
|
|
1515
|
+
ast.body.length === 0
|
|
1516
|
+
? () => {}
|
|
1517
|
+
: ast.body.length === 1
|
|
1518
|
+
? expressions[0]
|
|
1519
|
+
: function (scope, locals) {
|
|
1520
|
+
let lastValue;
|
|
1521
|
+
forEach(expressions, (exp) => {
|
|
1522
|
+
lastValue = exp(scope, locals);
|
|
1523
|
+
});
|
|
1524
|
+
return lastValue;
|
|
1525
|
+
};
|
|
1526
|
+
if (assign) {
|
|
1527
|
+
fn.assign = function (scope, value, locals) {
|
|
1528
|
+
return assign(scope, locals, value);
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
if (inputs) {
|
|
1532
|
+
fn.inputs = inputs;
|
|
1533
|
+
}
|
|
1534
|
+
return fn;
|
|
1535
|
+
},
|
|
1536
|
+
|
|
1537
|
+
recurse(ast, context, create) {
|
|
1538
|
+
let left;
|
|
1539
|
+
let right;
|
|
1540
|
+
const self = this;
|
|
1541
|
+
let args;
|
|
1542
|
+
if (ast.input) {
|
|
1543
|
+
return this.inputs(ast.input, ast.watchId);
|
|
1544
|
+
}
|
|
1545
|
+
switch (ast.type) {
|
|
1546
|
+
case ASTType.Literal:
|
|
1547
|
+
return this.value(ast.value, context);
|
|
1548
|
+
case ASTType.UnaryExpression:
|
|
1549
|
+
right = this.recurse(ast.argument);
|
|
1550
|
+
return this[`unary${ast.operator}`](right, context);
|
|
1551
|
+
case ASTType.BinaryExpression:
|
|
1552
|
+
left = this.recurse(ast.left);
|
|
1553
|
+
right = this.recurse(ast.right);
|
|
1554
|
+
return this[`binary${ast.operator}`](left, right, context);
|
|
1555
|
+
case ASTType.LogicalExpression:
|
|
1556
|
+
left = this.recurse(ast.left);
|
|
1557
|
+
right = this.recurse(ast.right);
|
|
1558
|
+
return this[`binary${ast.operator}`](left, right, context);
|
|
1559
|
+
case ASTType.ConditionalExpression:
|
|
1560
|
+
return this["ternary?:"](
|
|
1561
|
+
this.recurse(ast.test),
|
|
1562
|
+
this.recurse(ast.alternate),
|
|
1563
|
+
this.recurse(ast.consequent),
|
|
1564
|
+
context,
|
|
1565
|
+
);
|
|
1566
|
+
case ASTType.Identifier:
|
|
1567
|
+
return self.identifier(ast.name, context, create);
|
|
1568
|
+
case ASTType.MemberExpression:
|
|
1569
|
+
left = this.recurse(ast.object, false, !!create);
|
|
1570
|
+
if (!ast.computed) {
|
|
1571
|
+
right = ast.property.name;
|
|
1572
|
+
}
|
|
1573
|
+
if (ast.computed) right = this.recurse(ast.property);
|
|
1574
|
+
return ast.computed
|
|
1575
|
+
? this.computedMember(left, right, context, create)
|
|
1576
|
+
: this.nonComputedMember(left, right, context, create);
|
|
1577
|
+
case ASTType.CallExpression:
|
|
1578
|
+
args = [];
|
|
1579
|
+
forEach(ast.arguments, (expr) => {
|
|
1580
|
+
args.push(self.recurse(expr));
|
|
1581
|
+
});
|
|
1582
|
+
if (ast.filter) right = this.$filter(ast.callee.name);
|
|
1583
|
+
if (!ast.filter) right = this.recurse(ast.callee, true);
|
|
1584
|
+
return ast.filter
|
|
1585
|
+
? function (scope, locals, assign, inputs) {
|
|
1586
|
+
const values = [];
|
|
1587
|
+
for (let i = 0; i < args.length; ++i) {
|
|
1588
|
+
values.push(args[i](scope, locals, assign, inputs));
|
|
1589
|
+
}
|
|
1590
|
+
const value = right.apply(undefined, values, inputs);
|
|
1591
|
+
return context
|
|
1592
|
+
? { context: undefined, name: undefined, value }
|
|
1593
|
+
: value;
|
|
1594
|
+
}
|
|
1595
|
+
: function (scope, locals, assign, inputs) {
|
|
1596
|
+
const rhs = right(scope, locals, assign, inputs);
|
|
1597
|
+
let value;
|
|
1598
|
+
if (rhs.value != null) {
|
|
1599
|
+
const values = [];
|
|
1600
|
+
for (let i = 0; i < args.length; ++i) {
|
|
1601
|
+
values.push(args[i](scope, locals, assign, inputs));
|
|
1602
|
+
}
|
|
1603
|
+
value = rhs.value.apply(rhs.context, values);
|
|
1604
|
+
}
|
|
1605
|
+
return context ? { value } : value;
|
|
1606
|
+
};
|
|
1607
|
+
case ASTType.AssignmentExpression:
|
|
1608
|
+
left = this.recurse(ast.left, true, 1);
|
|
1609
|
+
right = this.recurse(ast.right);
|
|
1610
|
+
return function (scope, locals, assign, inputs) {
|
|
1611
|
+
const lhs = left(scope, locals, assign, inputs);
|
|
1612
|
+
const rhs = right(scope, locals, assign, inputs);
|
|
1613
|
+
lhs.context[lhs.name] = rhs;
|
|
1614
|
+
return context ? { value: rhs } : rhs;
|
|
1615
|
+
};
|
|
1616
|
+
case ASTType.ArrayExpression:
|
|
1617
|
+
args = [];
|
|
1618
|
+
forEach(ast.elements, (expr) => {
|
|
1619
|
+
args.push(self.recurse(expr));
|
|
1620
|
+
});
|
|
1621
|
+
return function (scope, locals, assign, inputs) {
|
|
1622
|
+
const value = [];
|
|
1623
|
+
for (let i = 0; i < args.length; ++i) {
|
|
1624
|
+
value.push(args[i](scope, locals, assign, inputs));
|
|
1625
|
+
}
|
|
1626
|
+
return context ? { value } : value;
|
|
1627
|
+
};
|
|
1628
|
+
case ASTType.ObjectExpression:
|
|
1629
|
+
args = [];
|
|
1630
|
+
forEach(ast.properties, (property) => {
|
|
1631
|
+
if (property.computed) {
|
|
1632
|
+
args.push({
|
|
1633
|
+
key: self.recurse(property.key),
|
|
1634
|
+
computed: true,
|
|
1635
|
+
value: self.recurse(property.value),
|
|
1636
|
+
});
|
|
1637
|
+
} else {
|
|
1638
|
+
args.push({
|
|
1639
|
+
key:
|
|
1640
|
+
property.key.type === ASTType.Identifier
|
|
1641
|
+
? property.key.name
|
|
1642
|
+
: `${property.key.value}`,
|
|
1643
|
+
computed: false,
|
|
1644
|
+
value: self.recurse(property.value),
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
});
|
|
1648
|
+
return function (scope, locals, assign, inputs) {
|
|
1649
|
+
const value = {};
|
|
1650
|
+
for (let i = 0; i < args.length; ++i) {
|
|
1651
|
+
if (args[i].computed) {
|
|
1652
|
+
value[args[i].key(scope, locals, assign, inputs)] = args[i].value(
|
|
1653
|
+
scope,
|
|
1654
|
+
locals,
|
|
1655
|
+
assign,
|
|
1656
|
+
inputs,
|
|
1657
|
+
);
|
|
1658
|
+
} else {
|
|
1659
|
+
value[args[i].key] = args[i].value(scope, locals, assign, inputs);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
return context ? { value } : value;
|
|
1663
|
+
};
|
|
1664
|
+
case ASTType.ThisExpression:
|
|
1665
|
+
return function (scope) {
|
|
1666
|
+
return context ? { value: scope } : scope;
|
|
1667
|
+
};
|
|
1668
|
+
case ASTType.LocalsExpression:
|
|
1669
|
+
return function (scope, locals) {
|
|
1670
|
+
return context ? { value: locals } : locals;
|
|
1671
|
+
};
|
|
1672
|
+
case ASTType.NGValueParameter:
|
|
1673
|
+
return function (scope, locals, assign) {
|
|
1674
|
+
return context ? { value: assign } : assign;
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
},
|
|
1678
|
+
|
|
1679
|
+
"unary+": function (argument, context) {
|
|
1680
|
+
return function (scope, locals, assign, inputs) {
|
|
1681
|
+
let arg = argument(scope, locals, assign, inputs);
|
|
1682
|
+
if (isDefined(arg)) {
|
|
1683
|
+
arg = +arg;
|
|
1684
|
+
} else {
|
|
1685
|
+
arg = 0;
|
|
1686
|
+
}
|
|
1687
|
+
return context ? { value: arg } : arg;
|
|
1688
|
+
};
|
|
1689
|
+
},
|
|
1690
|
+
"unary-": function (argument, context) {
|
|
1691
|
+
return function (scope, locals, assign, inputs) {
|
|
1692
|
+
let arg = argument(scope, locals, assign, inputs);
|
|
1693
|
+
if (isDefined(arg)) {
|
|
1694
|
+
arg = -arg;
|
|
1695
|
+
} else {
|
|
1696
|
+
arg = -0;
|
|
1697
|
+
}
|
|
1698
|
+
return context ? { value: arg } : arg;
|
|
1699
|
+
};
|
|
1700
|
+
},
|
|
1701
|
+
"unary!": function (argument, context) {
|
|
1702
|
+
return function (scope, locals, assign, inputs) {
|
|
1703
|
+
const arg = !argument(scope, locals, assign, inputs);
|
|
1704
|
+
return context ? { value: arg } : arg;
|
|
1705
|
+
};
|
|
1706
|
+
},
|
|
1707
|
+
"binary+": function (left, right, context) {
|
|
1708
|
+
return function (scope, locals, assign, inputs) {
|
|
1709
|
+
const lhs = left(scope, locals, assign, inputs);
|
|
1710
|
+
const rhs = right(scope, locals, assign, inputs);
|
|
1711
|
+
const arg = plusFn(lhs, rhs);
|
|
1712
|
+
return context ? { value: arg } : arg;
|
|
1713
|
+
};
|
|
1714
|
+
},
|
|
1715
|
+
"binary-": function (left, right, context) {
|
|
1716
|
+
return function (scope, locals, assign, inputs) {
|
|
1717
|
+
const lhs = left(scope, locals, assign, inputs);
|
|
1718
|
+
const rhs = right(scope, locals, assign, inputs);
|
|
1719
|
+
const arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0);
|
|
1720
|
+
return context ? { value: arg } : arg;
|
|
1721
|
+
};
|
|
1722
|
+
},
|
|
1723
|
+
"binary*": function (left, right, context) {
|
|
1724
|
+
return function (scope, locals, assign, inputs) {
|
|
1725
|
+
const arg =
|
|
1726
|
+
left(scope, locals, assign, inputs) *
|
|
1727
|
+
right(scope, locals, assign, inputs);
|
|
1728
|
+
return context ? { value: arg } : arg;
|
|
1729
|
+
};
|
|
1730
|
+
},
|
|
1731
|
+
"binary/": function (left, right, context) {
|
|
1732
|
+
return function (scope, locals, assign, inputs) {
|
|
1733
|
+
const arg =
|
|
1734
|
+
left(scope, locals, assign, inputs) /
|
|
1735
|
+
right(scope, locals, assign, inputs);
|
|
1736
|
+
return context ? { value: arg } : arg;
|
|
1737
|
+
};
|
|
1738
|
+
},
|
|
1739
|
+
"binary%": function (left, right, context) {
|
|
1740
|
+
return function (scope, locals, assign, inputs) {
|
|
1741
|
+
const arg =
|
|
1742
|
+
left(scope, locals, assign, inputs) %
|
|
1743
|
+
right(scope, locals, assign, inputs);
|
|
1744
|
+
return context ? { value: arg } : arg;
|
|
1745
|
+
};
|
|
1746
|
+
},
|
|
1747
|
+
"binary===": function (left, right, context) {
|
|
1748
|
+
return function (scope, locals, assign, inputs) {
|
|
1749
|
+
const arg =
|
|
1750
|
+
left(scope, locals, assign, inputs) ===
|
|
1751
|
+
right(scope, locals, assign, inputs);
|
|
1752
|
+
return context ? { value: arg } : arg;
|
|
1753
|
+
};
|
|
1754
|
+
},
|
|
1755
|
+
"binary!==": function (left, right, context) {
|
|
1756
|
+
return function (scope, locals, assign, inputs) {
|
|
1757
|
+
const arg =
|
|
1758
|
+
left(scope, locals, assign, inputs) !==
|
|
1759
|
+
right(scope, locals, assign, inputs);
|
|
1760
|
+
return context ? { value: arg } : arg;
|
|
1761
|
+
};
|
|
1762
|
+
},
|
|
1763
|
+
"binary==": function (left, right, context) {
|
|
1764
|
+
return function (scope, locals, assign, inputs) {
|
|
1765
|
+
const arg =
|
|
1766
|
+
left(scope, locals, assign, inputs) ==
|
|
1767
|
+
right(scope, locals, assign, inputs);
|
|
1768
|
+
return context ? { value: arg } : arg;
|
|
1769
|
+
};
|
|
1770
|
+
},
|
|
1771
|
+
"binary!=": function (left, right, context) {
|
|
1772
|
+
return function (scope, locals, assign, inputs) {
|
|
1773
|
+
const arg =
|
|
1774
|
+
left(scope, locals, assign, inputs) !=
|
|
1775
|
+
right(scope, locals, assign, inputs);
|
|
1776
|
+
return context ? { value: arg } : arg;
|
|
1777
|
+
};
|
|
1778
|
+
},
|
|
1779
|
+
"binary<": function (left, right, context) {
|
|
1780
|
+
return function (scope, locals, assign, inputs) {
|
|
1781
|
+
const arg =
|
|
1782
|
+
left(scope, locals, assign, inputs) <
|
|
1783
|
+
right(scope, locals, assign, inputs);
|
|
1784
|
+
return context ? { value: arg } : arg;
|
|
1785
|
+
};
|
|
1786
|
+
},
|
|
1787
|
+
"binary>": function (left, right, context) {
|
|
1788
|
+
return function (scope, locals, assign, inputs) {
|
|
1789
|
+
const arg =
|
|
1790
|
+
left(scope, locals, assign, inputs) >
|
|
1791
|
+
right(scope, locals, assign, inputs);
|
|
1792
|
+
return context ? { value: arg } : arg;
|
|
1793
|
+
};
|
|
1794
|
+
},
|
|
1795
|
+
"binary<=": function (left, right, context) {
|
|
1796
|
+
return function (scope, locals, assign, inputs) {
|
|
1797
|
+
const arg =
|
|
1798
|
+
left(scope, locals, assign, inputs) <=
|
|
1799
|
+
right(scope, locals, assign, inputs);
|
|
1800
|
+
return context ? { value: arg } : arg;
|
|
1801
|
+
};
|
|
1802
|
+
},
|
|
1803
|
+
"binary>=": function (left, right, context) {
|
|
1804
|
+
return function (scope, locals, assign, inputs) {
|
|
1805
|
+
const arg =
|
|
1806
|
+
left(scope, locals, assign, inputs) >=
|
|
1807
|
+
right(scope, locals, assign, inputs);
|
|
1808
|
+
return context ? { value: arg } : arg;
|
|
1809
|
+
};
|
|
1810
|
+
},
|
|
1811
|
+
"binary&&": function (left, right, context) {
|
|
1812
|
+
return function (scope, locals, assign, inputs) {
|
|
1813
|
+
const arg =
|
|
1814
|
+
left(scope, locals, assign, inputs) &&
|
|
1815
|
+
right(scope, locals, assign, inputs);
|
|
1816
|
+
return context ? { value: arg } : arg;
|
|
1817
|
+
};
|
|
1818
|
+
},
|
|
1819
|
+
"binary||": function (left, right, context) {
|
|
1820
|
+
return function (scope, locals, assign, inputs) {
|
|
1821
|
+
const arg =
|
|
1822
|
+
left(scope, locals, assign, inputs) ||
|
|
1823
|
+
right(scope, locals, assign, inputs);
|
|
1824
|
+
return context ? { value: arg } : arg;
|
|
1825
|
+
};
|
|
1826
|
+
},
|
|
1827
|
+
"ternary?:": function (test, alternate, consequent, context) {
|
|
1828
|
+
return function (scope, locals, assign, inputs) {
|
|
1829
|
+
const arg = test(scope, locals, assign, inputs)
|
|
1830
|
+
? alternate(scope, locals, assign, inputs)
|
|
1831
|
+
: consequent(scope, locals, assign, inputs);
|
|
1832
|
+
return context ? { value: arg } : arg;
|
|
1833
|
+
};
|
|
1834
|
+
},
|
|
1835
|
+
value(value, context) {
|
|
1836
|
+
return function () {
|
|
1837
|
+
return context ? { context: undefined, name: undefined, value } : value;
|
|
1838
|
+
};
|
|
1839
|
+
},
|
|
1840
|
+
identifier(name, context, create) {
|
|
1841
|
+
return function (scope, locals) {
|
|
1842
|
+
const base = locals && name in locals ? locals : scope;
|
|
1843
|
+
if (create && create !== 1 && base && base[name] == null) {
|
|
1844
|
+
base[name] = {};
|
|
1845
|
+
}
|
|
1846
|
+
const value = base ? base[name] : undefined;
|
|
1847
|
+
if (context) {
|
|
1848
|
+
return { context: base, name, value };
|
|
1849
|
+
}
|
|
1850
|
+
return value;
|
|
1851
|
+
};
|
|
1852
|
+
},
|
|
1853
|
+
computedMember(left, right, context, create) {
|
|
1854
|
+
return function (scope, locals, assign, inputs) {
|
|
1855
|
+
const lhs = left(scope, locals, assign, inputs);
|
|
1856
|
+
let rhs;
|
|
1857
|
+
let value;
|
|
1858
|
+
if (lhs != null) {
|
|
1859
|
+
rhs = right(scope, locals, assign, inputs);
|
|
1860
|
+
rhs = getStringValue(rhs);
|
|
1861
|
+
if (create && create !== 1) {
|
|
1862
|
+
if (lhs && !lhs[rhs]) {
|
|
1863
|
+
lhs[rhs] = {};
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
value = lhs[rhs];
|
|
1867
|
+
}
|
|
1868
|
+
if (context) {
|
|
1869
|
+
return { context: lhs, name: rhs, value };
|
|
1870
|
+
}
|
|
1871
|
+
return value;
|
|
1872
|
+
};
|
|
1873
|
+
},
|
|
1874
|
+
nonComputedMember(left, right, context, create) {
|
|
1875
|
+
return function (scope, locals, assign, inputs) {
|
|
1876
|
+
const lhs = left(scope, locals, assign, inputs);
|
|
1877
|
+
if (create && create !== 1) {
|
|
1878
|
+
if (lhs && lhs[right] == null) {
|
|
1879
|
+
lhs[right] = {};
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
const value = lhs != null ? lhs[right] : undefined;
|
|
1883
|
+
if (context) {
|
|
1884
|
+
return { context: lhs, name: right, value };
|
|
1885
|
+
}
|
|
1886
|
+
return value;
|
|
1887
|
+
};
|
|
1888
|
+
},
|
|
1889
|
+
inputs(input, watchId) {
|
|
1890
|
+
return function (scope, value, locals, inputs) {
|
|
1891
|
+
if (inputs) return inputs[watchId];
|
|
1892
|
+
return input(scope, value, locals);
|
|
1893
|
+
};
|
|
1894
|
+
},
|
|
1895
|
+
};
|
|
1896
|
+
|
|
1897
|
+
/**
|
|
1898
|
+
* @constructor
|
|
1899
|
+
*/
|
|
1900
|
+
class Parser {
|
|
1901
|
+
constructor(lexer, $filter, options) {
|
|
1902
|
+
this.ast = new AST(lexer, options);
|
|
1903
|
+
this.astCompiler = options.csp
|
|
1904
|
+
? new ASTInterpreter($filter)
|
|
1905
|
+
: new ASTCompiler($filter);
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
parse(text) {
|
|
1909
|
+
const { ast, oneTime } = this.getAst(text);
|
|
1910
|
+
const fn = this.astCompiler.compile(ast);
|
|
1911
|
+
fn.literal = isLiteral(ast);
|
|
1912
|
+
fn.constant = isConstant(ast);
|
|
1913
|
+
fn.oneTime = oneTime;
|
|
1914
|
+
return fn;
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
getAst(exp) {
|
|
1918
|
+
let oneTime = false;
|
|
1919
|
+
exp = exp.trim();
|
|
1920
|
+
|
|
1921
|
+
if (exp.startsWith("::")) {
|
|
1922
|
+
oneTime = true;
|
|
1923
|
+
exp = exp.substring(2);
|
|
1924
|
+
}
|
|
1925
|
+
return {
|
|
1926
|
+
ast: this.ast.ast(exp),
|
|
1927
|
+
oneTime,
|
|
1928
|
+
};
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
function getValueOf(value) {
|
|
1933
|
+
return isFunction(value.valueOf)
|
|
1934
|
+
? value.valueOf()
|
|
1935
|
+
: objectValueOf.call(value);
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
/// ////////////////////////////////
|
|
1939
|
+
|
|
1940
|
+
/**
|
|
1941
|
+
* @ngdoc service
|
|
1942
|
+
* @name $parse
|
|
1943
|
+
* @kind function
|
|
1944
|
+
*
|
|
1945
|
+
* @description
|
|
1946
|
+
*
|
|
1947
|
+
* Converts AngularJS {@link guide/expression expression} into a function.
|
|
1948
|
+
*
|
|
1949
|
+
* ```js
|
|
1950
|
+
* let getter = $parse('user.name');
|
|
1951
|
+
* let setter = getter.assign;
|
|
1952
|
+
* let context = {user:{name:'AngularJS'}};
|
|
1953
|
+
* let locals = {user:{name:'local'}};
|
|
1954
|
+
*
|
|
1955
|
+
* expect(getter(context)).toEqual('AngularJS');
|
|
1956
|
+
* setter(context, 'newValue');
|
|
1957
|
+
* expect(context.user.name).toEqual('newValue');
|
|
1958
|
+
* expect(getter(context, locals)).toEqual('local');
|
|
1959
|
+
* ```
|
|
1960
|
+
*
|
|
1961
|
+
*
|
|
1962
|
+
* @param {string} expression String expression to compile.
|
|
1963
|
+
* @returns {function(context, locals)} a function which represents the compiled expression:
|
|
1964
|
+
*
|
|
1965
|
+
* * `context` – `{object}` – an object against which any expressions embedded in the strings
|
|
1966
|
+
* are evaluated against (typically a scope object).
|
|
1967
|
+
* * `locals` – `{object=}` – local variables context object, useful for overriding values in
|
|
1968
|
+
* `context`.
|
|
1969
|
+
*
|
|
1970
|
+
* The returned function also has the following properties:
|
|
1971
|
+
* * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript
|
|
1972
|
+
* literal.
|
|
1973
|
+
* * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript
|
|
1974
|
+
* constant literals.
|
|
1975
|
+
* * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be
|
|
1976
|
+
* set to a function to change its value on the given context.
|
|
1977
|
+
*
|
|
1978
|
+
*/
|
|
1979
|
+
|
|
1980
|
+
export const literals = {
|
|
1981
|
+
true: true,
|
|
1982
|
+
false: false,
|
|
1983
|
+
null: null,
|
|
1984
|
+
undefined,
|
|
1985
|
+
};
|
|
1986
|
+
|
|
1987
|
+
/**
|
|
1988
|
+
* @ngdoc provider
|
|
1989
|
+
* @name $parseProvider
|
|
1990
|
+
*
|
|
1991
|
+
*
|
|
1992
|
+
* @description
|
|
1993
|
+
* `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse}
|
|
1994
|
+
* service.
|
|
1995
|
+
*/
|
|
1996
|
+
export function $ParseProvider() {
|
|
1997
|
+
const cache = createMap();
|
|
1998
|
+
const literals = {
|
|
1999
|
+
true: true,
|
|
2000
|
+
false: false,
|
|
2001
|
+
null: null,
|
|
2002
|
+
undefined: undefined,
|
|
2003
|
+
};
|
|
2004
|
+
var identStart, identContinue;
|
|
2005
|
+
|
|
2006
|
+
/**
|
|
2007
|
+
* @ngdoc method
|
|
2008
|
+
* @name $parseProvider#addLiteral
|
|
2009
|
+
* @description
|
|
2010
|
+
*
|
|
2011
|
+
* Configure $parse service to add literal values that will be present as literal at expressions.
|
|
2012
|
+
*
|
|
2013
|
+
* @param {string} literalName Token for the literal value. The literal name value must be a valid literal name.
|
|
2014
|
+
* @param {*} literalValue Value for this literal. All literal values must be primitives or `undefined`.
|
|
2015
|
+
*
|
|
2016
|
+
**/
|
|
2017
|
+
this.addLiteral = function (literalName, literalValue) {
|
|
2018
|
+
literals[literalName] = literalValue;
|
|
2019
|
+
};
|
|
2020
|
+
|
|
2021
|
+
/**
|
|
2022
|
+
* @ngdoc method
|
|
2023
|
+
* @name $parseProvider#setIdentifierFns
|
|
2024
|
+
*
|
|
2025
|
+
* @description
|
|
2026
|
+
*
|
|
2027
|
+
* Allows defining the set of characters that are allowed in AngularJS expressions. The function
|
|
2028
|
+
* `identifierStart` will get called to know if a given character is a valid character to be the
|
|
2029
|
+
* first character for an identifier. The function `identifierContinue` will get called to know if
|
|
2030
|
+
* a given character is a valid character to be a follow-up identifier character. The functions
|
|
2031
|
+
* `identifierStart` and `identifierContinue` will receive as arguments the single character to be
|
|
2032
|
+
* identifier and the character code point. These arguments will be `string` and `numeric`. Keep in
|
|
2033
|
+
* mind that the `string` parameter can be two characters long depending on the character
|
|
2034
|
+
* representation. It is expected for the function to return `true` or `false`, whether that
|
|
2035
|
+
* character is allowed or not.
|
|
2036
|
+
*
|
|
2037
|
+
* Since this function will be called extensively, keep the implementation of these functions fast,
|
|
2038
|
+
* as the performance of these functions have a direct impact on the expressions parsing speed.
|
|
2039
|
+
*
|
|
2040
|
+
* @param {function=} identifierStart The function that will decide whether the given character is
|
|
2041
|
+
* a valid identifier start character.
|
|
2042
|
+
* @param {function=} identifierContinue The function that will decide whether the given character is
|
|
2043
|
+
* a valid identifier continue character.
|
|
2044
|
+
*/
|
|
2045
|
+
this.setIdentifierFns = function (identifierStart, identifierContinue) {
|
|
2046
|
+
identStart = identifierStart;
|
|
2047
|
+
identContinue = identifierContinue;
|
|
2048
|
+
return this;
|
|
2049
|
+
};
|
|
2050
|
+
|
|
2051
|
+
this.$get = [
|
|
2052
|
+
"$filter",
|
|
2053
|
+
function ($filter) {
|
|
2054
|
+
var noUnsafeEval = csp().noUnsafeEval;
|
|
2055
|
+
var $parseOptions = {
|
|
2056
|
+
csp: noUnsafeEval,
|
|
2057
|
+
literals: structuredClone(literals),
|
|
2058
|
+
isIdentifierStart: isFunction(identStart) && identStart,
|
|
2059
|
+
isIdentifierContinue: isFunction(identContinue) && identContinue,
|
|
2060
|
+
};
|
|
2061
|
+
$parse.$$getAst = $$getAst;
|
|
2062
|
+
return $parse;
|
|
2063
|
+
|
|
2064
|
+
function $parse(exp, interceptorFn) {
|
|
2065
|
+
var parsedExpression, cacheKey;
|
|
2066
|
+
|
|
2067
|
+
switch (typeof exp) {
|
|
2068
|
+
case "string":
|
|
2069
|
+
exp = exp.trim();
|
|
2070
|
+
cacheKey = exp;
|
|
2071
|
+
|
|
2072
|
+
parsedExpression = cache[cacheKey];
|
|
2073
|
+
|
|
2074
|
+
if (!parsedExpression) {
|
|
2075
|
+
var lexer = new Lexer($parseOptions);
|
|
2076
|
+
var parser = new Parser(lexer, $filter, $parseOptions);
|
|
2077
|
+
parsedExpression = parser.parse(exp);
|
|
2078
|
+
|
|
2079
|
+
cache[cacheKey] = addWatchDelegate(parsedExpression);
|
|
2080
|
+
}
|
|
2081
|
+
return addInterceptor(parsedExpression, interceptorFn);
|
|
2082
|
+
|
|
2083
|
+
case "function":
|
|
2084
|
+
return addInterceptor(exp, interceptorFn);
|
|
2085
|
+
|
|
2086
|
+
default:
|
|
2087
|
+
return addInterceptor(() => {}, interceptorFn);
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
function $$getAst(exp) {
|
|
2092
|
+
var lexer = new Lexer($parseOptions);
|
|
2093
|
+
var parser = new Parser(lexer, $filter, $parseOptions);
|
|
2094
|
+
return parser.getAst(exp).ast;
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
function expressionInputDirtyCheck(
|
|
2098
|
+
newValue,
|
|
2099
|
+
oldValueOfValue,
|
|
2100
|
+
compareObjectIdentity,
|
|
2101
|
+
) {
|
|
2102
|
+
if (newValue == null || oldValueOfValue == null) {
|
|
2103
|
+
// null/undefined
|
|
2104
|
+
return newValue === oldValueOfValue;
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
if (typeof newValue === "object") {
|
|
2108
|
+
// attempt to convert the value to a primitive type
|
|
2109
|
+
// TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can
|
|
2110
|
+
// be cheaply dirty-checked
|
|
2111
|
+
newValue = getValueOf(newValue);
|
|
2112
|
+
|
|
2113
|
+
if (typeof newValue === "object" && !compareObjectIdentity) {
|
|
2114
|
+
// objects/arrays are not supported - deep-watching them would be too expensive
|
|
2115
|
+
return false;
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
// fall-through to the primitive equality check
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
//Primitive or NaN
|
|
2122
|
+
// eslint-disable-next-line no-self-compare
|
|
2123
|
+
return (
|
|
2124
|
+
newValue === oldValueOfValue ||
|
|
2125
|
+
(newValue !== newValue && oldValueOfValue !== oldValueOfValue)
|
|
2126
|
+
);
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
function inputsWatchDelegate(
|
|
2130
|
+
scope,
|
|
2131
|
+
listener,
|
|
2132
|
+
objectEquality,
|
|
2133
|
+
parsedExpression,
|
|
2134
|
+
) {
|
|
2135
|
+
var inputExpressions = parsedExpression.inputs;
|
|
2136
|
+
var lastResult;
|
|
2137
|
+
|
|
2138
|
+
if (inputExpressions.length === 1) {
|
|
2139
|
+
var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails
|
|
2140
|
+
inputExpressions = inputExpressions[0];
|
|
2141
|
+
return scope.$watch(
|
|
2142
|
+
function expressionInputWatch(scope) {
|
|
2143
|
+
var newInputValue = inputExpressions(scope);
|
|
2144
|
+
if (
|
|
2145
|
+
!expressionInputDirtyCheck(
|
|
2146
|
+
newInputValue,
|
|
2147
|
+
oldInputValueOf,
|
|
2148
|
+
inputExpressions.isPure,
|
|
2149
|
+
)
|
|
2150
|
+
) {
|
|
2151
|
+
lastResult = parsedExpression(scope, undefined, undefined, [
|
|
2152
|
+
newInputValue,
|
|
2153
|
+
]);
|
|
2154
|
+
oldInputValueOf = newInputValue && getValueOf(newInputValue);
|
|
2155
|
+
}
|
|
2156
|
+
return lastResult;
|
|
2157
|
+
},
|
|
2158
|
+
listener,
|
|
2159
|
+
objectEquality,
|
|
2160
|
+
);
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
var oldInputValueOfValues = [];
|
|
2164
|
+
var oldInputValues = [];
|
|
2165
|
+
for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
|
|
2166
|
+
oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails
|
|
2167
|
+
oldInputValues[i] = null;
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
return scope.$watch(
|
|
2171
|
+
function expressionInputsWatch(scope) {
|
|
2172
|
+
var changed = false;
|
|
2173
|
+
|
|
2174
|
+
for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
|
|
2175
|
+
var newInputValue = inputExpressions[i](scope);
|
|
2176
|
+
if (
|
|
2177
|
+
changed ||
|
|
2178
|
+
(changed = !expressionInputDirtyCheck(
|
|
2179
|
+
newInputValue,
|
|
2180
|
+
oldInputValueOfValues[i],
|
|
2181
|
+
inputExpressions[i].isPure,
|
|
2182
|
+
))
|
|
2183
|
+
) {
|
|
2184
|
+
oldInputValues[i] = newInputValue;
|
|
2185
|
+
oldInputValueOfValues[i] =
|
|
2186
|
+
newInputValue && getValueOf(newInputValue);
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
if (changed) {
|
|
2191
|
+
lastResult = parsedExpression(
|
|
2192
|
+
scope,
|
|
2193
|
+
undefined,
|
|
2194
|
+
undefined,
|
|
2195
|
+
oldInputValues,
|
|
2196
|
+
);
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
return lastResult;
|
|
2200
|
+
},
|
|
2201
|
+
listener,
|
|
2202
|
+
objectEquality,
|
|
2203
|
+
);
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
function oneTimeWatchDelegate(
|
|
2207
|
+
scope,
|
|
2208
|
+
listener,
|
|
2209
|
+
objectEquality,
|
|
2210
|
+
parsedExpression,
|
|
2211
|
+
) {
|
|
2212
|
+
var isDone = parsedExpression.literal ? isAllDefined : isDefined;
|
|
2213
|
+
var unwatch, lastValue;
|
|
2214
|
+
|
|
2215
|
+
var exp = parsedExpression.$$intercepted || parsedExpression;
|
|
2216
|
+
var post = parsedExpression.$$interceptor || identity;
|
|
2217
|
+
|
|
2218
|
+
var useInputs = parsedExpression.inputs && !exp.inputs;
|
|
2219
|
+
|
|
2220
|
+
// Propagate the literal/inputs/constant attributes
|
|
2221
|
+
// ... but not oneTime since we are handling it
|
|
2222
|
+
oneTimeWatch.literal = parsedExpression.literal;
|
|
2223
|
+
oneTimeWatch.constant = parsedExpression.constant;
|
|
2224
|
+
oneTimeWatch.inputs = parsedExpression.inputs;
|
|
2225
|
+
|
|
2226
|
+
// Allow other delegates to run on this wrapped expression
|
|
2227
|
+
addWatchDelegate(oneTimeWatch);
|
|
2228
|
+
|
|
2229
|
+
unwatch = scope.$watch(oneTimeWatch, listener, objectEquality);
|
|
2230
|
+
|
|
2231
|
+
return unwatch;
|
|
2232
|
+
|
|
2233
|
+
function unwatchIfDone() {
|
|
2234
|
+
if (isDone(lastValue)) {
|
|
2235
|
+
unwatch();
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
function oneTimeWatch(scope, locals, assign, inputs) {
|
|
2240
|
+
lastValue =
|
|
2241
|
+
useInputs && inputs
|
|
2242
|
+
? inputs[0]
|
|
2243
|
+
: exp(scope, locals, assign, inputs);
|
|
2244
|
+
if (isDone(lastValue)) {
|
|
2245
|
+
scope.$$postDigest(unwatchIfDone);
|
|
2246
|
+
}
|
|
2247
|
+
return post(lastValue);
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
function isAllDefined(value) {
|
|
2252
|
+
var allDefined = true;
|
|
2253
|
+
forEach(value, function (val) {
|
|
2254
|
+
if (!isDefined(val)) allDefined = false;
|
|
2255
|
+
});
|
|
2256
|
+
return allDefined;
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
function constantWatchDelegate(
|
|
2260
|
+
scope,
|
|
2261
|
+
listener,
|
|
2262
|
+
objectEquality,
|
|
2263
|
+
parsedExpression,
|
|
2264
|
+
) {
|
|
2265
|
+
var unwatch = scope.$watch(
|
|
2266
|
+
function constantWatch(scope) {
|
|
2267
|
+
unwatch();
|
|
2268
|
+
return parsedExpression(scope);
|
|
2269
|
+
},
|
|
2270
|
+
listener,
|
|
2271
|
+
objectEquality,
|
|
2272
|
+
);
|
|
2273
|
+
return unwatch;
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
function addWatchDelegate(parsedExpression) {
|
|
2277
|
+
if (parsedExpression.constant) {
|
|
2278
|
+
parsedExpression.$$watchDelegate = constantWatchDelegate;
|
|
2279
|
+
} else if (parsedExpression.oneTime) {
|
|
2280
|
+
parsedExpression.$$watchDelegate = oneTimeWatchDelegate;
|
|
2281
|
+
} else if (parsedExpression.inputs) {
|
|
2282
|
+
parsedExpression.$$watchDelegate = inputsWatchDelegate;
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
return parsedExpression;
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
function chainInterceptors(first, second) {
|
|
2289
|
+
function chainedInterceptor(value) {
|
|
2290
|
+
return second(first(value));
|
|
2291
|
+
}
|
|
2292
|
+
chainedInterceptor.$stateful = first.$stateful || second.$stateful;
|
|
2293
|
+
chainedInterceptor.$$pure = first.$$pure && second.$$pure;
|
|
2294
|
+
|
|
2295
|
+
return chainedInterceptor;
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
function addInterceptor(parsedExpression, interceptorFn) {
|
|
2299
|
+
if (!interceptorFn) return parsedExpression;
|
|
2300
|
+
|
|
2301
|
+
// Extract any existing interceptors out of the parsedExpression
|
|
2302
|
+
// to ensure the original parsedExpression is always the $$intercepted
|
|
2303
|
+
if (parsedExpression.$$interceptor) {
|
|
2304
|
+
interceptorFn = chainInterceptors(
|
|
2305
|
+
parsedExpression.$$interceptor,
|
|
2306
|
+
interceptorFn,
|
|
2307
|
+
);
|
|
2308
|
+
parsedExpression = parsedExpression.$$intercepted;
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
var useInputs = false;
|
|
2312
|
+
|
|
2313
|
+
var fn = function interceptedExpression(scope, locals, assign, inputs) {
|
|
2314
|
+
var value =
|
|
2315
|
+
useInputs && inputs
|
|
2316
|
+
? inputs[0]
|
|
2317
|
+
: parsedExpression(scope, locals, assign, inputs);
|
|
2318
|
+
return interceptorFn(value);
|
|
2319
|
+
};
|
|
2320
|
+
|
|
2321
|
+
// Maintain references to the interceptor/intercepted
|
|
2322
|
+
fn.$$intercepted = parsedExpression;
|
|
2323
|
+
fn.$$interceptor = interceptorFn;
|
|
2324
|
+
|
|
2325
|
+
// Propagate the literal/oneTime/constant attributes
|
|
2326
|
+
fn.literal = parsedExpression.literal;
|
|
2327
|
+
fn.oneTime = parsedExpression.oneTime;
|
|
2328
|
+
fn.constant = parsedExpression.constant;
|
|
2329
|
+
|
|
2330
|
+
// Treat the interceptor like filters.
|
|
2331
|
+
// If it is not $stateful then only watch its inputs.
|
|
2332
|
+
// If the expression itself has no inputs then use the full expression as an input.
|
|
2333
|
+
if (!interceptorFn.$stateful) {
|
|
2334
|
+
useInputs = !parsedExpression.inputs;
|
|
2335
|
+
fn.inputs = parsedExpression.inputs
|
|
2336
|
+
? parsedExpression.inputs
|
|
2337
|
+
: [parsedExpression];
|
|
2338
|
+
|
|
2339
|
+
if (!interceptorFn.$$pure) {
|
|
2340
|
+
fn.inputs = fn.inputs.map(function (e) {
|
|
2341
|
+
// Remove the isPure flag of inputs when it is not absolute because they are now wrapped in a
|
|
2342
|
+
// non-pure interceptor function.
|
|
2343
|
+
if (e.isPure === PURITY_RELATIVE) {
|
|
2344
|
+
return function depurifier(s) {
|
|
2345
|
+
return e(s);
|
|
2346
|
+
};
|
|
2347
|
+
}
|
|
2348
|
+
return e;
|
|
2349
|
+
});
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
return addWatchDelegate(fn);
|
|
2354
|
+
}
|
|
2355
|
+
},
|
|
2356
|
+
];
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
function constantWatchDelegate(
|
|
2360
|
+
scope,
|
|
2361
|
+
listener,
|
|
2362
|
+
objectEquality,
|
|
2363
|
+
parsedExpression,
|
|
2364
|
+
) {
|
|
2365
|
+
const unwatch = scope.$watch(
|
|
2366
|
+
($scope) => {
|
|
2367
|
+
unwatch();
|
|
2368
|
+
return parsedExpression($scope);
|
|
2369
|
+
},
|
|
2370
|
+
listener,
|
|
2371
|
+
objectEquality,
|
|
2372
|
+
);
|
|
2373
|
+
return unwatch;
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
function addWatchDelegate(parsedExpression) {
|
|
2377
|
+
if (parsedExpression.constant) {
|
|
2378
|
+
parsedExpression.$$watchDelegate = constantWatchDelegate;
|
|
2379
|
+
} else if (parsedExpression.oneTime) {
|
|
2380
|
+
parsedExpression.$$watchDelegate = oneTimeWatchDelegate;
|
|
2381
|
+
} else if (parsedExpression.inputs) {
|
|
2382
|
+
parsedExpression.$$watchDelegate = inputsWatchDelegate;
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
return parsedExpression;
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
export function inputsWatchDelegate(
|
|
2389
|
+
scope,
|
|
2390
|
+
listener,
|
|
2391
|
+
objectEquality,
|
|
2392
|
+
parsedExpression,
|
|
2393
|
+
) {
|
|
2394
|
+
let inputExpressions = parsedExpression.inputs;
|
|
2395
|
+
let lastResult;
|
|
2396
|
+
|
|
2397
|
+
if (inputExpressions.length === 1) {
|
|
2398
|
+
let oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails
|
|
2399
|
+
// eslint-disable-next-line prefer-destructuring
|
|
2400
|
+
inputExpressions = inputExpressions[0];
|
|
2401
|
+
return scope.$watch(
|
|
2402
|
+
($scope) => {
|
|
2403
|
+
const newInputValue = inputExpressions($scope);
|
|
2404
|
+
if (
|
|
2405
|
+
!expressionInputDirtyCheck(
|
|
2406
|
+
newInputValue,
|
|
2407
|
+
oldInputValueOf,
|
|
2408
|
+
inputExpressions.isPure,
|
|
2409
|
+
)
|
|
2410
|
+
) {
|
|
2411
|
+
lastResult = parsedExpression($scope, undefined, undefined, [
|
|
2412
|
+
newInputValue,
|
|
2413
|
+
]);
|
|
2414
|
+
oldInputValueOf = newInputValue && getValueOf(newInputValue);
|
|
2415
|
+
}
|
|
2416
|
+
return lastResult;
|
|
2417
|
+
},
|
|
2418
|
+
listener,
|
|
2419
|
+
objectEquality,
|
|
2420
|
+
);
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
const oldInputValueOfValues = [];
|
|
2424
|
+
const oldInputValues = [];
|
|
2425
|
+
for (let i = 0, ii = inputExpressions.length; i < ii; i++) {
|
|
2426
|
+
oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails
|
|
2427
|
+
oldInputValues[i] = null;
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
return scope.$watch(
|
|
2431
|
+
(scope) => {
|
|
2432
|
+
let changed = false;
|
|
2433
|
+
|
|
2434
|
+
// eslint-disable-next-line no-plusplus
|
|
2435
|
+
for (let i = 0, ii = inputExpressions.length; i < ii; i++) {
|
|
2436
|
+
const newInputValue = inputExpressions[i](scope);
|
|
2437
|
+
if (
|
|
2438
|
+
changed ||
|
|
2439
|
+
// eslint-disable-next-line no-cond-assign
|
|
2440
|
+
(changed = !expressionInputDirtyCheck(
|
|
2441
|
+
newInputValue,
|
|
2442
|
+
oldInputValueOfValues[i],
|
|
2443
|
+
inputExpressions[i].isPure,
|
|
2444
|
+
))
|
|
2445
|
+
) {
|
|
2446
|
+
oldInputValues[i] = newInputValue;
|
|
2447
|
+
oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue);
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
if (changed) {
|
|
2452
|
+
lastResult = parsedExpression(
|
|
2453
|
+
scope,
|
|
2454
|
+
undefined,
|
|
2455
|
+
undefined,
|
|
2456
|
+
oldInputValues,
|
|
2457
|
+
);
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
return lastResult;
|
|
2461
|
+
},
|
|
2462
|
+
listener,
|
|
2463
|
+
objectEquality,
|
|
2464
|
+
);
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
export function oneTimeWatchDelegate(
|
|
2468
|
+
scope,
|
|
2469
|
+
listener,
|
|
2470
|
+
objectEquality,
|
|
2471
|
+
parsedExpression,
|
|
2472
|
+
) {
|
|
2473
|
+
const isDone = parsedExpression.literal ? isAllDefined : isDefined;
|
|
2474
|
+
|
|
2475
|
+
let unwatch;
|
|
2476
|
+
let lastValue;
|
|
2477
|
+
|
|
2478
|
+
const exp = parsedExpression.$$intercepted || parsedExpression;
|
|
2479
|
+
const post = parsedExpression.$$interceptor || identity;
|
|
2480
|
+
|
|
2481
|
+
const useInputs = parsedExpression.inputs && !exp.inputs;
|
|
2482
|
+
|
|
2483
|
+
// Propagate the literal/inputs/constant attributes
|
|
2484
|
+
// ... but not oneTime since we are handling it
|
|
2485
|
+
oneTimeWatch.literal = parsedExpression.literal;
|
|
2486
|
+
oneTimeWatch.constant = parsedExpression.constant;
|
|
2487
|
+
oneTimeWatch.inputs = parsedExpression.inputs;
|
|
2488
|
+
|
|
2489
|
+
// Allow other delegates to run on this wrapped expression
|
|
2490
|
+
addWatchDelegate(oneTimeWatch);
|
|
2491
|
+
|
|
2492
|
+
function unwatchIfDone() {
|
|
2493
|
+
if (isDone(lastValue)) {
|
|
2494
|
+
unwatch();
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
function oneTimeWatch(scope, locals, assign, inputs) {
|
|
2499
|
+
lastValue =
|
|
2500
|
+
useInputs && inputs ? inputs[0] : exp(scope, locals, assign, inputs);
|
|
2501
|
+
if (isDone(lastValue)) {
|
|
2502
|
+
scope.$$postDigest(unwatchIfDone);
|
|
2503
|
+
}
|
|
2504
|
+
return post(lastValue);
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
unwatch = scope.$watch(oneTimeWatch, listener, objectEquality);
|
|
2508
|
+
|
|
2509
|
+
return unwatch;
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
export function chainInterceptors(first, second) {
|
|
2513
|
+
function chainedInterceptor(value) {
|
|
2514
|
+
return second(first(value));
|
|
2515
|
+
}
|
|
2516
|
+
chainedInterceptor.$stateful = first.$stateful || second.$stateful;
|
|
2517
|
+
chainedInterceptor.$$pure = first.$$pure && second.$$pure;
|
|
2518
|
+
|
|
2519
|
+
return chainedInterceptor;
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
export function expressionInputDirtyCheck(
|
|
2523
|
+
newValue,
|
|
2524
|
+
oldValueOfValue,
|
|
2525
|
+
compareObjectIdentity,
|
|
2526
|
+
) {
|
|
2527
|
+
if (newValue == null || oldValueOfValue == null) {
|
|
2528
|
+
// null/undefined
|
|
2529
|
+
return newValue === oldValueOfValue;
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
if (typeof newValue === "object") {
|
|
2533
|
+
// attempt to convert the value to a primitive type
|
|
2534
|
+
// TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can
|
|
2535
|
+
// be cheaply dirty-checked
|
|
2536
|
+
newValue = getValueOf(newValue);
|
|
2537
|
+
|
|
2538
|
+
if (typeof newValue === "object" && !compareObjectIdentity) {
|
|
2539
|
+
// objects/arrays are not supported - deep-watching them would be too expensive
|
|
2540
|
+
return false;
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
// fall-through to the primitive equality check
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
// Primitive or NaN
|
|
2547
|
+
// eslint-disable-next-line no-self-compare
|
|
2548
|
+
return (
|
|
2549
|
+
newValue === oldValueOfValue ||
|
|
2550
|
+
// eslint-disable-next-line no-self-compare
|
|
2551
|
+
(newValue !== newValue && oldValueOfValue !== oldValueOfValue)
|
|
2552
|
+
);
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
// eslint-disable-next-line class-methods-use-this
|
|
2556
|
+
export function isAllDefined(value) {
|
|
2557
|
+
let allDefined = true;
|
|
2558
|
+
forEach(value, (val) => {
|
|
2559
|
+
if (!isDefined(val)) allDefined = false;
|
|
2560
|
+
});
|
|
2561
|
+
return allDefined;
|
|
2562
|
+
}
|