haori 0.13.0 → 0.13.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 +10 -10
- package/dist/haori.es.js +191 -165
- package/dist/haori.iife.js +10 -10
- package/dist/index.d.ts +1 -1
- package/package.json +11 -3
- package/dist/haori.cjs.js.map +0 -1
- package/dist/haori.es.js.map +0 -1
- package/dist/haori.iife.js.map +0 -1
- package/dist/package.json +0 -70
- package/dist/src/builtins.d.ts +0 -100
- package/dist/src/builtins.d.ts.map +0 -1
- package/dist/src/builtins.js +0 -194
- package/dist/src/builtins.js.map +0 -1
- package/dist/src/core.d.ts +0 -505
- package/dist/src/core.d.ts.map +0 -1
- package/dist/src/core.js +0 -1817
- package/dist/src/core.js.map +0 -1
- package/dist/src/dev.d.ts +0 -35
- package/dist/src/dev.d.ts.map +0 -1
- package/dist/src/dev.js +0 -44
- package/dist/src/dev.js.map +0 -1
- package/dist/src/env.d.ts +0 -43
- package/dist/src/env.d.ts.map +0 -1
- package/dist/src/env.js +0 -108
- package/dist/src/env.js.map +0 -1
- package/dist/src/event.d.ts +0 -171
- package/dist/src/event.d.ts.map +0 -1
- package/dist/src/event.js +0 -235
- package/dist/src/event.js.map +0 -1
- package/dist/src/event_dispatcher.d.ts +0 -66
- package/dist/src/event_dispatcher.d.ts.map +0 -1
- package/dist/src/event_dispatcher.js +0 -167
- package/dist/src/event_dispatcher.js.map +0 -1
- package/dist/src/expression.d.ts +0 -260
- package/dist/src/expression.d.ts.map +0 -1
- package/dist/src/expression.js +0 -975
- package/dist/src/expression.js.map +0 -1
- package/dist/src/form.d.ts +0 -148
- package/dist/src/form.d.ts.map +0 -1
- package/dist/src/form.js +0 -428
- package/dist/src/form.js.map +0 -1
- package/dist/src/fragment.d.ts +0 -621
- package/dist/src/fragment.d.ts.map +0 -1
- package/dist/src/fragment.js +0 -1963
- package/dist/src/fragment.js.map +0 -1
- package/dist/src/haori.d.ts +0 -130
- package/dist/src/haori.d.ts.map +0 -1
- package/dist/src/haori.js +0 -215
- package/dist/src/haori.js.map +0 -1
- package/dist/src/import.d.ts +0 -19
- package/dist/src/import.d.ts.map +0 -1
- package/dist/src/import.js +0 -64
- package/dist/src/import.js.map +0 -1
- package/dist/src/index.d.ts +0 -27
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js +0 -30
- package/dist/src/index.js.map +0 -1
- package/dist/src/intersect.d.ts +0 -28
- package/dist/src/intersect.d.ts.map +0 -1
- package/dist/src/intersect.js +0 -201
- package/dist/src/intersect.js.map +0 -1
- package/dist/src/log.d.ts +0 -32
- package/dist/src/log.d.ts.map +0 -1
- package/dist/src/log.js +0 -43
- package/dist/src/log.js.map +0 -1
- package/dist/src/observer.d.ts +0 -24
- package/dist/src/observer.d.ts.map +0 -1
- package/dist/src/observer.js +0 -131
- package/dist/src/observer.js.map +0 -1
- package/dist/src/procedure.d.ts +0 -447
- package/dist/src/procedure.d.ts.map +0 -1
- package/dist/src/procedure.js +0 -1921
- package/dist/src/procedure.js.map +0 -1
- package/dist/src/queue.d.ts +0 -35
- package/dist/src/queue.d.ts.map +0 -1
- package/dist/src/queue.js +0 -202
- package/dist/src/queue.js.map +0 -1
- package/dist/src/url.d.ts +0 -14
- package/dist/src/url.d.ts.map +0 -1
- package/dist/src/url.js +0 -22
- package/dist/src/url.js.map +0 -1
- package/dist/tests/bind-arg-own-binding.test.d.ts +0 -2
- package/dist/tests/bind-arg-own-binding.test.d.ts.map +0 -1
- package/dist/tests/bind-arg-own-binding.test.js +0 -63
- package/dist/tests/bind-arg-own-binding.test.js.map +0 -1
- package/dist/tests/bindcomplete-and-scope.test.d.ts +0 -2
- package/dist/tests/bindcomplete-and-scope.test.d.ts.map +0 -1
- package/dist/tests/bindcomplete-and-scope.test.js +0 -115
- package/dist/tests/bindcomplete-and-scope.test.js.map +0 -1
- package/dist/tests/builtins.test.d.ts +0 -2
- package/dist/tests/builtins.test.d.ts.map +0 -1
- package/dist/tests/builtins.test.js +0 -152
- package/dist/tests/builtins.test.js.map +0 -1
- package/dist/tests/click-attributes.test.d.ts +0 -2
- package/dist/tests/click-attributes.test.d.ts.map +0 -1
- package/dist/tests/click-attributes.test.js +0 -113
- package/dist/tests/click-attributes.test.js.map +0 -1
- package/dist/tests/click-no-disabled.test.d.ts +0 -2
- package/dist/tests/click-no-disabled.test.d.ts.map +0 -1
- package/dist/tests/click-no-disabled.test.js +0 -67
- package/dist/tests/click-no-disabled.test.js.map +0 -1
- package/dist/tests/core.test.d.ts +0 -5
- package/dist/tests/core.test.d.ts.map +0 -1
- package/dist/tests/core.test.js +0 -1056
- package/dist/tests/core.test.js.map +0 -1
- package/dist/tests/data-bind-arg-reeval.test.d.ts +0 -2
- package/dist/tests/data-bind-arg-reeval.test.d.ts.map +0 -1
- package/dist/tests/data-bind-arg-reeval.test.js +0 -119
- package/dist/tests/data-bind-arg-reeval.test.js.map +0 -1
- package/dist/tests/data-bind-merge.test.d.ts +0 -2
- package/dist/tests/data-bind-merge.test.d.ts.map +0 -1
- package/dist/tests/data-bind-merge.test.js +0 -86
- package/dist/tests/data-bind-merge.test.js.map +0 -1
- package/dist/tests/data-click-defer.test.d.ts +0 -2
- package/dist/tests/data-click-defer.test.d.ts.map +0 -1
- package/dist/tests/data-click-defer.test.js +0 -57
- package/dist/tests/data-click-defer.test.js.map +0 -1
- package/dist/tests/data-click-run.test.d.ts +0 -5
- package/dist/tests/data-click-run.test.d.ts.map +0 -1
- package/dist/tests/data-click-run.test.js +0 -109
- package/dist/tests/data-click-run.test.js.map +0 -1
- package/dist/tests/data-derive.test.d.ts +0 -2
- package/dist/tests/data-derive.test.d.ts.map +0 -1
- package/dist/tests/data-derive.test.js +0 -999
- package/dist/tests/data-derive.test.js.map +0 -1
- package/dist/tests/data-each-browserlike.test.d.ts +0 -2
- package/dist/tests/data-each-browserlike.test.d.ts.map +0 -1
- package/dist/tests/data-each-browserlike.test.js +0 -48
- package/dist/tests/data-each-browserlike.test.js.map +0 -1
- package/dist/tests/data-each-fragment-debug.test.d.ts +0 -2
- package/dist/tests/data-each-fragment-debug.test.d.ts.map +0 -1
- package/dist/tests/data-each-fragment-debug.test.js +0 -119
- package/dist/tests/data-each-fragment-debug.test.js.map +0 -1
- package/dist/tests/data-each-table.test.d.ts +0 -2
- package/dist/tests/data-each-table.test.d.ts.map +0 -1
- package/dist/tests/data-each-table.test.js +0 -131
- package/dist/tests/data-each-table.test.js.map +0 -1
- package/dist/tests/data-each-template-recovery.test.d.ts +0 -2
- package/dist/tests/data-each-template-recovery.test.d.ts.map +0 -1
- package/dist/tests/data-each-template-recovery.test.js +0 -87
- package/dist/tests/data-each-template-recovery.test.js.map +0 -1
- package/dist/tests/data-event-prevent.test.d.ts +0 -2
- package/dist/tests/data-event-prevent.test.d.ts.map +0 -1
- package/dist/tests/data-event-prevent.test.js +0 -76
- package/dist/tests/data-event-prevent.test.js.map +0 -1
- package/dist/tests/data-fetch-form-input-binding.test.d.ts +0 -2
- package/dist/tests/data-fetch-form-input-binding.test.d.ts.map +0 -1
- package/dist/tests/data-fetch-form-input-binding.test.js +0 -202
- package/dist/tests/data-fetch-form-input-binding.test.js.map +0 -1
- package/dist/tests/data-fetch-tbody-dom.test.d.ts +0 -2
- package/dist/tests/data-fetch-tbody-dom.test.d.ts.map +0 -1
- package/dist/tests/data-fetch-tbody-dom.test.js +0 -82
- package/dist/tests/data-fetch-tbody-dom.test.js.map +0 -1
- package/dist/tests/data-form-container.test.d.ts +0 -2
- package/dist/tests/data-form-container.test.d.ts.map +0 -1
- package/dist/tests/data-form-container.test.js +0 -88
- package/dist/tests/data-form-container.test.js.map +0 -1
- package/dist/tests/data-if-dev-diagnostics.test.d.ts +0 -2
- package/dist/tests/data-if-dev-diagnostics.test.d.ts.map +0 -1
- package/dist/tests/data-if-dev-diagnostics.test.js +0 -55
- package/dist/tests/data-if-dev-diagnostics.test.js.map +0 -1
- package/dist/tests/data-if-falsy.test.d.ts +0 -2
- package/dist/tests/data-if-falsy.test.d.ts.map +0 -1
- package/dist/tests/data-if-falsy.test.js +0 -73
- package/dist/tests/data-if-falsy.test.js.map +0 -1
- package/dist/tests/data-load-on-show.test.d.ts +0 -2
- package/dist/tests/data-load-on-show.test.d.ts.map +0 -1
- package/dist/tests/data-load-on-show.test.js +0 -98
- package/dist/tests/data-load-on-show.test.js.map +0 -1
- package/dist/tests/derive-each-render-wait.test.d.ts +0 -2
- package/dist/tests/derive-each-render-wait.test.d.ts.map +0 -1
- package/dist/tests/derive-each-render-wait.test.js +0 -90
- package/dist/tests/derive-each-render-wait.test.js.map +0 -1
- package/dist/tests/dev.test.d.ts +0 -2
- package/dist/tests/dev.test.d.ts.map +0 -1
- package/dist/tests/dev.test.js +0 -51
- package/dist/tests/dev.test.js.map +0 -1
- package/dist/tests/each-update-event.test.d.ts +0 -2
- package/dist/tests/each-update-event.test.d.ts.map +0 -1
- package/dist/tests/each-update-event.test.js +0 -83
- package/dist/tests/each-update-event.test.js.map +0 -1
- package/dist/tests/each_arg.test.d.ts +0 -2
- package/dist/tests/each_arg.test.d.ts.map +0 -1
- package/dist/tests/each_arg.test.js +0 -41
- package/dist/tests/each_arg.test.js.map +0 -1
- package/dist/tests/env.test.d.ts +0 -2
- package/dist/tests/env.test.d.ts.map +0 -1
- package/dist/tests/env.test.js +0 -124
- package/dist/tests/env.test.js.map +0 -1
- package/dist/tests/evaluation-profile.test.d.ts +0 -2
- package/dist/tests/evaluation-profile.test.d.ts.map +0 -1
- package/dist/tests/evaluation-profile.test.js +0 -92
- package/dist/tests/evaluation-profile.test.js.map +0 -1
- package/dist/tests/event.test.d.ts +0 -2
- package/dist/tests/event.test.d.ts.map +0 -1
- package/dist/tests/event.test.js +0 -300
- package/dist/tests/event.test.js.map +0 -1
- package/dist/tests/expression-disallowed-keyword-hint.test.d.ts +0 -2
- package/dist/tests/expression-disallowed-keyword-hint.test.d.ts.map +0 -1
- package/dist/tests/expression-disallowed-keyword-hint.test.js +0 -45
- package/dist/tests/expression-disallowed-keyword-hint.test.js.map +0 -1
- package/dist/tests/expression-forbidden-hint.test.d.ts +0 -2
- package/dist/tests/expression-forbidden-hint.test.d.ts.map +0 -1
- package/dist/tests/expression-forbidden-hint.test.js +0 -37
- package/dist/tests/expression-forbidden-hint.test.js.map +0 -1
- package/dist/tests/expression.test.d.ts +0 -2
- package/dist/tests/expression.test.d.ts.map +0 -1
- package/dist/tests/expression.test.js +0 -482
- package/dist/tests/expression.test.js.map +0 -1
- package/dist/tests/fetch-and-procedure-scenarios.test.d.ts +0 -2
- package/dist/tests/fetch-and-procedure-scenarios.test.d.ts.map +0 -1
- package/dist/tests/fetch-and-procedure-scenarios.test.js +0 -195
- package/dist/tests/fetch-and-procedure-scenarios.test.js.map +0 -1
- package/dist/tests/form-number-type.test.d.ts +0 -2
- package/dist/tests/form-number-type.test.d.ts.map +0 -1
- package/dist/tests/form-number-type.test.js +0 -83
- package/dist/tests/form-number-type.test.js.map +0 -1
- package/dist/tests/form.test.d.ts +0 -2
- package/dist/tests/form.test.d.ts.map +0 -1
- package/dist/tests/form.test.js +0 -636
- package/dist/tests/form.test.js.map +0 -1
- package/dist/tests/fragment.test.d.ts +0 -2
- package/dist/tests/fragment.test.d.ts.map +0 -1
- package/dist/tests/fragment.test.js +0 -284
- package/dist/tests/fragment.test.js.map +0 -1
- package/dist/tests/haori.test.d.ts +0 -2
- package/dist/tests/haori.test.d.ts.map +0 -1
- package/dist/tests/haori.test.js +0 -149
- package/dist/tests/haori.test.js.map +0 -1
- package/dist/tests/helpers/async.d.ts +0 -20
- package/dist/tests/helpers/async.d.ts.map +0 -1
- package/dist/tests/helpers/async.js +0 -35
- package/dist/tests/helpers/async.js.map +0 -1
- package/dist/tests/import.test.d.ts +0 -2
- package/dist/tests/import.test.d.ts.map +0 -1
- package/dist/tests/import.test.js +0 -148
- package/dist/tests/import.test.js.map +0 -1
- package/dist/tests/intersect.test.d.ts +0 -2
- package/dist/tests/intersect.test.d.ts.map +0 -1
- package/dist/tests/intersect.test.js +0 -173
- package/dist/tests/intersect.test.js.map +0 -1
- package/dist/tests/log.test.d.ts +0 -2
- package/dist/tests/log.test.d.ts.map +0 -1
- package/dist/tests/log.test.js +0 -58
- package/dist/tests/log.test.js.map +0 -1
- package/dist/tests/observer.test.d.ts +0 -2
- package/dist/tests/observer.test.d.ts.map +0 -1
- package/dist/tests/observer.test.js +0 -64
- package/dist/tests/observer.test.js.map +0 -1
- package/dist/tests/placeholder-resolution.test.d.ts +0 -2
- package/dist/tests/placeholder-resolution.test.d.ts.map +0 -1
- package/dist/tests/placeholder-resolution.test.js +0 -463
- package/dist/tests/placeholder-resolution.test.js.map +0 -1
- package/dist/tests/procedure-action-operations.test.d.ts +0 -2
- package/dist/tests/procedure-action-operations.test.d.ts.map +0 -1
- package/dist/tests/procedure-action-operations.test.js +0 -1140
- package/dist/tests/procedure-action-operations.test.js.map +0 -1
- package/dist/tests/procedure-bind-append.test.d.ts +0 -2
- package/dist/tests/procedure-bind-append.test.d.ts.map +0 -1
- package/dist/tests/procedure-bind-append.test.js +0 -80
- package/dist/tests/procedure-bind-append.test.js.map +0 -1
- package/dist/tests/procedure-fetch-options.test.d.ts +0 -2
- package/dist/tests/procedure-fetch-options.test.d.ts.map +0 -1
- package/dist/tests/procedure-fetch-options.test.js +0 -255
- package/dist/tests/procedure-fetch-options.test.js.map +0 -1
- package/dist/tests/procedure.test.d.ts +0 -2
- package/dist/tests/procedure.test.d.ts.map +0 -1
- package/dist/tests/procedure.test.js +0 -137
- package/dist/tests/procedure.test.js.map +0 -1
- package/dist/tests/procedure_events.test.d.ts +0 -7
- package/dist/tests/procedure_events.test.d.ts.map +0 -1
- package/dist/tests/procedure_events.test.js +0 -261
- package/dist/tests/procedure_events.test.js.map +0 -1
- package/dist/tests/render-wait.test.d.ts +0 -2
- package/dist/tests/render-wait.test.d.ts.map +0 -1
- package/dist/tests/render-wait.test.js +0 -68
- package/dist/tests/render-wait.test.js.map +0 -1
- package/dist/tests/reset_each.test.d.ts +0 -2
- package/dist/tests/reset_each.test.d.ts.map +0 -1
- package/dist/tests/reset_each.test.js +0 -215
- package/dist/tests/reset_each.test.js.map +0 -1
- package/dist/tests/row-move.test.d.ts +0 -2
- package/dist/tests/row-move.test.d.ts.map +0 -1
- package/dist/tests/row-move.test.js +0 -220
- package/dist/tests/row-move.test.js.map +0 -1
- package/dist/tests/row-operations.test.d.ts +0 -2
- package/dist/tests/row-operations.test.d.ts.map +0 -1
- package/dist/tests/row-operations.test.js +0 -259
- package/dist/tests/row-operations.test.js.map +0 -1
- package/dist/tests/scheduled-evaluate-wait.test.d.ts +0 -2
- package/dist/tests/scheduled-evaluate-wait.test.d.ts.map +0 -1
- package/dist/tests/scheduled-evaluate-wait.test.js +0 -68
- package/dist/tests/scheduled-evaluate-wait.test.js.map +0 -1
- package/dist/tests/url.test.d.ts +0 -2
- package/dist/tests/url.test.d.ts.map +0 -1
- package/dist/tests/url.test.js +0 -150
- package/dist/tests/url.test.js.map +0 -1
- package/dist/vite.config.d.ts +0 -3
- package/dist/vite.config.d.ts.map +0 -1
- package/dist/vite.config.js +0 -28
- package/dist/vite.config.js.map +0 -1
- package/dist/vitest.config.d.ts +0 -3
- package/dist/vitest.config.d.ts.map +0 -1
- package/dist/vitest.config.js +0 -19
- package/dist/vitest.config.js.map +0 -1
package/dist/src/expression.js
DELETED
|
@@ -1,975 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview 式評価エンジン
|
|
3
|
-
*
|
|
4
|
-
* 式評価システムです。
|
|
5
|
-
* XSS攻撃やコードインジェクションを防ぐためのセキュリティ機能と、
|
|
6
|
-
* パフォーマンス向上のためのキャッシュ機能を提供します。
|
|
7
|
-
*/
|
|
8
|
-
import Log from './log';
|
|
9
|
-
import Builtins from './builtins';
|
|
10
|
-
class Expression {
|
|
11
|
-
/**
|
|
12
|
-
* 明示バインド内に持ち込まれてはならない危険値を返します。
|
|
13
|
-
*
|
|
14
|
-
* @returns 危険値の配列
|
|
15
|
-
*/
|
|
16
|
-
static getForbiddenBindingValues() {
|
|
17
|
-
const scope = globalThis;
|
|
18
|
-
const candidates = [
|
|
19
|
-
scope,
|
|
20
|
-
scope.window,
|
|
21
|
-
scope.document,
|
|
22
|
-
scope.navigator,
|
|
23
|
-
scope.history,
|
|
24
|
-
scope.localStorage,
|
|
25
|
-
scope.sessionStorage,
|
|
26
|
-
scope.fetch,
|
|
27
|
-
scope.Function,
|
|
28
|
-
scope.setTimeout,
|
|
29
|
-
scope.setInterval,
|
|
30
|
-
scope.requestAnimationFrame,
|
|
31
|
-
scope.alert,
|
|
32
|
-
scope.confirm,
|
|
33
|
-
scope.prompt,
|
|
34
|
-
];
|
|
35
|
-
if (scope.window?.location) {
|
|
36
|
-
candidates.push(scope.window.location);
|
|
37
|
-
}
|
|
38
|
-
return candidates.filter(value => value !== undefined && value !== null);
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* 現在の評価サイクルで利用する危険値集合を返します。
|
|
42
|
-
*
|
|
43
|
-
* @returns 危険値の集合
|
|
44
|
-
*/
|
|
45
|
-
static getForbiddenBindingValueSet() {
|
|
46
|
-
return new Set(this.getForbiddenBindingValues());
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* 危険値チェック用の短命キャッシュを次の microtask で破棄します。
|
|
50
|
-
*/
|
|
51
|
-
static scheduleForbiddenBindingValueCacheReset() {
|
|
52
|
-
if (this.forbiddenBindingValueCacheResetScheduled) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
this.forbiddenBindingValueCacheResetScheduled = true;
|
|
56
|
-
queueMicrotask(() => {
|
|
57
|
-
this.forbiddenBindingValueCache = new WeakMap();
|
|
58
|
-
this.forbiddenBindingValueCacheResetScheduled = false;
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* 現在のバインド識別子に含まれない禁止グローバルを遮断するコードを生成します。
|
|
63
|
-
*
|
|
64
|
-
* @param bindKeys 現在の式で利用するバインド識別子一覧
|
|
65
|
-
* @returns 評価前に挿入する初期化コード
|
|
66
|
-
*/
|
|
67
|
-
static buildAssignments(bindKeys) {
|
|
68
|
-
const bindKeySet = new Set(bindKeys);
|
|
69
|
-
return this.FORBIDDEN_NAMES.filter(name => !bindKeySet.has(name))
|
|
70
|
-
.map(name => `const ${name} = undefined`)
|
|
71
|
-
.join(';\n');
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* 式の中で「使用できない(ブロックされた)グローバル識別子」を参照しているものを
|
|
75
|
-
* 検出します。`Object` などは評価時に `undefined` へ遮断されるため、`Object.assign`
|
|
76
|
-
* のように使うと原因の分かりにくい `TypeError` になります。エラー時のヒント表示に
|
|
77
|
-
* 用います。プロパティアクセス(`foo.Object`)は対象外です。
|
|
78
|
-
*
|
|
79
|
-
* @param expression 評価対象の式
|
|
80
|
-
* @returns 式が参照しているブロック済み識別子の一覧
|
|
81
|
-
*/
|
|
82
|
-
static detectForbiddenIdentifiers(expression) {
|
|
83
|
-
return this.FORBIDDEN_NAMES.filter(name => {
|
|
84
|
-
// 直前が単語構成文字・`$`・`.` でなく、直後が単語構成文字・`$` でない
|
|
85
|
-
// 独立した識別子としての出現のみを検出する。
|
|
86
|
-
const pattern = new RegExp(`(^|[^\\w$.])${name}(?![\\w$])`);
|
|
87
|
-
return pattern.test(expression);
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* 式を評価します。
|
|
92
|
-
*
|
|
93
|
-
* @param expression 評価する式文字列
|
|
94
|
-
* @param bindedValue バインドされた値のオブジェクト
|
|
95
|
-
*/
|
|
96
|
-
static evaluate(expression, bindedValues = {}) {
|
|
97
|
-
return this.evaluateDetailed(expression, bindedValues).value;
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* 式を評価し、未解決参照の有無を含む詳細結果を返します。
|
|
101
|
-
*
|
|
102
|
-
* @param expression 評価する式文字列
|
|
103
|
-
* @param bindedValues バインドされた値のオブジェクト
|
|
104
|
-
* @returns 評価結果と未解決参照の有無
|
|
105
|
-
*/
|
|
106
|
-
static evaluateDetailed(expression, bindedValues = {}) {
|
|
107
|
-
this.scheduleForbiddenBindingValueCacheReset();
|
|
108
|
-
if (expression.trim() === '') {
|
|
109
|
-
Log.warn('[Haori]', expression, 'Expression is empty');
|
|
110
|
-
return { value: null, unresolvedReference: false };
|
|
111
|
-
}
|
|
112
|
-
if (this.containsDangerousPatterns(expression)) {
|
|
113
|
-
const keywords = this.detectDisallowedKeywords(expression);
|
|
114
|
-
if (keywords.length > 0) {
|
|
115
|
-
// function 宣言・return・各種ステートメント系キーワードは式構文として
|
|
116
|
-
// 使用できない。よくある原因は function(){return ...} の使用なので、
|
|
117
|
-
// アロー関数への置き換えを促す具体的なヒントを併記する。
|
|
118
|
-
const hint = keywords.some(k => k === 'function' || k === 'return')
|
|
119
|
-
? ' Statement keywords are not allowed in expressions;' +
|
|
120
|
-
' use an arrow function such as `x => ({key: value})`' +
|
|
121
|
-
' instead of `function(x){ return {key: value}; }`.'
|
|
122
|
-
: ' These are statement keywords and cannot be used in expressions.';
|
|
123
|
-
Log.warn('[Haori]', expression, 'Expression uses disallowed keyword(s): ' +
|
|
124
|
-
keywords.join(', ') +
|
|
125
|
-
'.' +
|
|
126
|
-
hint);
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
Log.warn('[Haori]', expression, 'Expression contains dangerous patterns');
|
|
130
|
-
}
|
|
131
|
-
return { value: null, unresolvedReference: false };
|
|
132
|
-
}
|
|
133
|
-
if (this.containsForbiddenKeys(bindedValues)) {
|
|
134
|
-
Log.warn('[Haori]', bindedValues, 'Binded values contain forbidden keys');
|
|
135
|
-
return { value: null, unresolvedReference: false };
|
|
136
|
-
}
|
|
137
|
-
const forbiddenBindingValues = this.getForbiddenBindingValueSet();
|
|
138
|
-
if (this.containsForbiddenBindingValues(bindedValues, new WeakSet(), forbiddenBindingValues)) {
|
|
139
|
-
Log.warn('[Haori]', bindedValues, 'Binded values contain forbidden values');
|
|
140
|
-
return { value: null, unresolvedReference: false };
|
|
141
|
-
}
|
|
142
|
-
// 式が予約名前空間 `haori` を参照している場合のみ組み込みヘルパーを注入する。
|
|
143
|
-
// 参照していない式に無駄な引数・Proxy ラップを増やさないための最適化。
|
|
144
|
-
const referencesBuiltins = this.BUILTIN_REFERENCE_PATTERN.test(expression);
|
|
145
|
-
if (referencesBuiltins && this.BUILTIN_NAMESPACE in bindedValues) {
|
|
146
|
-
Log.warn('[Haori]', `Binding key "${this.BUILTIN_NAMESPACE}" is reserved for built-in` +
|
|
147
|
-
' helpers; the bound value is ignored in expressions.');
|
|
148
|
-
}
|
|
149
|
-
const runtimeBindings = referencesBuiltins
|
|
150
|
-
? { ...bindedValues, [this.BUILTIN_NAMESPACE]: this.BUILTIN_HELPERS }
|
|
151
|
-
: { ...bindedValues };
|
|
152
|
-
const allowMissingIdentifierRecovery = this.canAttemptMissingIdentifierRecovery(expression);
|
|
153
|
-
for (let recoveryCount = 0; recoveryCount <= this.MAX_IDENTIFIER_RECOVERY_COUNT; recoveryCount += 1) {
|
|
154
|
-
const setup = this.prepareEvaluator(expression, runtimeBindings);
|
|
155
|
-
if (setup.compileFailed || setup.evaluator === null) {
|
|
156
|
-
return { value: null, unresolvedReference: false };
|
|
157
|
-
}
|
|
158
|
-
try {
|
|
159
|
-
const argValues = [];
|
|
160
|
-
const wrappedValues = this.wrapBoundValues(runtimeBindings);
|
|
161
|
-
setup.bindKeys.forEach((key) => {
|
|
162
|
-
argValues.push(wrappedValues[key]);
|
|
163
|
-
});
|
|
164
|
-
return {
|
|
165
|
-
value: this.withBlockedPropertyAccess(() => setup.evaluator(...argValues)),
|
|
166
|
-
unresolvedReference: false,
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
catch (error) {
|
|
170
|
-
if (allowMissingIdentifierRecovery && error instanceof ReferenceError) {
|
|
171
|
-
const missingIdentifier = this.extractMissingIdentifier(error);
|
|
172
|
-
if (missingIdentifier !== null &&
|
|
173
|
-
this.canRecoverMissingIdentifier(missingIdentifier, runtimeBindings)) {
|
|
174
|
-
runtimeBindings[missingIdentifier] = undefined;
|
|
175
|
-
continue;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
// 式が使用できない(ブロックされた)識別子を参照している場合は、
|
|
179
|
-
// 原因が分かりにくい TypeError になりがちなため明示的なヒントを出す。
|
|
180
|
-
const blocked = this.detectForbiddenIdentifiers(expression);
|
|
181
|
-
if (blocked.length > 0) {
|
|
182
|
-
Log.warn('[Haori]', 'Expression references blocked identifier(s): ' +
|
|
183
|
-
blocked.join(', ') +
|
|
184
|
-
'. These are blocked in expressions and evaluate to' +
|
|
185
|
-
' undefined (often the cause of this error).' +
|
|
186
|
-
' Use spread {...a, ...b} instead of Object.assign.', expression);
|
|
187
|
-
}
|
|
188
|
-
Log.error('[Haori]', 'Expression evaluation error:', expression, error);
|
|
189
|
-
if (error instanceof ReferenceError) {
|
|
190
|
-
// ReferenceError(未定義変数)はundefinedを返す
|
|
191
|
-
return { value: undefined, unresolvedReference: true };
|
|
192
|
-
}
|
|
193
|
-
return { value: null, unresolvedReference: false };
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
Log.error('[Haori]', 'Failed to recover missing identifiers:', expression, runtimeBindings);
|
|
197
|
-
return { value: undefined, unresolvedReference: true };
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* 現在のバインド集合で evaluator を取得または生成します。
|
|
201
|
-
*
|
|
202
|
-
* @param expression 評価する式
|
|
203
|
-
* @param bindedValues バインドされた値のオブジェクト
|
|
204
|
-
* @returns evaluator 準備結果
|
|
205
|
-
*/
|
|
206
|
-
static prepareEvaluator(expression, bindedValues) {
|
|
207
|
-
const bindKeys = Object.keys(bindedValues)
|
|
208
|
-
.filter(key => !this.FORBIDDEN_BINDING_NAMES.has(key))
|
|
209
|
-
.sort();
|
|
210
|
-
const cacheKey = `${expression}:${bindKeys.join(',')}`;
|
|
211
|
-
let evaluator = this.EXPRESSION_CACHE.get(cacheKey) || null;
|
|
212
|
-
if (evaluator !== null) {
|
|
213
|
-
return {
|
|
214
|
-
bindKeys,
|
|
215
|
-
evaluator,
|
|
216
|
-
compileFailed: false,
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
const assignments = this.buildAssignments(bindKeys);
|
|
220
|
-
const body = assignments
|
|
221
|
-
? '"use strict";\n' + `${assignments};\nreturn (${expression});`
|
|
222
|
-
: '"use strict";\n' + `return (${expression});`;
|
|
223
|
-
try {
|
|
224
|
-
evaluator = new Function(...bindKeys, body);
|
|
225
|
-
this.EXPRESSION_CACHE.set(cacheKey, evaluator);
|
|
226
|
-
return {
|
|
227
|
-
bindKeys,
|
|
228
|
-
evaluator,
|
|
229
|
-
compileFailed: false,
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
catch (error) {
|
|
233
|
-
Log.error('[Haori]', 'Failed to compile expression:', expression, error);
|
|
234
|
-
return {
|
|
235
|
-
bindKeys,
|
|
236
|
-
evaluator: null,
|
|
237
|
-
compileFailed: true,
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* ReferenceError から未宣言識別子名を抽出します。
|
|
243
|
-
*
|
|
244
|
-
* @param error 発生した ReferenceError
|
|
245
|
-
* @returns 識別子名。抽出できない場合は null
|
|
246
|
-
*/
|
|
247
|
-
static extractMissingIdentifier(error) {
|
|
248
|
-
const message = String(error.message || '');
|
|
249
|
-
const match = message.match(/^([A-Za-z_$][A-Za-z0-9_$]*) is not defined$/);
|
|
250
|
-
return match?.[1] || null;
|
|
251
|
-
}
|
|
252
|
-
/**
|
|
253
|
-
* 未宣言識別子を undefined バインドとして補完可能かを返します。
|
|
254
|
-
*
|
|
255
|
-
* @param identifier 識別子名
|
|
256
|
-
* @param bindedValues 現在のバインド値
|
|
257
|
-
* @returns 補完可能なら true
|
|
258
|
-
*/
|
|
259
|
-
static canRecoverMissingIdentifier(identifier, bindedValues) {
|
|
260
|
-
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) {
|
|
261
|
-
return false;
|
|
262
|
-
}
|
|
263
|
-
return (bindedValues[identifier] === undefined &&
|
|
264
|
-
!(identifier in bindedValues));
|
|
265
|
-
}
|
|
266
|
-
/**
|
|
267
|
-
* 未宣言識別子の補完を試みてよい式かを返します。
|
|
268
|
-
*
|
|
269
|
-
* @param expression 評価する式
|
|
270
|
-
* @returns 補完を試みてよい場合は true
|
|
271
|
-
*/
|
|
272
|
-
static canAttemptMissingIdentifierRecovery(expression) {
|
|
273
|
-
return (expression.includes('?.') ||
|
|
274
|
-
expression.includes('??') ||
|
|
275
|
-
expression.includes('||') ||
|
|
276
|
-
expression.includes('&&'));
|
|
277
|
-
}
|
|
278
|
-
/**
|
|
279
|
-
* 式にevalや危険な構文が含まれているかチェックします。
|
|
280
|
-
*
|
|
281
|
-
* @param expression チェック対象の式文字列
|
|
282
|
-
* @return 危険なパターンが含まれている場合はtrue
|
|
283
|
-
*/
|
|
284
|
-
static containsDangerousPatterns(expression) {
|
|
285
|
-
if (!this.hasAllowedSyntax(expression)) {
|
|
286
|
-
return true;
|
|
287
|
-
}
|
|
288
|
-
const dangerousPatterns = [
|
|
289
|
-
/\beval\s*\(/, // eval(...)
|
|
290
|
-
/\barguments\s*\[/, // arguments[...]
|
|
291
|
-
/\barguments\s*\./, // arguments.xxx
|
|
292
|
-
];
|
|
293
|
-
return dangerousPatterns.some(pattern => pattern.test(expression));
|
|
294
|
-
}
|
|
295
|
-
/**
|
|
296
|
-
* 式の中で使用されている「使用できないキーワード」を検出します。
|
|
297
|
-
* 文字列リテラルやプロパティ名は対象外とするため、トークナイザを用いて
|
|
298
|
-
* 識別子トークンのみを判定します。エラー時のヒント表示に使用します。
|
|
299
|
-
*
|
|
300
|
-
* @param expression 検査対象の式
|
|
301
|
-
* @returns 式に現れた使用できないキーワードの一覧(重複なし、出現順)
|
|
302
|
-
*/
|
|
303
|
-
static detectDisallowedKeywords(expression) {
|
|
304
|
-
// トークナイザは `;` など式構文外の文字で null を返すため、ここでは独立した
|
|
305
|
-
// 識別子としての出現を正規表現で検出する。直前が単語構成文字・`$`・`.`(プロパティ
|
|
306
|
-
// アクセス)でなく、直後が単語構成文字・`$` でないものだけを対象にする。
|
|
307
|
-
const found = [];
|
|
308
|
-
this.DISALLOWED_KEYWORDS.forEach(keyword => {
|
|
309
|
-
const pattern = new RegExp(`(^|[^\\w$.])${keyword}(?![\\w$])`);
|
|
310
|
-
if (pattern.test(expression)) {
|
|
311
|
-
found.push(keyword);
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
return found;
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* 許可する式構文かどうかを検証します。
|
|
318
|
-
*
|
|
319
|
-
* @param expression 検証対象の式
|
|
320
|
-
* @returns 許可する構文であればtrue
|
|
321
|
-
*/
|
|
322
|
-
static hasAllowedSyntax(expression) {
|
|
323
|
-
const tokens = this.tokenizeExpression(expression);
|
|
324
|
-
if (tokens === null || tokens.length === 0) {
|
|
325
|
-
return false;
|
|
326
|
-
}
|
|
327
|
-
const groups = [];
|
|
328
|
-
let previous = null;
|
|
329
|
-
for (let index = 0; index < tokens.length; index++) {
|
|
330
|
-
const token = tokens[index];
|
|
331
|
-
const next = tokens[index + 1] || null;
|
|
332
|
-
const activeGroup = groups[groups.length - 1] || null;
|
|
333
|
-
const beforePrevious = tokens[index - 2] || null;
|
|
334
|
-
const thirdPrevious = tokens[index - 3] || null;
|
|
335
|
-
if (this.startsObjectKey(activeGroup, previous, beforePrevious, thirdPrevious)) {
|
|
336
|
-
if (token.value === '[') {
|
|
337
|
-
return false;
|
|
338
|
-
}
|
|
339
|
-
if (token.type === 'identifier' &&
|
|
340
|
-
this.FORBIDDEN_PROPERTY_NAMES.has(token.value)) {
|
|
341
|
-
return false;
|
|
342
|
-
}
|
|
343
|
-
if (token.type === 'string' &&
|
|
344
|
-
this.FORBIDDEN_PROPERTY_NAMES.has(this.decodeStringLiteral(token.value))) {
|
|
345
|
-
return false;
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
if (token.type === 'identifier') {
|
|
349
|
-
if (this.DISALLOWED_KEYWORDS.has(token.value)) {
|
|
350
|
-
return false;
|
|
351
|
-
}
|
|
352
|
-
if (this.STRICT_FORBIDDEN_NAMES.includes(token.value)) {
|
|
353
|
-
return false;
|
|
354
|
-
}
|
|
355
|
-
if ((previous?.value === '.' || previous?.value === '?.') &&
|
|
356
|
-
this.FORBIDDEN_PROPERTY_NAMES.has(token.value)) {
|
|
357
|
-
return false;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
if (activeGroup === 'member' && token.value !== ']') {
|
|
361
|
-
if (token.type === 'string' &&
|
|
362
|
-
this.FORBIDDEN_PROPERTY_NAMES.has(this.decodeStringLiteral(token.value))) {
|
|
363
|
-
return false;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
if (token.value === '.' && next?.type !== 'identifier') {
|
|
367
|
-
return false;
|
|
368
|
-
}
|
|
369
|
-
if (token.value === '?.' &&
|
|
370
|
-
next?.type !== 'identifier' &&
|
|
371
|
-
next?.value !== '[' &&
|
|
372
|
-
next?.value !== '(') {
|
|
373
|
-
return false;
|
|
374
|
-
}
|
|
375
|
-
switch (token.value) {
|
|
376
|
-
case '(':
|
|
377
|
-
groups.push('paren');
|
|
378
|
-
break;
|
|
379
|
-
case ')': {
|
|
380
|
-
const group = groups.pop();
|
|
381
|
-
if (group !== 'paren') {
|
|
382
|
-
return false;
|
|
383
|
-
}
|
|
384
|
-
break;
|
|
385
|
-
}
|
|
386
|
-
case '[': {
|
|
387
|
-
const group = this.startsMemberAccess(previous)
|
|
388
|
-
? 'member'
|
|
389
|
-
: 'array';
|
|
390
|
-
groups.push(group);
|
|
391
|
-
break;
|
|
392
|
-
}
|
|
393
|
-
case '{':
|
|
394
|
-
groups.push('object');
|
|
395
|
-
break;
|
|
396
|
-
case ']': {
|
|
397
|
-
const group = groups.pop();
|
|
398
|
-
if (group === undefined) {
|
|
399
|
-
return false;
|
|
400
|
-
}
|
|
401
|
-
break;
|
|
402
|
-
}
|
|
403
|
-
case '}': {
|
|
404
|
-
const group = groups.pop();
|
|
405
|
-
if (group !== 'object') {
|
|
406
|
-
return false;
|
|
407
|
-
}
|
|
408
|
-
break;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
previous = token;
|
|
412
|
-
}
|
|
413
|
-
return groups.length === 0;
|
|
414
|
-
}
|
|
415
|
-
/**
|
|
416
|
-
* 式をトークン列に分解します。
|
|
417
|
-
*
|
|
418
|
-
* @param expression 評価前に検証する式
|
|
419
|
-
* @returns 分解結果。未対応構文を含む場合はnull
|
|
420
|
-
*/
|
|
421
|
-
static tokenizeExpression(expression) {
|
|
422
|
-
const tokens = [];
|
|
423
|
-
const operators = [
|
|
424
|
-
'===',
|
|
425
|
-
'!==',
|
|
426
|
-
'...',
|
|
427
|
-
'?.',
|
|
428
|
-
'&&',
|
|
429
|
-
'||',
|
|
430
|
-
'>=',
|
|
431
|
-
'<=',
|
|
432
|
-
'==',
|
|
433
|
-
'!=',
|
|
434
|
-
'=>',
|
|
435
|
-
];
|
|
436
|
-
const singleCharacters = new Set([
|
|
437
|
-
'(',
|
|
438
|
-
')',
|
|
439
|
-
'{',
|
|
440
|
-
'}',
|
|
441
|
-
'[',
|
|
442
|
-
']',
|
|
443
|
-
'.',
|
|
444
|
-
',',
|
|
445
|
-
'?',
|
|
446
|
-
':',
|
|
447
|
-
'+',
|
|
448
|
-
'-',
|
|
449
|
-
'*',
|
|
450
|
-
'/',
|
|
451
|
-
'%',
|
|
452
|
-
'!',
|
|
453
|
-
'>',
|
|
454
|
-
'<',
|
|
455
|
-
]);
|
|
456
|
-
let index = 0;
|
|
457
|
-
while (index < expression.length) {
|
|
458
|
-
const current = expression[index];
|
|
459
|
-
if (/\s/.test(current)) {
|
|
460
|
-
index += 1;
|
|
461
|
-
continue;
|
|
462
|
-
}
|
|
463
|
-
if (current === '/' &&
|
|
464
|
-
(expression[index + 1] === '/' || expression[index + 1] === '*')) {
|
|
465
|
-
return null;
|
|
466
|
-
}
|
|
467
|
-
if (current === '"' || current === '\'') {
|
|
468
|
-
const stringToken = this.readStringToken(expression, index);
|
|
469
|
-
if (stringToken === null) {
|
|
470
|
-
return null;
|
|
471
|
-
}
|
|
472
|
-
tokens.push(stringToken.token);
|
|
473
|
-
index = stringToken.nextIndex;
|
|
474
|
-
continue;
|
|
475
|
-
}
|
|
476
|
-
const operator = operators.find(item => expression.startsWith(item, index));
|
|
477
|
-
if (operator) {
|
|
478
|
-
tokens.push({ type: 'operator', value: operator, position: index });
|
|
479
|
-
index += operator.length;
|
|
480
|
-
continue;
|
|
481
|
-
}
|
|
482
|
-
if (/[0-9]/.test(current)) {
|
|
483
|
-
const numberToken = this.readNumberToken(expression, index);
|
|
484
|
-
tokens.push(numberToken.token);
|
|
485
|
-
index = numberToken.nextIndex;
|
|
486
|
-
continue;
|
|
487
|
-
}
|
|
488
|
-
if (/[A-Za-z_$]/.test(current)) {
|
|
489
|
-
const identifierToken = this.readIdentifierToken(expression, index);
|
|
490
|
-
tokens.push(identifierToken.token);
|
|
491
|
-
index = identifierToken.nextIndex;
|
|
492
|
-
continue;
|
|
493
|
-
}
|
|
494
|
-
if (singleCharacters.has(current)) {
|
|
495
|
-
tokens.push({ type: 'operator', value: current, position: index });
|
|
496
|
-
index += 1;
|
|
497
|
-
continue;
|
|
498
|
-
}
|
|
499
|
-
return null;
|
|
500
|
-
}
|
|
501
|
-
return tokens;
|
|
502
|
-
}
|
|
503
|
-
/**
|
|
504
|
-
* 文字列リテラルを読み取ります。
|
|
505
|
-
*
|
|
506
|
-
* @param expression 式全体
|
|
507
|
-
* @param start 開始位置
|
|
508
|
-
* @returns トークンと次の位置
|
|
509
|
-
*/
|
|
510
|
-
static readStringToken(expression, start) {
|
|
511
|
-
const quote = expression[start];
|
|
512
|
-
let index = start + 1;
|
|
513
|
-
while (index < expression.length) {
|
|
514
|
-
const current = expression[index];
|
|
515
|
-
if (current === '\\') {
|
|
516
|
-
index += 2;
|
|
517
|
-
continue;
|
|
518
|
-
}
|
|
519
|
-
if (current === quote) {
|
|
520
|
-
return {
|
|
521
|
-
token: {
|
|
522
|
-
type: 'string',
|
|
523
|
-
value: expression.slice(start, index + 1),
|
|
524
|
-
position: start,
|
|
525
|
-
},
|
|
526
|
-
nextIndex: index + 1,
|
|
527
|
-
};
|
|
528
|
-
}
|
|
529
|
-
index += 1;
|
|
530
|
-
}
|
|
531
|
-
return null;
|
|
532
|
-
}
|
|
533
|
-
/**
|
|
534
|
-
* 数値リテラルを読み取ります。
|
|
535
|
-
*
|
|
536
|
-
* @param expression 式全体
|
|
537
|
-
* @param start 開始位置
|
|
538
|
-
* @returns トークンと次の位置
|
|
539
|
-
*/
|
|
540
|
-
static readNumberToken(expression, start) {
|
|
541
|
-
let index = start;
|
|
542
|
-
while (index < expression.length && /[0-9_]/.test(expression[index])) {
|
|
543
|
-
index += 1;
|
|
544
|
-
}
|
|
545
|
-
if (expression[index] === '.') {
|
|
546
|
-
index += 1;
|
|
547
|
-
while (index < expression.length && /[0-9_]/.test(expression[index])) {
|
|
548
|
-
index += 1;
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
return {
|
|
552
|
-
token: {
|
|
553
|
-
type: 'number',
|
|
554
|
-
value: expression.slice(start, index),
|
|
555
|
-
position: start,
|
|
556
|
-
},
|
|
557
|
-
nextIndex: index,
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
/**
|
|
561
|
-
* 識別子を読み取ります。
|
|
562
|
-
*
|
|
563
|
-
* @param expression 式全体
|
|
564
|
-
* @param start 開始位置
|
|
565
|
-
* @returns トークンと次の位置
|
|
566
|
-
*/
|
|
567
|
-
static readIdentifierToken(expression, start) {
|
|
568
|
-
let index = start;
|
|
569
|
-
while (index < expression.length &&
|
|
570
|
-
/[A-Za-z0-9_$]/.test(expression[index])) {
|
|
571
|
-
index += 1;
|
|
572
|
-
}
|
|
573
|
-
return {
|
|
574
|
-
token: {
|
|
575
|
-
type: 'identifier',
|
|
576
|
-
value: expression.slice(start, index),
|
|
577
|
-
position: start,
|
|
578
|
-
},
|
|
579
|
-
nextIndex: index,
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
/**
|
|
583
|
-
* 角括弧がメンバーアクセスかどうかを判定します。
|
|
584
|
-
*
|
|
585
|
-
* @param previous 直前のトークン
|
|
586
|
-
* @returns メンバーアクセスであればtrue
|
|
587
|
-
*/
|
|
588
|
-
static startsMemberAccess(previous) {
|
|
589
|
-
if (previous === null) {
|
|
590
|
-
return false;
|
|
591
|
-
}
|
|
592
|
-
if (previous.type === 'identifier' || previous.type === 'number') {
|
|
593
|
-
return true;
|
|
594
|
-
}
|
|
595
|
-
return (previous.value === ')' ||
|
|
596
|
-
previous.value === ']' ||
|
|
597
|
-
previous.value === '?.');
|
|
598
|
-
}
|
|
599
|
-
/**
|
|
600
|
-
* object literal 内で次のトークンがキー位置かどうかを判定します。
|
|
601
|
-
*
|
|
602
|
-
* @param activeGroup 現在のグループ種別
|
|
603
|
-
* @param previous 直前のトークン
|
|
604
|
-
* @returns object literal のキー位置であれば true
|
|
605
|
-
*/
|
|
606
|
-
static startsObjectKey(activeGroup, previous, beforePrevious, thirdPrevious) {
|
|
607
|
-
if (activeGroup !== 'object') {
|
|
608
|
-
return false;
|
|
609
|
-
}
|
|
610
|
-
if (previous?.value === '{' || previous?.value === ',') {
|
|
611
|
-
return true;
|
|
612
|
-
}
|
|
613
|
-
if (previous?.type === 'identifier' &&
|
|
614
|
-
this.OBJECT_PROPERTY_MODIFIERS.has(previous.value) &&
|
|
615
|
-
(beforePrevious?.value === '{' || beforePrevious?.value === ',')) {
|
|
616
|
-
return true;
|
|
617
|
-
}
|
|
618
|
-
if (previous?.value !== '*') {
|
|
619
|
-
return false;
|
|
620
|
-
}
|
|
621
|
-
if (beforePrevious?.value === '{' || beforePrevious?.value === ',') {
|
|
622
|
-
return true;
|
|
623
|
-
}
|
|
624
|
-
return (beforePrevious?.type === 'identifier' &&
|
|
625
|
-
beforePrevious.value === 'async' &&
|
|
626
|
-
(thirdPrevious?.value === '{' || thirdPrevious?.value === ','));
|
|
627
|
-
}
|
|
628
|
-
/**
|
|
629
|
-
* 文字列リテラルをプレーン文字列へ変換します。
|
|
630
|
-
*
|
|
631
|
-
* @param literal 文字列リテラル
|
|
632
|
-
* @returns デコード後の文字列
|
|
633
|
-
*/
|
|
634
|
-
static decodeStringLiteral(literal) {
|
|
635
|
-
return literal
|
|
636
|
-
.slice(1, -1)
|
|
637
|
-
.replace(/\\u\{([0-9a-fA-F]+)\}/g, (_, code) => String.fromCodePoint(parseInt(code, 16)))
|
|
638
|
-
.replace(/\\u([0-9a-fA-F]{4})/g, (_, code) => String.fromCharCode(parseInt(code, 16)))
|
|
639
|
-
.replace(/\\x([0-9a-fA-F]{2})/g, (_, code) => String.fromCharCode(parseInt(code, 16)))
|
|
640
|
-
.replace(/\\(["'\\bfnrtv0])/g, (_, escaped) => {
|
|
641
|
-
switch (escaped) {
|
|
642
|
-
case 'b':
|
|
643
|
-
return '\b';
|
|
644
|
-
case 'f':
|
|
645
|
-
return '\f';
|
|
646
|
-
case 'n':
|
|
647
|
-
return '\n';
|
|
648
|
-
case 'r':
|
|
649
|
-
return '\r';
|
|
650
|
-
case 't':
|
|
651
|
-
return '\t';
|
|
652
|
-
case 'v':
|
|
653
|
-
return '\v';
|
|
654
|
-
case '0':
|
|
655
|
-
return '\0';
|
|
656
|
-
default:
|
|
657
|
-
return escaped;
|
|
658
|
-
}
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
/**
|
|
662
|
-
* バインド値を安全なProxyでラップします。
|
|
663
|
-
*
|
|
664
|
-
* @param bindedValues バインド値
|
|
665
|
-
* @returns ラップ済みのバインド値
|
|
666
|
-
*/
|
|
667
|
-
static wrapBoundValues(bindedValues) {
|
|
668
|
-
const cache = new WeakMap();
|
|
669
|
-
const wrappedValues = {};
|
|
670
|
-
Object.entries(bindedValues).forEach(([key, value]) => {
|
|
671
|
-
wrappedValues[key] = this.wrapBoundValue(value, cache);
|
|
672
|
-
});
|
|
673
|
-
return wrappedValues;
|
|
674
|
-
}
|
|
675
|
-
/**
|
|
676
|
-
* 危険なプロパティアクセスを防ぐために値を再帰的にラップします。
|
|
677
|
-
*
|
|
678
|
-
* @param value ラップ対象の値
|
|
679
|
-
* @param cache 既存Proxyのキャッシュ
|
|
680
|
-
* @returns ラップ済みの値
|
|
681
|
-
*/
|
|
682
|
-
static wrapBoundValue(value, cache) {
|
|
683
|
-
if (!this.shouldWrapValue(value)) {
|
|
684
|
-
return value;
|
|
685
|
-
}
|
|
686
|
-
const target = value;
|
|
687
|
-
const cachedValue = cache.get(target);
|
|
688
|
-
if (cachedValue !== undefined) {
|
|
689
|
-
return cachedValue;
|
|
690
|
-
}
|
|
691
|
-
const proxy = new Proxy(target, {
|
|
692
|
-
get: (currentTarget, property, receiver) => {
|
|
693
|
-
if (typeof property === 'string' &&
|
|
694
|
-
this.FORBIDDEN_PROPERTY_NAMES.has(property)) {
|
|
695
|
-
return undefined;
|
|
696
|
-
}
|
|
697
|
-
const result = Reflect.get(currentTarget, property, receiver);
|
|
698
|
-
if (typeof property === 'symbol') {
|
|
699
|
-
return result;
|
|
700
|
-
}
|
|
701
|
-
return this.wrapBoundValue(result, cache);
|
|
702
|
-
},
|
|
703
|
-
has: (currentTarget, property) => {
|
|
704
|
-
if (typeof property === 'string' &&
|
|
705
|
-
this.FORBIDDEN_PROPERTY_NAMES.has(property)) {
|
|
706
|
-
return false;
|
|
707
|
-
}
|
|
708
|
-
return Reflect.has(currentTarget, property);
|
|
709
|
-
},
|
|
710
|
-
getOwnPropertyDescriptor: (currentTarget, property) => {
|
|
711
|
-
if (typeof property === 'string' &&
|
|
712
|
-
this.FORBIDDEN_PROPERTY_NAMES.has(property)) {
|
|
713
|
-
return undefined;
|
|
714
|
-
}
|
|
715
|
-
return Reflect.getOwnPropertyDescriptor(currentTarget, property);
|
|
716
|
-
},
|
|
717
|
-
apply: (currentTarget, thisArg, argArray) => {
|
|
718
|
-
const result = Reflect.apply(currentTarget, thisArg, argArray);
|
|
719
|
-
if (this.isIteratorLike(result)) {
|
|
720
|
-
return result;
|
|
721
|
-
}
|
|
722
|
-
return this.wrapBoundValue(result, cache);
|
|
723
|
-
},
|
|
724
|
-
construct: (currentTarget, argArray, newTarget) => {
|
|
725
|
-
return this.wrapBoundValue(Reflect.construct(currentTarget, argArray, newTarget), cache);
|
|
726
|
-
},
|
|
727
|
-
});
|
|
728
|
-
cache.set(target, proxy);
|
|
729
|
-
return proxy;
|
|
730
|
-
}
|
|
731
|
-
/**
|
|
732
|
-
* Proxy ラップ対象の値かどうかを判定します。
|
|
733
|
-
*
|
|
734
|
-
* @param value 判定対象
|
|
735
|
-
* @returns ラップ対象であればtrue
|
|
736
|
-
*/
|
|
737
|
-
static shouldWrapValue(value) {
|
|
738
|
-
if (typeof value === 'function') {
|
|
739
|
-
return true;
|
|
740
|
-
}
|
|
741
|
-
if (value === null || typeof value !== 'object') {
|
|
742
|
-
return false;
|
|
743
|
-
}
|
|
744
|
-
if (Array.isArray(value)) {
|
|
745
|
-
return true;
|
|
746
|
-
}
|
|
747
|
-
const prototype = Object.getPrototypeOf(value);
|
|
748
|
-
return prototype === Object.prototype || prototype === null;
|
|
749
|
-
}
|
|
750
|
-
/**
|
|
751
|
-
* 評価中のみ prototype 系プロパティへの生アクセスを抑止します。
|
|
752
|
-
*
|
|
753
|
-
* @param callback 実行する処理
|
|
754
|
-
* @returns 処理結果
|
|
755
|
-
*/
|
|
756
|
-
static withBlockedPropertyAccess(callback) {
|
|
757
|
-
const blockedDescriptors = [
|
|
758
|
-
{ target: Object.prototype, property: 'constructor' },
|
|
759
|
-
{ target: Function.prototype, property: 'constructor' },
|
|
760
|
-
{ target: Object.prototype, property: '__proto__' },
|
|
761
|
-
];
|
|
762
|
-
const originals = blockedDescriptors
|
|
763
|
-
.map(item => ({
|
|
764
|
-
...item,
|
|
765
|
-
descriptor: Object.getOwnPropertyDescriptor(item.target, item.property),
|
|
766
|
-
}))
|
|
767
|
-
.filter(item => item.descriptor?.configurable === true);
|
|
768
|
-
originals.forEach(({ target, property }) => {
|
|
769
|
-
Object.defineProperty(target, property, {
|
|
770
|
-
configurable: true,
|
|
771
|
-
enumerable: false,
|
|
772
|
-
get: () => undefined,
|
|
773
|
-
set: () => undefined,
|
|
774
|
-
});
|
|
775
|
-
});
|
|
776
|
-
try {
|
|
777
|
-
return callback();
|
|
778
|
-
}
|
|
779
|
-
finally {
|
|
780
|
-
originals.forEach(({ target, property, descriptor }) => {
|
|
781
|
-
if (descriptor !== undefined) {
|
|
782
|
-
Object.defineProperty(target, property, descriptor);
|
|
783
|
-
}
|
|
784
|
-
});
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
/**
|
|
788
|
-
* イテレータ互換オブジェクトかどうかを判定します。
|
|
789
|
-
*
|
|
790
|
-
* @param value 判定対象
|
|
791
|
-
* @returns イテレータ互換であればtrue
|
|
792
|
-
*/
|
|
793
|
-
static isIteratorLike(value) {
|
|
794
|
-
if (value === null || typeof value !== 'object') {
|
|
795
|
-
return false;
|
|
796
|
-
}
|
|
797
|
-
return typeof value.next === 'function';
|
|
798
|
-
}
|
|
799
|
-
/**
|
|
800
|
-
* トップレベルのバインド識別子に拒否対象名が含まれていないかを判定します。
|
|
801
|
-
* ネストしたオブジェクトのプロパティ名は識別子として評価されないため、ここでは拒否しません。
|
|
802
|
-
*
|
|
803
|
-
* @param obj チェック対象のオブジェクト
|
|
804
|
-
* @return 禁止識別子が含まれていればtrue
|
|
805
|
-
*/
|
|
806
|
-
static containsForbiddenKeys(obj) {
|
|
807
|
-
if (!obj || typeof obj !== 'object') {
|
|
808
|
-
return false;
|
|
809
|
-
}
|
|
810
|
-
for (const key of Object.keys(obj)) {
|
|
811
|
-
if (this.FORBIDDEN_BINDING_NAMES.has(key)) {
|
|
812
|
-
return true;
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
return false;
|
|
816
|
-
}
|
|
817
|
-
/**
|
|
818
|
-
* バインド値に危険なホストオブジェクトやグローバル関数が含まれていないかを再帰的に判定します。
|
|
819
|
-
*
|
|
820
|
-
* @param obj チェック対象の値
|
|
821
|
-
* @param seen 循環参照検出用の訪問済み集合
|
|
822
|
-
* @return 危険値が含まれていればtrue
|
|
823
|
-
*/
|
|
824
|
-
static containsForbiddenBindingValues(obj, seen = new WeakSet(), forbiddenBindingValues = this.getForbiddenBindingValueSet()) {
|
|
825
|
-
if (!obj || typeof obj !== 'object') {
|
|
826
|
-
return false;
|
|
827
|
-
}
|
|
828
|
-
const cached = this.forbiddenBindingValueCache.get(obj);
|
|
829
|
-
if (cached !== undefined) {
|
|
830
|
-
return cached;
|
|
831
|
-
}
|
|
832
|
-
if (seen.has(obj)) {
|
|
833
|
-
return false;
|
|
834
|
-
}
|
|
835
|
-
seen.add(obj);
|
|
836
|
-
if (forbiddenBindingValues.has(obj)) {
|
|
837
|
-
this.forbiddenBindingValueCache.set(obj, true);
|
|
838
|
-
return true;
|
|
839
|
-
}
|
|
840
|
-
for (const value of Object.values(obj)) {
|
|
841
|
-
if (typeof value === 'function') {
|
|
842
|
-
if (forbiddenBindingValues.has(value)) {
|
|
843
|
-
this.forbiddenBindingValueCache.set(obj, true);
|
|
844
|
-
return true;
|
|
845
|
-
}
|
|
846
|
-
continue;
|
|
847
|
-
}
|
|
848
|
-
if (this.containsForbiddenBindingValues(value, seen, forbiddenBindingValues)) {
|
|
849
|
-
this.forbiddenBindingValueCache.set(obj, true);
|
|
850
|
-
return true;
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
this.forbiddenBindingValueCache.set(obj, false);
|
|
854
|
-
return false;
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
/** 未宣言識別子の自動補完を試みる最大回数 */
|
|
858
|
-
Expression.MAX_IDENTIFIER_RECOVERY_COUNT = 8;
|
|
859
|
-
/** 組み込みヘルパーを公開する予約名前空間 */
|
|
860
|
-
Expression.BUILTIN_NAMESPACE = 'haori';
|
|
861
|
-
/**
|
|
862
|
-
* 式スコープへ注入する組み込みヘルパー。公開 API の凍結オブジェクトをそのまま
|
|
863
|
-
* 注入すると、評価時の Proxy ラップが「read-only プロパティに別値を返せない」と
|
|
864
|
-
* いう Proxy 不変条件に違反するため、非凍結の浅いコピーを用いる。
|
|
865
|
-
*/
|
|
866
|
-
Expression.BUILTIN_HELPERS = {
|
|
867
|
-
...Builtins,
|
|
868
|
-
};
|
|
869
|
-
/**
|
|
870
|
-
* 式が予約名前空間 `haori` を独立した識別子として参照しているか判定する正規表現。
|
|
871
|
-
* `foo.haori` のようなプロパティアクセスは対象外とする。
|
|
872
|
-
*/
|
|
873
|
-
Expression.BUILTIN_REFERENCE_PATTERN = /(^|[^\w$.])haori(?![\w$])/;
|
|
874
|
-
/** 危険値チェック結果の短命キャッシュ */
|
|
875
|
-
Expression.forbiddenBindingValueCache = new WeakMap();
|
|
876
|
-
/** 危険値チェックキャッシュのクリア予約済みフラグ */
|
|
877
|
-
Expression.forbiddenBindingValueCacheResetScheduled = false;
|
|
878
|
-
/** Haoriで禁止すべき識別子一覧(eval と arguments は strict モードで無効化) */
|
|
879
|
-
Expression.FORBIDDEN_NAMES = [
|
|
880
|
-
// グローバルオブジェクト
|
|
881
|
-
'window',
|
|
882
|
-
'self',
|
|
883
|
-
'globalThis',
|
|
884
|
-
'frames',
|
|
885
|
-
'parent',
|
|
886
|
-
'top',
|
|
887
|
-
// 危険な関数/オブジェクト
|
|
888
|
-
'Function',
|
|
889
|
-
'setTimeout',
|
|
890
|
-
'setInterval',
|
|
891
|
-
'requestAnimationFrame',
|
|
892
|
-
'alert',
|
|
893
|
-
'confirm',
|
|
894
|
-
'prompt',
|
|
895
|
-
'fetch',
|
|
896
|
-
'XMLHttpRequest',
|
|
897
|
-
'Reflect',
|
|
898
|
-
// 脱出経路・プロトタイプ
|
|
899
|
-
'constructor',
|
|
900
|
-
'__proto__',
|
|
901
|
-
'prototype',
|
|
902
|
-
'Object',
|
|
903
|
-
// その他
|
|
904
|
-
'document',
|
|
905
|
-
'location',
|
|
906
|
-
'navigator',
|
|
907
|
-
'localStorage',
|
|
908
|
-
'sessionStorage',
|
|
909
|
-
'IndexedDB',
|
|
910
|
-
'history',
|
|
911
|
-
];
|
|
912
|
-
/** strict モードで禁止される識別子 */
|
|
913
|
-
Expression.STRICT_FORBIDDEN_NAMES = ['eval', 'arguments'];
|
|
914
|
-
/** 明示バインド時のみ利用を許可する衝突名 */
|
|
915
|
-
Expression.REBINDABLE_FORBIDDEN_NAMES = new Set(['location']);
|
|
916
|
-
/** バインド識別子としては拒否する名前 */
|
|
917
|
-
Expression.FORBIDDEN_BINDING_NAMES = new Set([
|
|
918
|
-
...Expression.FORBIDDEN_NAMES.filter(name => !Expression.REBINDABLE_FORBIDDEN_NAMES.has(name)),
|
|
919
|
-
'constructor',
|
|
920
|
-
'__proto__',
|
|
921
|
-
'prototype',
|
|
922
|
-
...Expression.STRICT_FORBIDDEN_NAMES,
|
|
923
|
-
]);
|
|
924
|
-
/** プロパティアクセスで拒否する名前 */
|
|
925
|
-
Expression.FORBIDDEN_PROPERTY_NAMES = new Set([
|
|
926
|
-
'constructor',
|
|
927
|
-
'__proto__',
|
|
928
|
-
'prototype',
|
|
929
|
-
]);
|
|
930
|
-
/** object literal のプロパティ定義で前置修飾子として扱う識別子 */
|
|
931
|
-
Expression.OBJECT_PROPERTY_MODIFIERS = new Set([
|
|
932
|
-
'get',
|
|
933
|
-
'set',
|
|
934
|
-
'async',
|
|
935
|
-
]);
|
|
936
|
-
/** 式構文として許可しない予約語 */
|
|
937
|
-
Expression.DISALLOWED_KEYWORDS = new Set([
|
|
938
|
-
'await',
|
|
939
|
-
'break',
|
|
940
|
-
'case',
|
|
941
|
-
'catch',
|
|
942
|
-
'class',
|
|
943
|
-
'const',
|
|
944
|
-
'continue',
|
|
945
|
-
'debugger',
|
|
946
|
-
'default',
|
|
947
|
-
'delete',
|
|
948
|
-
'do',
|
|
949
|
-
'else',
|
|
950
|
-
'export',
|
|
951
|
-
'finally',
|
|
952
|
-
'for',
|
|
953
|
-
'function',
|
|
954
|
-
'if',
|
|
955
|
-
'import',
|
|
956
|
-
'in',
|
|
957
|
-
'instanceof',
|
|
958
|
-
'let',
|
|
959
|
-
'new',
|
|
960
|
-
'return',
|
|
961
|
-
'switch',
|
|
962
|
-
'this',
|
|
963
|
-
'throw',
|
|
964
|
-
'try',
|
|
965
|
-
'typeof',
|
|
966
|
-
'var',
|
|
967
|
-
'void',
|
|
968
|
-
'while',
|
|
969
|
-
'with',
|
|
970
|
-
'yield',
|
|
971
|
-
]);
|
|
972
|
-
/** 式 → 評価関数のグローバルキャッシュ */
|
|
973
|
-
Expression.EXPRESSION_CACHE = new Map();
|
|
974
|
-
export default Expression;
|
|
975
|
-
//# sourceMappingURL=expression.js.map
|