haori 0.6.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +1 -1
- package/README.md +1 -1
- package/dist/haori.cjs.js +14 -14
- package/dist/haori.cjs.js.map +1 -1
- package/dist/haori.es.js +2060 -1505
- package/dist/haori.es.js.map +1 -1
- package/dist/haori.iife.js +14 -14
- package/dist/haori.iife.js.map +1 -1
- package/dist/index.d.ts +95 -0
- package/dist/package.json +1 -1
- package/dist/src/core.d.ts +67 -0
- package/dist/src/core.d.ts.map +1 -1
- package/dist/src/core.js +274 -10
- package/dist/src/core.js.map +1 -1
- package/dist/src/expression.d.ts +32 -0
- package/dist/src/expression.d.ts.map +1 -1
- package/dist/src/expression.js +106 -26
- package/dist/src/expression.js.map +1 -1
- package/dist/src/fragment.d.ts +28 -0
- package/dist/src/fragment.d.ts.map +1 -1
- package/dist/src/fragment.js +356 -42
- package/dist/src/fragment.js.map +1 -1
- package/dist/tests/data-derive.test.js +100 -0
- package/dist/tests/data-derive.test.js.map +1 -1
- package/dist/tests/evaluation-profile.test.d.ts +2 -0
- package/dist/tests/evaluation-profile.test.d.ts.map +1 -0
- package/dist/tests/evaluation-profile.test.js +92 -0
- package/dist/tests/evaluation-profile.test.js.map +1 -0
- package/dist/tests/expression.test.js +4 -0
- package/dist/tests/expression.test.js.map +1 -1
- package/package.json +1 -1
package/dist/src/fragment.js
CHANGED
|
@@ -8,6 +8,253 @@ import Queue from './queue';
|
|
|
8
8
|
import Log from './log';
|
|
9
9
|
import Expression from './expression';
|
|
10
10
|
import Env from './env';
|
|
11
|
+
import Dev from './dev';
|
|
12
|
+
/**
|
|
13
|
+
* 開発時の属性・テキスト評価回数を集計するレジストリです。
|
|
14
|
+
*/
|
|
15
|
+
class EvaluationProfileRegistry {
|
|
16
|
+
/**
|
|
17
|
+
* 集計状態を初期化します。
|
|
18
|
+
*/
|
|
19
|
+
static reset() {
|
|
20
|
+
EvaluationProfileRegistry.ELEMENT_STORES.clear();
|
|
21
|
+
EvaluationProfileRegistry.ensureGlobalAccess();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 現在の集計結果スナップショットを返します。
|
|
25
|
+
*
|
|
26
|
+
* @returns エレメントごとの集計結果
|
|
27
|
+
*/
|
|
28
|
+
static snapshot() {
|
|
29
|
+
EvaluationProfileRegistry.ensureGlobalAccess();
|
|
30
|
+
return [...EvaluationProfileRegistry.ELEMENT_STORES.entries()]
|
|
31
|
+
.map(([elementId, store]) => ({
|
|
32
|
+
elementId,
|
|
33
|
+
tagName: store.tagName,
|
|
34
|
+
attributes: [...store.attributes.entries()]
|
|
35
|
+
.map(([name, counter]) => ({
|
|
36
|
+
name,
|
|
37
|
+
template: counter.template,
|
|
38
|
+
calls: counter.calls,
|
|
39
|
+
totalDurationMs: counter.totalDurationMs,
|
|
40
|
+
maxDurationMs: counter.maxDurationMs,
|
|
41
|
+
placeholders: EvaluationProfileRegistry.sortPlaceholders(counter.placeholders),
|
|
42
|
+
}))
|
|
43
|
+
.sort((left, right) => right.calls - left.calls),
|
|
44
|
+
texts: [...store.texts.entries()]
|
|
45
|
+
.map(([childIndex, counter]) => ({
|
|
46
|
+
childIndex: Number(childIndex),
|
|
47
|
+
template: counter.template,
|
|
48
|
+
calls: counter.calls,
|
|
49
|
+
totalDurationMs: counter.totalDurationMs,
|
|
50
|
+
maxDurationMs: counter.maxDurationMs,
|
|
51
|
+
placeholders: EvaluationProfileRegistry.sortPlaceholders(counter.placeholders),
|
|
52
|
+
}))
|
|
53
|
+
.sort((left, right) => right.calls - left.calls),
|
|
54
|
+
}))
|
|
55
|
+
.sort((left, right) => {
|
|
56
|
+
const leftCalls = left.attributes.reduce((sum, item) => sum + item.calls, 0) +
|
|
57
|
+
left.texts.reduce((sum, item) => sum + item.calls, 0);
|
|
58
|
+
const rightCalls = right.attributes.reduce((sum, item) => sum + item.calls, 0) +
|
|
59
|
+
right.texts.reduce((sum, item) => sum + item.calls, 0);
|
|
60
|
+
return rightCalls - leftCalls;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 評価呼び出しを記録します。
|
|
65
|
+
*
|
|
66
|
+
* @param context 評価コンテキスト
|
|
67
|
+
* @param expressions 今回評価した式一覧
|
|
68
|
+
*/
|
|
69
|
+
static record(context, expressions, totalDurationMs) {
|
|
70
|
+
if (!Dev.isEnabled() || !context || expressions.length === 0) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
EvaluationProfileRegistry.ensureGlobalAccess();
|
|
74
|
+
const store = EvaluationProfileRegistry.getOrCreateElementStore(context.element);
|
|
75
|
+
if (context.kind === 'attribute') {
|
|
76
|
+
const counter = EvaluationProfileRegistry.getOrCreateCounter(store.attributes, context.rawName, context.template);
|
|
77
|
+
EvaluationProfileRegistry.updateCounter(counter, expressions, totalDurationMs);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const counter = EvaluationProfileRegistry.getOrCreateCounter(store.texts, String(context.childIndex), context.template);
|
|
81
|
+
EvaluationProfileRegistry.updateCounter(counter, expressions, totalDurationMs);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* globalThis から dev-only の取得窓口を参照できるようにします。
|
|
85
|
+
*/
|
|
86
|
+
static ensureGlobalAccess() {
|
|
87
|
+
if (!Dev.isEnabled()) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const globalRecord = globalThis;
|
|
91
|
+
if (globalRecord[EvaluationProfileRegistry.GLOBAL_KEY] !== undefined) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
globalRecord[EvaluationProfileRegistry.GLOBAL_KEY] = {
|
|
95
|
+
reset: () => EvaluationProfileRegistry.reset(),
|
|
96
|
+
snapshot: () => EvaluationProfileRegistry.snapshot(),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* エレメント単位の集計ストアを取得または初期化します。
|
|
101
|
+
*
|
|
102
|
+
* @param element 対象エレメント
|
|
103
|
+
* @returns 集計ストア
|
|
104
|
+
*/
|
|
105
|
+
static getOrCreateElementStore(element) {
|
|
106
|
+
const elementId = EvaluationProfileRegistry.createElementId(element);
|
|
107
|
+
const existing = EvaluationProfileRegistry.ELEMENT_STORES.get(elementId);
|
|
108
|
+
if (existing) {
|
|
109
|
+
return existing;
|
|
110
|
+
}
|
|
111
|
+
const store = {
|
|
112
|
+
tagName: element.tagName.toLowerCase(),
|
|
113
|
+
attributes: new Map(),
|
|
114
|
+
texts: new Map(),
|
|
115
|
+
};
|
|
116
|
+
EvaluationProfileRegistry.ELEMENT_STORES.set(elementId, store);
|
|
117
|
+
return store;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* カウンタを取得または初期化します。
|
|
121
|
+
*
|
|
122
|
+
* @param counters 種別ごとのカウンタマップ
|
|
123
|
+
* @param key カウンタキー
|
|
124
|
+
* @param template 元テンプレート
|
|
125
|
+
* @returns カウンタ
|
|
126
|
+
*/
|
|
127
|
+
static getOrCreateCounter(counters, key, template) {
|
|
128
|
+
const existing = counters.get(key);
|
|
129
|
+
if (existing) {
|
|
130
|
+
return existing;
|
|
131
|
+
}
|
|
132
|
+
const counter = {
|
|
133
|
+
template,
|
|
134
|
+
calls: 0,
|
|
135
|
+
totalDurationMs: 0,
|
|
136
|
+
maxDurationMs: 0,
|
|
137
|
+
placeholders: new Map(),
|
|
138
|
+
};
|
|
139
|
+
counters.set(key, counter);
|
|
140
|
+
return counter;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* プレースホルダカウンタを取得または初期化します。
|
|
144
|
+
*
|
|
145
|
+
* @param placeholders プレースホルダカウンタマップ
|
|
146
|
+
* @param expression 式文字列
|
|
147
|
+
* @returns プレースホルダカウンタ
|
|
148
|
+
*/
|
|
149
|
+
static getOrCreatePlaceholder(placeholders, expression) {
|
|
150
|
+
const existing = placeholders.get(expression);
|
|
151
|
+
if (existing) {
|
|
152
|
+
return existing;
|
|
153
|
+
}
|
|
154
|
+
const counter = {
|
|
155
|
+
calls: 0,
|
|
156
|
+
totalDurationMs: 0,
|
|
157
|
+
maxDurationMs: 0,
|
|
158
|
+
};
|
|
159
|
+
placeholders.set(expression, counter);
|
|
160
|
+
return counter;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* カウンタへ今回の評価結果を加算します。
|
|
164
|
+
*
|
|
165
|
+
* @param counter 更新対象カウンタ
|
|
166
|
+
* @param expressions 今回評価した式一覧
|
|
167
|
+
* @param totalDurationMs 今回の総所要時間
|
|
168
|
+
*/
|
|
169
|
+
static updateCounter(counter, expressions, totalDurationMs) {
|
|
170
|
+
counter.calls += 1;
|
|
171
|
+
counter.totalDurationMs += totalDurationMs;
|
|
172
|
+
counter.maxDurationMs = Math.max(counter.maxDurationMs, totalDurationMs);
|
|
173
|
+
expressions.forEach(expression => {
|
|
174
|
+
const placeholder = EvaluationProfileRegistry.getOrCreatePlaceholder(counter.placeholders, expression.expression);
|
|
175
|
+
placeholder.calls += 1;
|
|
176
|
+
placeholder.totalDurationMs += expression.durationMs;
|
|
177
|
+
placeholder.maxDurationMs = Math.max(placeholder.maxDurationMs, expression.durationMs);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* プレースホルダ集計を calls 降順で返します。
|
|
182
|
+
*
|
|
183
|
+
* @param placeholders プレースホルダ集計
|
|
184
|
+
* @returns スナップショット
|
|
185
|
+
*/
|
|
186
|
+
static sortPlaceholders(placeholders) {
|
|
187
|
+
return [...placeholders.entries()]
|
|
188
|
+
.map(([expression, counter]) => ({
|
|
189
|
+
expression,
|
|
190
|
+
calls: counter.calls,
|
|
191
|
+
totalDurationMs: counter.totalDurationMs,
|
|
192
|
+
maxDurationMs: counter.maxDurationMs,
|
|
193
|
+
}))
|
|
194
|
+
.sort((left, right) => {
|
|
195
|
+
if (right.calls !== left.calls) {
|
|
196
|
+
return right.calls - left.calls;
|
|
197
|
+
}
|
|
198
|
+
return right.totalDurationMs - left.totalDurationMs;
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* 現在時刻のタイムスタンプを返します。
|
|
203
|
+
*
|
|
204
|
+
* @returns ミリ秒
|
|
205
|
+
*/
|
|
206
|
+
static now() {
|
|
207
|
+
return globalThis.performance?.now() ?? Date.now();
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* 評価処理の所要時間を計測します。
|
|
211
|
+
*
|
|
212
|
+
* @param callback 計測対象
|
|
213
|
+
* @returns 結果と所要時間
|
|
214
|
+
*/
|
|
215
|
+
static measure(callback) {
|
|
216
|
+
const startedAt = EvaluationProfileRegistry.now();
|
|
217
|
+
const value = callback();
|
|
218
|
+
return {
|
|
219
|
+
value,
|
|
220
|
+
durationMs: EvaluationProfileRegistry.now() - startedAt,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* エレメント識別子を生成します。
|
|
225
|
+
*
|
|
226
|
+
* @param element 対象エレメント
|
|
227
|
+
* @returns 識別子
|
|
228
|
+
*/
|
|
229
|
+
static createElementId(element) {
|
|
230
|
+
const segments = [];
|
|
231
|
+
let current = element;
|
|
232
|
+
while (current) {
|
|
233
|
+
let segment = current.tagName.toLowerCase();
|
|
234
|
+
const rawId = current.getAttribute('id') || '';
|
|
235
|
+
if (rawId.trim() !== '') {
|
|
236
|
+
segment += `#${rawId.trim()}`;
|
|
237
|
+
segments.unshift(segment);
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
const deriveName = current.getAttribute(`${Env.prefix}derive-name`);
|
|
241
|
+
if (deriveName && deriveName.trim() !== '') {
|
|
242
|
+
segment += `[${Env.prefix}derive-name="${deriveName.trim()}"]`;
|
|
243
|
+
}
|
|
244
|
+
const parent = current.parentElement;
|
|
245
|
+
if (parent) {
|
|
246
|
+
segment += `:nth-child(${[...parent.children].indexOf(current) + 1})`;
|
|
247
|
+
}
|
|
248
|
+
segments.unshift(segment);
|
|
249
|
+
current = parent;
|
|
250
|
+
}
|
|
251
|
+
return segments.join(' > ');
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/** globalThis に公開するキー */
|
|
255
|
+
EvaluationProfileRegistry.GLOBAL_KEY = '__HAORI_EVALUATION_PROFILE__';
|
|
256
|
+
/** エレメントごとの集計 */
|
|
257
|
+
EvaluationProfileRegistry.ELEMENT_STORES = new Map();
|
|
11
258
|
/**
|
|
12
259
|
* 仮想DOMのフラグメントの抽象クラス。
|
|
13
260
|
*/
|
|
@@ -237,6 +484,10 @@ export class ElementFragment extends Fragment {
|
|
|
237
484
|
this.renderSignature = null;
|
|
238
485
|
/** 直近に描画した data-each 全体の入力署名 */
|
|
239
486
|
this.eachInputSignature = null;
|
|
487
|
+
/** 直近に公開した data-derive subtree の入力署名 */
|
|
488
|
+
this.deriveSubtreeSignature = null;
|
|
489
|
+
/** 直近に評価した data-derive の入力署名 */
|
|
490
|
+
this.deriveInputSignature = null;
|
|
240
491
|
/** fresh clone 初期化を subtree ごと省略できるかどうか */
|
|
241
492
|
this.freshInitializationSkippable = false;
|
|
242
493
|
/** valueプロパティの値 */
|
|
@@ -327,6 +578,8 @@ export class ElementFragment extends Fragment {
|
|
|
327
578
|
clone.template = this.template;
|
|
328
579
|
clone.renderSignature = this.renderSignature;
|
|
329
580
|
clone.eachInputSignature = this.eachInputSignature;
|
|
581
|
+
clone.deriveSubtreeSignature = null;
|
|
582
|
+
clone.deriveInputSignature = null;
|
|
330
583
|
clone.freshInitializationSkippable = this.freshInitializationSkippable;
|
|
331
584
|
clone.normalizeClonedVisibilityState();
|
|
332
585
|
return clone;
|
|
@@ -372,6 +625,8 @@ export class ElementFragment extends Fragment {
|
|
|
372
625
|
this.template = null;
|
|
373
626
|
}
|
|
374
627
|
this.eachInputSignature = null;
|
|
628
|
+
this.deriveSubtreeSignature = null;
|
|
629
|
+
this.deriveInputSignature = null;
|
|
375
630
|
promises.push(super.remove(unmount));
|
|
376
631
|
return Promise.all(promises).then(() => undefined);
|
|
377
632
|
}
|
|
@@ -538,6 +793,38 @@ export class ElementFragment extends Fragment {
|
|
|
538
793
|
setEachInputSignature(signature) {
|
|
539
794
|
this.eachInputSignature = signature;
|
|
540
795
|
}
|
|
796
|
+
/**
|
|
797
|
+
* 直近に公開した data-derive subtree の入力署名を取得します。
|
|
798
|
+
*
|
|
799
|
+
* @returns 入力署名
|
|
800
|
+
*/
|
|
801
|
+
getDeriveSubtreeSignature() {
|
|
802
|
+
return this.deriveSubtreeSignature;
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* 直近に公開した data-derive subtree の入力署名を設定します。
|
|
806
|
+
*
|
|
807
|
+
* @param signature 入力署名
|
|
808
|
+
*/
|
|
809
|
+
setDeriveSubtreeSignature(signature) {
|
|
810
|
+
this.deriveSubtreeSignature = signature;
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* 直近に評価した data-derive の入力署名を取得します。
|
|
814
|
+
*
|
|
815
|
+
* @returns 入力署名
|
|
816
|
+
*/
|
|
817
|
+
getDeriveInputSignature() {
|
|
818
|
+
return this.deriveInputSignature;
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* 直近に評価した data-derive の入力署名を設定します。
|
|
822
|
+
*
|
|
823
|
+
* @param signature 入力署名
|
|
824
|
+
*/
|
|
825
|
+
setDeriveInputSignature(signature) {
|
|
826
|
+
this.deriveInputSignature = signature;
|
|
827
|
+
}
|
|
541
828
|
/**
|
|
542
829
|
* fresh clone 初期化を subtree ごと省略できるかどうかを返します。
|
|
543
830
|
*
|
|
@@ -785,7 +1072,12 @@ export class ElementFragment extends Fragment {
|
|
|
785
1072
|
}
|
|
786
1073
|
this.attributeMap.set(rawName, contents);
|
|
787
1074
|
const element = this.getTarget();
|
|
788
|
-
const detail = contents.evaluateDetailed(this.getBindingData()
|
|
1075
|
+
const detail = contents.evaluateDetailed(this.getBindingData(), {
|
|
1076
|
+
kind: 'attribute',
|
|
1077
|
+
element,
|
|
1078
|
+
rawName,
|
|
1079
|
+
template: value,
|
|
1080
|
+
});
|
|
789
1081
|
const hasTemplateExpression = contents.isEvaluate || contents.isRawEvaluate;
|
|
790
1082
|
const isBooleanAttribute = rawName === targetName &&
|
|
791
1083
|
ElementFragment.BOOLEAN_ATTRIBUTES.has(targetName.toLowerCase());
|
|
@@ -905,7 +1197,12 @@ export class ElementFragment extends Fragment {
|
|
|
905
1197
|
if (contents === undefined) {
|
|
906
1198
|
return null;
|
|
907
1199
|
}
|
|
908
|
-
const detail = contents.evaluateDetailed(this.getBindingData()
|
|
1200
|
+
const detail = contents.evaluateDetailed(this.getBindingData(), {
|
|
1201
|
+
kind: 'attribute',
|
|
1202
|
+
element: this.getTarget(),
|
|
1203
|
+
rawName: name,
|
|
1204
|
+
template: contents.getValue(),
|
|
1205
|
+
});
|
|
909
1206
|
if (detail.results.length === 1) {
|
|
910
1207
|
return {
|
|
911
1208
|
value: detail.results[0],
|
|
@@ -1291,10 +1588,20 @@ export class TextFragment extends Fragment {
|
|
|
1291
1588
|
this.skipMutation = true;
|
|
1292
1589
|
let nextText = this.text;
|
|
1293
1590
|
if (this.contents.isRawEvaluate) {
|
|
1294
|
-
nextText = this.contents.evaluate(this.parent.getBindingData()
|
|
1591
|
+
nextText = this.contents.evaluate(this.parent.getBindingData(), {
|
|
1592
|
+
kind: 'text',
|
|
1593
|
+
element: this.parent.getTarget(),
|
|
1594
|
+
childIndex: this.parent.getChildren().indexOf(this),
|
|
1595
|
+
template: this.text,
|
|
1596
|
+
})[0];
|
|
1295
1597
|
}
|
|
1296
1598
|
else if (this.contents.isEvaluate) {
|
|
1297
|
-
nextText = TextContents.joinEvaluateResults(this.contents.evaluate(this.parent.getBindingData()
|
|
1599
|
+
nextText = TextContents.joinEvaluateResults(this.contents.evaluate(this.parent.getBindingData(), {
|
|
1600
|
+
kind: 'text',
|
|
1601
|
+
element: this.parent.getTarget(),
|
|
1602
|
+
childIndex: this.parent.getChildren().indexOf(this),
|
|
1603
|
+
template: this.text,
|
|
1604
|
+
}));
|
|
1298
1605
|
}
|
|
1299
1606
|
const currentText = this.contents.isRawEvaluate
|
|
1300
1607
|
? this.parent.getTarget().innerHTML
|
|
@@ -1496,8 +1803,8 @@ class TextContents {
|
|
|
1496
1803
|
* @param bindingValues バインディングされた値のオブジェクト
|
|
1497
1804
|
* @returns 評価結果のリスト
|
|
1498
1805
|
*/
|
|
1499
|
-
evaluate(bindingValues) {
|
|
1500
|
-
return this.evaluateDetailed(bindingValues).results;
|
|
1806
|
+
evaluate(bindingValues, profileContext) {
|
|
1807
|
+
return this.evaluateDetailed(bindingValues, profileContext).results;
|
|
1501
1808
|
}
|
|
1502
1809
|
/**
|
|
1503
1810
|
* 式評価を行い、未解決参照の有無を含む結果を返します。
|
|
@@ -1505,33 +1812,58 @@ class TextContents {
|
|
|
1505
1812
|
* @param bindingValues バインディングされた値のオブジェクト
|
|
1506
1813
|
* @returns 評価結果と未解決参照の有無
|
|
1507
1814
|
*/
|
|
1508
|
-
evaluateDetailed(bindingValues) {
|
|
1815
|
+
evaluateDetailed(bindingValues, profileContext) {
|
|
1509
1816
|
if (!this.isEvaluate && !this.isRawEvaluate) {
|
|
1510
1817
|
return {
|
|
1511
1818
|
results: this.contents.map(c => c.text),
|
|
1512
1819
|
hasUnresolvedReference: false,
|
|
1513
1820
|
};
|
|
1514
1821
|
}
|
|
1822
|
+
return this.evaluateWithProfile(bindingValues, profileContext, content => content.type === ExpressionType.EXPRESSION ||
|
|
1823
|
+
content.type === ExpressionType.RAW_EXPRESSION, 'text');
|
|
1824
|
+
}
|
|
1825
|
+
/**
|
|
1826
|
+
* 式評価と profiler 記録をまとめて実行します。
|
|
1827
|
+
*
|
|
1828
|
+
* @param bindingValues バインディングされた値のオブジェクト
|
|
1829
|
+
* @param profileContext profiler 用コンテキスト
|
|
1830
|
+
* @param shouldEvaluate 評価対象判定
|
|
1831
|
+
* @param errorKind エラーログ種別
|
|
1832
|
+
* @returns 評価結果と未解決参照の有無
|
|
1833
|
+
*/
|
|
1834
|
+
evaluateWithProfile(bindingValues, profileContext, shouldEvaluate, errorKind) {
|
|
1515
1835
|
const results = [];
|
|
1836
|
+
const profileExpressions = [];
|
|
1837
|
+
let totalDurationMs = 0;
|
|
1516
1838
|
let hasUnresolvedReference = false;
|
|
1517
|
-
this.contents.forEach(
|
|
1839
|
+
this.contents.forEach(content => {
|
|
1518
1840
|
try {
|
|
1519
|
-
if (
|
|
1520
|
-
|
|
1521
|
-
const result =
|
|
1841
|
+
if (shouldEvaluate(content)) {
|
|
1842
|
+
const measured = EvaluationProfileRegistry.measure(() => Expression.evaluateDetailed(content.text, bindingValues));
|
|
1843
|
+
const result = measured.value;
|
|
1844
|
+
totalDurationMs += measured.durationMs;
|
|
1845
|
+
profileExpressions.push({
|
|
1846
|
+
expression: content.text,
|
|
1847
|
+
durationMs: measured.durationMs,
|
|
1848
|
+
});
|
|
1522
1849
|
hasUnresolvedReference =
|
|
1523
1850
|
hasUnresolvedReference || result.unresolvedReference;
|
|
1524
1851
|
results.push(result.value);
|
|
1525
1852
|
}
|
|
1526
1853
|
else {
|
|
1527
|
-
results.push(
|
|
1854
|
+
results.push(content.text);
|
|
1528
1855
|
}
|
|
1529
1856
|
}
|
|
1530
1857
|
catch (error) {
|
|
1531
|
-
Log.error('[Haori]', `Error evaluating
|
|
1858
|
+
Log.error('[Haori]', `Error evaluating ${errorKind} expression: ${content.text}`, error);
|
|
1859
|
+
profileExpressions.push({
|
|
1860
|
+
expression: content.text,
|
|
1861
|
+
durationMs: 0,
|
|
1862
|
+
});
|
|
1532
1863
|
results.push('');
|
|
1533
1864
|
}
|
|
1534
1865
|
});
|
|
1866
|
+
EvaluationProfileRegistry.record(profileContext, profileExpressions, totalDurationMs);
|
|
1535
1867
|
return { results, hasUnresolvedReference };
|
|
1536
1868
|
}
|
|
1537
1869
|
}
|
|
@@ -1567,8 +1899,8 @@ class AttributeContents extends TextContents {
|
|
|
1567
1899
|
* @param bindingValues バインディングされた値のオブジェクト
|
|
1568
1900
|
* @returns 評価結果のリスト
|
|
1569
1901
|
*/
|
|
1570
|
-
evaluate(bindingValues) {
|
|
1571
|
-
return this.evaluateDetailed(bindingValues).results;
|
|
1902
|
+
evaluate(bindingValues, profileContext) {
|
|
1903
|
+
return this.evaluateDetailed(bindingValues, profileContext).results;
|
|
1572
1904
|
}
|
|
1573
1905
|
/**
|
|
1574
1906
|
* 式評価を行い、未解決参照の有無を含む結果を返します。
|
|
@@ -1576,42 +1908,24 @@ class AttributeContents extends TextContents {
|
|
|
1576
1908
|
* @param bindingValues バインディングされた値のオブジェクト
|
|
1577
1909
|
* @returns 評価結果と未解決参照の有無
|
|
1578
1910
|
*/
|
|
1579
|
-
evaluateDetailed(bindingValues) {
|
|
1911
|
+
evaluateDetailed(bindingValues, profileContext) {
|
|
1580
1912
|
if (!this.isEvaluate && !this.forceEvaluation) {
|
|
1581
1913
|
return {
|
|
1582
1914
|
results: this.contents.map(c => c.text),
|
|
1583
1915
|
hasUnresolvedReference: false,
|
|
1584
1916
|
};
|
|
1585
1917
|
}
|
|
1586
|
-
const
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
c.type === ExpressionType.EXPRESSION ||
|
|
1592
|
-
c.type === ExpressionType.RAW_EXPRESSION) {
|
|
1593
|
-
const result = Expression.evaluateDetailed(c.text, bindingValues);
|
|
1594
|
-
hasUnresolvedReference =
|
|
1595
|
-
hasUnresolvedReference || result.unresolvedReference;
|
|
1596
|
-
results.push(result.value);
|
|
1597
|
-
}
|
|
1598
|
-
else {
|
|
1599
|
-
results.push(c.text);
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
catch (error) {
|
|
1603
|
-
Log.error('[Haori]', `Error evaluating attribute expression: ${c.text}`, error);
|
|
1604
|
-
results.push('');
|
|
1605
|
-
}
|
|
1606
|
-
});
|
|
1607
|
-
if (this.forceEvaluation && results.length > 1) {
|
|
1608
|
-
Log.error('[Haori]', 'each or if expressions must have a single content.', results);
|
|
1918
|
+
const detail = this.evaluateWithProfile(bindingValues, profileContext, content => (this.forceEvaluation && content.type === ExpressionType.TEXT) ||
|
|
1919
|
+
content.type === ExpressionType.EXPRESSION ||
|
|
1920
|
+
content.type === ExpressionType.RAW_EXPRESSION, 'attribute');
|
|
1921
|
+
if (this.forceEvaluation && detail.results.length > 1) {
|
|
1922
|
+
Log.error('[Haori]', 'each or if expressions must have a single content.', detail.results);
|
|
1609
1923
|
return {
|
|
1610
|
-
results: [results[0]],
|
|
1611
|
-
hasUnresolvedReference,
|
|
1924
|
+
results: [detail.results[0]],
|
|
1925
|
+
hasUnresolvedReference: detail.hasUnresolvedReference,
|
|
1612
1926
|
};
|
|
1613
1927
|
}
|
|
1614
|
-
return
|
|
1928
|
+
return detail;
|
|
1615
1929
|
}
|
|
1616
1930
|
}
|
|
1617
1931
|
/** 強制評価する属性名 */
|