@reactive-vscode/reactivity 0.0.1-beta.5 → 0.0.1-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,120 +1,120 @@
1
- import { warn } from './warning'
2
- import { isArray, isFunction, isPromise } from '@vue/shared'
3
- import { LifecycleHooks } from './enums'
4
-
5
- // contexts where user provided function may be executed, in addition to
6
- // lifecycle hooks.
7
- export enum ErrorCodes {
8
- SETUP_FUNCTION,
9
- RENDER_FUNCTION,
10
- WATCH_GETTER,
11
- WATCH_CALLBACK,
12
- WATCH_CLEANUP,
13
- NATIVE_EVENT_HANDLER,
14
- COMPONENT_EVENT_HANDLER,
15
- VNODE_HOOK,
16
- DIRECTIVE_HOOK,
17
- TRANSITION_HOOK,
18
- APP_ERROR_HANDLER,
19
- APP_WARN_HANDLER,
20
- FUNCTION_REF,
21
- ASYNC_COMPONENT_LOADER,
22
- SCHEDULER,
23
- }
24
-
25
- export const ErrorTypeStrings: Record<LifecycleHooks | ErrorCodes, string> = {
26
- [LifecycleHooks.ACTIVATED]: 'activated hook',
27
- [LifecycleHooks.DEACTIVATED]: 'deactivated hook',
28
- [ErrorCodes.SETUP_FUNCTION]: 'setup function',
29
- [ErrorCodes.RENDER_FUNCTION]: 'render function',
30
- [ErrorCodes.WATCH_GETTER]: 'watcher getter',
31
- [ErrorCodes.WATCH_CALLBACK]: 'watcher callback',
32
- [ErrorCodes.WATCH_CLEANUP]: 'watcher cleanup function',
33
- [ErrorCodes.NATIVE_EVENT_HANDLER]: 'native event handler',
34
- [ErrorCodes.COMPONENT_EVENT_HANDLER]: 'component event handler',
35
- [ErrorCodes.VNODE_HOOK]: 'vnode hook',
36
- [ErrorCodes.DIRECTIVE_HOOK]: 'directive hook',
37
- [ErrorCodes.TRANSITION_HOOK]: 'transition hook',
38
- [ErrorCodes.APP_ERROR_HANDLER]: 'app errorHandler',
39
- [ErrorCodes.APP_WARN_HANDLER]: 'app warnHandler',
40
- [ErrorCodes.FUNCTION_REF]: 'ref function',
41
- [ErrorCodes.ASYNC_COMPONENT_LOADER]: 'async component loader',
42
- [ErrorCodes.SCHEDULER]:
43
- 'scheduler flush. This is likely a Vue internals bug. ' +
44
- 'Please open an issue at https://github.com/vuejs/core .',
45
- }
46
-
47
- export type ErrorTypes = LifecycleHooks | ErrorCodes
48
-
49
- export function callWithErrorHandling(
50
- fn: Function,
51
- instance: null,
52
- type: ErrorTypes,
53
- args?: unknown[],
54
- ) {
55
- try {
56
- return args ? fn(...args) : fn()
57
- } catch (err) {
58
- handleError(err, instance, type)
59
- }
60
- }
61
-
62
- export function callWithAsyncErrorHandling(
63
- fn: Function | Function[],
64
- instance: null,
65
- type: ErrorTypes,
66
- args?: unknown[],
67
- ): any {
68
- if (isFunction(fn)) {
69
- const res = callWithErrorHandling(fn, instance, type, args)
70
- if (res && isPromise(res)) {
71
- res.catch(err => {
72
- handleError(err, instance, type)
73
- })
74
- }
75
- return res
76
- }
77
-
78
- if (isArray(fn)) {
79
- const values = []
80
- for (let i = 0; i < fn.length; i++) {
81
- values.push(callWithAsyncErrorHandling(fn[i], instance, type, args))
82
- }
83
- return values
84
- } else if (__DEV__) {
85
- warn(
86
- `Invalid value type passed to callWithAsyncErrorHandling(): ${typeof fn}`,
87
- )
88
- }
89
- }
90
-
91
- export function handleError(
92
- err: unknown,
93
- instance: null,
94
- type: ErrorTypes,
95
- throwInDev = true,
96
- ) {
97
- const contextVNode = null
98
- logError(err, type, contextVNode, throwInDev)
99
- }
100
-
101
- function logError(
102
- err: unknown,
103
- type: ErrorTypes,
104
- contextVNode: null,
105
- throwInDev = true,
106
- ) {
107
- if (__DEV__) {
108
- const info = ErrorTypeStrings[type]
109
- warn(`Unhandled error${info ? ` during execution of ${info}` : ``}`)
110
- // crash in dev by default so it's more noticeable
111
- if (throwInDev) {
112
- throw err
113
- } else {
114
- console.error(err)
115
- }
116
- } else {
117
- // recover in prod to reduce the impact on end-user
118
- console.error(err)
119
- }
1
+ import { warn } from './warning'
2
+ import { isArray, isFunction, isPromise } from '@vue/shared'
3
+ import { LifecycleHooks } from './enums'
4
+
5
+ // contexts where user provided function may be executed, in addition to
6
+ // lifecycle hooks.
7
+ export enum ErrorCodes {
8
+ SETUP_FUNCTION,
9
+ RENDER_FUNCTION,
10
+ WATCH_GETTER,
11
+ WATCH_CALLBACK,
12
+ WATCH_CLEANUP,
13
+ NATIVE_EVENT_HANDLER,
14
+ COMPONENT_EVENT_HANDLER,
15
+ VNODE_HOOK,
16
+ DIRECTIVE_HOOK,
17
+ TRANSITION_HOOK,
18
+ APP_ERROR_HANDLER,
19
+ APP_WARN_HANDLER,
20
+ FUNCTION_REF,
21
+ ASYNC_COMPONENT_LOADER,
22
+ SCHEDULER,
23
+ }
24
+
25
+ export const ErrorTypeStrings: Record<LifecycleHooks | ErrorCodes, string> = {
26
+ [LifecycleHooks.ACTIVATED]: 'activated hook',
27
+ [LifecycleHooks.DEACTIVATED]: 'deactivated hook',
28
+ [ErrorCodes.SETUP_FUNCTION]: 'setup function',
29
+ [ErrorCodes.RENDER_FUNCTION]: 'render function',
30
+ [ErrorCodes.WATCH_GETTER]: 'watcher getter',
31
+ [ErrorCodes.WATCH_CALLBACK]: 'watcher callback',
32
+ [ErrorCodes.WATCH_CLEANUP]: 'watcher cleanup function',
33
+ [ErrorCodes.NATIVE_EVENT_HANDLER]: 'native event handler',
34
+ [ErrorCodes.COMPONENT_EVENT_HANDLER]: 'component event handler',
35
+ [ErrorCodes.VNODE_HOOK]: 'vnode hook',
36
+ [ErrorCodes.DIRECTIVE_HOOK]: 'directive hook',
37
+ [ErrorCodes.TRANSITION_HOOK]: 'transition hook',
38
+ [ErrorCodes.APP_ERROR_HANDLER]: 'app errorHandler',
39
+ [ErrorCodes.APP_WARN_HANDLER]: 'app warnHandler',
40
+ [ErrorCodes.FUNCTION_REF]: 'ref function',
41
+ [ErrorCodes.ASYNC_COMPONENT_LOADER]: 'async component loader',
42
+ [ErrorCodes.SCHEDULER]:
43
+ 'scheduler flush. This is likely a Vue internals bug. ' +
44
+ 'Please open an issue at https://github.com/vuejs/core .',
45
+ }
46
+
47
+ export type ErrorTypes = LifecycleHooks | ErrorCodes
48
+
49
+ export function callWithErrorHandling(
50
+ fn: Function,
51
+ instance: null,
52
+ type: ErrorTypes,
53
+ args?: unknown[],
54
+ ) {
55
+ try {
56
+ return args ? fn(...args) : fn()
57
+ } catch (err) {
58
+ handleError(err, instance, type)
59
+ }
60
+ }
61
+
62
+ export function callWithAsyncErrorHandling(
63
+ fn: Function | Function[],
64
+ instance: null,
65
+ type: ErrorTypes,
66
+ args?: unknown[],
67
+ ): any {
68
+ if (isFunction(fn)) {
69
+ const res = callWithErrorHandling(fn, instance, type, args)
70
+ if (res && isPromise(res)) {
71
+ res.catch(err => {
72
+ handleError(err, instance, type)
73
+ })
74
+ }
75
+ return res
76
+ }
77
+
78
+ if (isArray(fn)) {
79
+ const values = []
80
+ for (let i = 0; i < fn.length; i++) {
81
+ values.push(callWithAsyncErrorHandling(fn[i], instance, type, args))
82
+ }
83
+ return values
84
+ } else if (__DEV__) {
85
+ warn(
86
+ `Invalid value type passed to callWithAsyncErrorHandling(): ${typeof fn}`,
87
+ )
88
+ }
89
+ }
90
+
91
+ export function handleError(
92
+ err: unknown,
93
+ instance: null,
94
+ type: ErrorTypes,
95
+ throwInDev = true,
96
+ ) {
97
+ const contextVNode = null
98
+ logError(err, type, contextVNode, throwInDev)
99
+ }
100
+
101
+ function logError(
102
+ err: unknown,
103
+ type: ErrorTypes,
104
+ contextVNode: null,
105
+ throwInDev = true,
106
+ ) {
107
+ if (__DEV__) {
108
+ const info = ErrorTypeStrings[type]
109
+ warn(`Unhandled error${info ? ` during execution of ${info}` : ``}`)
110
+ // crash in dev by default so it's more noticeable
111
+ if (throwInDev) {
112
+ throw err
113
+ } else {
114
+ console.error(err)
115
+ }
116
+ } else {
117
+ // recover in prod to reduce the impact on end-user
118
+ console.error(err)
119
+ }
120
120
  }
package/src/scheduler.ts CHANGED
@@ -1,211 +1,211 @@
1
- import { ErrorCodes, callWithErrorHandling, handleError } from './errorHandling'
2
- import { type Awaited, NOOP, isArray } from '@vue/shared'
3
-
4
- export interface SchedulerJob extends Function {
5
- id?: number
6
- pre?: boolean
7
- active?: boolean
8
- computed?: boolean
9
- /**
10
- * Indicates whether the effect is allowed to recursively trigger itself
11
- * when managed by the scheduler.
12
- *
13
- * By default, a job cannot trigger itself because some built-in method calls,
14
- * e.g. Array.prototype.push actually performs reads as well (#1740) which
15
- * can lead to confusing infinite loops.
16
- * The allowed cases are component update functions and watch callbacks.
17
- * Component update functions may update child component props, which in turn
18
- * trigger flush: "pre" watch callbacks that mutates state that the parent
19
- * relies on (#1801). Watch callbacks doesn't track its dependencies so if it
20
- * triggers itself again, it's likely intentional and it is the user's
21
- * responsibility to perform recursive state mutation that eventually
22
- * stabilizes (#1727).
23
- */
24
- allowRecurse?: boolean
25
- }
26
-
27
- export type SchedulerJobs = SchedulerJob | SchedulerJob[]
28
-
29
- let isFlushing = false
30
- let isFlushPending = false
31
-
32
- const queue: SchedulerJob[] = []
33
- let flushIndex = 0
34
-
35
- const resolvedPromise = /*#__PURE__*/ Promise.resolve() as Promise<any>
36
- let currentFlushPromise: Promise<void> | null = null
37
-
38
- const RECURSION_LIMIT = 100
39
- type CountMap = Map<SchedulerJob, number>
40
-
41
- export function nextTick<T = void, R = void>(
42
- this: T,
43
- fn?: (this: T) => R,
44
- ): Promise<Awaited<R>> {
45
- const p = currentFlushPromise || resolvedPromise
46
- return fn ? p.then(this ? fn.bind(this) : fn) : p
47
- }
48
-
49
- // #2768
50
- // Use binary-search to find a suitable position in the queue,
51
- // so that the queue maintains the increasing order of job's id,
52
- // which can prevent the job from being skipped and also can avoid repeated patching.
53
- function findInsertionIndex(id: number) {
54
- // the start index should be `flushIndex + 1`
55
- let start = flushIndex + 1
56
- let end = queue.length
57
-
58
- while (start < end) {
59
- const middle = (start + end) >>> 1
60
- const middleJob = queue[middle]
61
- const middleJobId = getId(middleJob)
62
- if (middleJobId < id || (middleJobId === id && middleJob.pre)) {
63
- start = middle + 1
64
- } else {
65
- end = middle
66
- }
67
- }
68
-
69
- return start
70
- }
71
-
72
- export function queueJob(job: SchedulerJob) {
73
- // the dedupe search uses the startIndex argument of Array.includes()
74
- // by default the search index includes the current job that is being run
75
- // so it cannot recursively trigger itself again.
76
- // if the job is a watch() callback, the search will start with a +1 index to
77
- // allow it recursively trigger itself - it is the user's responsibility to
78
- // ensure it doesn't end up in an infinite loop.
79
- if (
80
- !queue.length ||
81
- !queue.includes(
82
- job,
83
- isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex,
84
- )
85
- ) {
86
- if (job.id == null) {
87
- queue.push(job)
88
- } else {
89
- queue.splice(findInsertionIndex(job.id), 0, job)
90
- }
91
- queueFlush()
92
- }
93
- }
94
-
95
- function queueFlush() {
96
- if (!isFlushing && !isFlushPending) {
97
- isFlushPending = true
98
- currentFlushPromise = resolvedPromise.then(flushJobs)
99
- }
100
- }
101
-
102
- export function invalidateJob(job: SchedulerJob) {
103
- const i = queue.indexOf(job)
104
- if (i > flushIndex) {
105
- queue.splice(i, 1)
106
- }
107
- }
108
-
109
- export function flushPreFlushCbs(
110
- seen?: CountMap,
111
- // if currently flushing, skip the current job itself
112
- i = isFlushing ? flushIndex + 1 : 0,
113
- ) {
114
- if (__DEV__) {
115
- seen = seen || new Map()
116
- }
117
- for (; i < queue.length; i++) {
118
- const cb = queue[i]
119
- if (cb && cb.pre) {
120
- if (__DEV__ && checkRecursiveUpdates(seen!, cb)) {
121
- continue
122
- }
123
- queue.splice(i, 1)
124
- i--
125
- cb()
126
- }
127
- }
128
- }
129
-
130
- const getId = (job: SchedulerJob): number =>
131
- job.id == null ? Infinity : job.id
132
-
133
- const comparator = (a: SchedulerJob, b: SchedulerJob): number => {
134
- const diff = getId(a) - getId(b)
135
- if (diff === 0) {
136
- if (a.pre && !b.pre) return -1
137
- if (b.pre && !a.pre) return 1
138
- }
139
- return diff
140
- }
141
-
142
- function flushJobs(seen?: CountMap) {
143
- isFlushPending = false
144
- isFlushing = true
145
- if (__DEV__) {
146
- seen = seen || new Map()
147
- }
148
-
149
- // Sort queue before flush.
150
- // This ensures that:
151
- // 1. Components are updated from parent to child. (because parent is always
152
- // created before the child so its render effect will have smaller
153
- // priority number)
154
- // 2. If a component is unmounted during a parent component's update,
155
- // its update can be skipped.
156
- queue.sort(comparator)
157
-
158
- // conditional usage of checkRecursiveUpdate must be determined out of
159
- // try ... catch block since Rollup by default de-optimizes treeshaking
160
- // inside try-catch. This can leave all warning code unshaked. Although
161
- // they would get eventually shaken by a minifier like terser, some minifiers
162
- // would fail to do that (e.g. https://github.com/evanw/esbuild/issues/1610)
163
- const check = __DEV__
164
- ? (job: SchedulerJob) => checkRecursiveUpdates(seen!, job)
165
- : NOOP
166
-
167
- try {
168
- for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
169
- const job = queue[flushIndex]
170
- if (job && job.active !== false) {
171
- if (__DEV__ && check(job)) {
172
- continue
173
- }
174
- callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
175
- }
176
- }
177
- } finally {
178
- flushIndex = 0
179
- queue.length = 0
180
-
181
- isFlushing = false
182
- currentFlushPromise = null
183
- // some postFlushCb queued jobs!
184
- // keep flushing until it drains.
185
- if (queue.length) {
186
- flushJobs(seen)
187
- }
188
- }
189
- }
190
-
191
- function checkRecursiveUpdates(seen: CountMap, fn: SchedulerJob) {
192
- if (!seen.has(fn)) {
193
- seen.set(fn, 1)
194
- } else {
195
- const count = seen.get(fn)!
196
- if (count > RECURSION_LIMIT) {
197
- handleError(
198
- `Maximum recursive updates exceeded. ` +
199
- `This means you have a reactive effect that is mutating its own ` +
200
- `dependencies and thus recursively triggering itself. Possible sources ` +
201
- `include component template, render function, updated hook or ` +
202
- `watcher source function.`,
203
- null,
204
- ErrorCodes.APP_ERROR_HANDLER,
205
- )
206
- return true
207
- } else {
208
- seen.set(fn, count + 1)
209
- }
210
- }
1
+ import { ErrorCodes, callWithErrorHandling, handleError } from './errorHandling'
2
+ import { type Awaited, NOOP, isArray } from '@vue/shared'
3
+
4
+ export interface SchedulerJob extends Function {
5
+ id?: number
6
+ pre?: boolean
7
+ active?: boolean
8
+ computed?: boolean
9
+ /**
10
+ * Indicates whether the effect is allowed to recursively trigger itself
11
+ * when managed by the scheduler.
12
+ *
13
+ * By default, a job cannot trigger itself because some built-in method calls,
14
+ * e.g. Array.prototype.push actually performs reads as well (#1740) which
15
+ * can lead to confusing infinite loops.
16
+ * The allowed cases are component update functions and watch callbacks.
17
+ * Component update functions may update child component props, which in turn
18
+ * trigger flush: "pre" watch callbacks that mutates state that the parent
19
+ * relies on (#1801). Watch callbacks doesn't track its dependencies so if it
20
+ * triggers itself again, it's likely intentional and it is the user's
21
+ * responsibility to perform recursive state mutation that eventually
22
+ * stabilizes (#1727).
23
+ */
24
+ allowRecurse?: boolean
25
+ }
26
+
27
+ export type SchedulerJobs = SchedulerJob | SchedulerJob[]
28
+
29
+ let isFlushing = false
30
+ let isFlushPending = false
31
+
32
+ const queue: SchedulerJob[] = []
33
+ let flushIndex = 0
34
+
35
+ const resolvedPromise = /*#__PURE__*/ Promise.resolve() as Promise<any>
36
+ let currentFlushPromise: Promise<void> | null = null
37
+
38
+ const RECURSION_LIMIT = 100
39
+ type CountMap = Map<SchedulerJob, number>
40
+
41
+ export function nextTick<T = void, R = void>(
42
+ this: T,
43
+ fn?: (this: T) => R,
44
+ ): Promise<Awaited<R>> {
45
+ const p = currentFlushPromise || resolvedPromise
46
+ return fn ? p.then(this ? fn.bind(this) : fn) : p
47
+ }
48
+
49
+ // #2768
50
+ // Use binary-search to find a suitable position in the queue,
51
+ // so that the queue maintains the increasing order of job's id,
52
+ // which can prevent the job from being skipped and also can avoid repeated patching.
53
+ function findInsertionIndex(id: number) {
54
+ // the start index should be `flushIndex + 1`
55
+ let start = flushIndex + 1
56
+ let end = queue.length
57
+
58
+ while (start < end) {
59
+ const middle = (start + end) >>> 1
60
+ const middleJob = queue[middle]
61
+ const middleJobId = getId(middleJob)
62
+ if (middleJobId < id || (middleJobId === id && middleJob.pre)) {
63
+ start = middle + 1
64
+ } else {
65
+ end = middle
66
+ }
67
+ }
68
+
69
+ return start
70
+ }
71
+
72
+ export function queueJob(job: SchedulerJob) {
73
+ // the dedupe search uses the startIndex argument of Array.includes()
74
+ // by default the search index includes the current job that is being run
75
+ // so it cannot recursively trigger itself again.
76
+ // if the job is a watch() callback, the search will start with a +1 index to
77
+ // allow it recursively trigger itself - it is the user's responsibility to
78
+ // ensure it doesn't end up in an infinite loop.
79
+ if (
80
+ !queue.length ||
81
+ !queue.includes(
82
+ job,
83
+ isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex,
84
+ )
85
+ ) {
86
+ if (job.id == null) {
87
+ queue.push(job)
88
+ } else {
89
+ queue.splice(findInsertionIndex(job.id), 0, job)
90
+ }
91
+ queueFlush()
92
+ }
93
+ }
94
+
95
+ function queueFlush() {
96
+ if (!isFlushing && !isFlushPending) {
97
+ isFlushPending = true
98
+ currentFlushPromise = resolvedPromise.then(flushJobs)
99
+ }
100
+ }
101
+
102
+ export function invalidateJob(job: SchedulerJob) {
103
+ const i = queue.indexOf(job)
104
+ if (i > flushIndex) {
105
+ queue.splice(i, 1)
106
+ }
107
+ }
108
+
109
+ export function flushPreFlushCbs(
110
+ seen?: CountMap,
111
+ // if currently flushing, skip the current job itself
112
+ i = isFlushing ? flushIndex + 1 : 0,
113
+ ) {
114
+ if (__DEV__) {
115
+ seen = seen || new Map()
116
+ }
117
+ for (; i < queue.length; i++) {
118
+ const cb = queue[i]
119
+ if (cb && cb.pre) {
120
+ if (__DEV__ && checkRecursiveUpdates(seen!, cb)) {
121
+ continue
122
+ }
123
+ queue.splice(i, 1)
124
+ i--
125
+ cb()
126
+ }
127
+ }
128
+ }
129
+
130
+ const getId = (job: SchedulerJob): number =>
131
+ job.id == null ? Infinity : job.id
132
+
133
+ const comparator = (a: SchedulerJob, b: SchedulerJob): number => {
134
+ const diff = getId(a) - getId(b)
135
+ if (diff === 0) {
136
+ if (a.pre && !b.pre) return -1
137
+ if (b.pre && !a.pre) return 1
138
+ }
139
+ return diff
140
+ }
141
+
142
+ function flushJobs(seen?: CountMap) {
143
+ isFlushPending = false
144
+ isFlushing = true
145
+ if (__DEV__) {
146
+ seen = seen || new Map()
147
+ }
148
+
149
+ // Sort queue before flush.
150
+ // This ensures that:
151
+ // 1. Components are updated from parent to child. (because parent is always
152
+ // created before the child so its render effect will have smaller
153
+ // priority number)
154
+ // 2. If a component is unmounted during a parent component's update,
155
+ // its update can be skipped.
156
+ queue.sort(comparator)
157
+
158
+ // conditional usage of checkRecursiveUpdate must be determined out of
159
+ // try ... catch block since Rollup by default de-optimizes treeshaking
160
+ // inside try-catch. This can leave all warning code unshaked. Although
161
+ // they would get eventually shaken by a minifier like terser, some minifiers
162
+ // would fail to do that (e.g. https://github.com/evanw/esbuild/issues/1610)
163
+ const check = __DEV__
164
+ ? (job: SchedulerJob) => checkRecursiveUpdates(seen!, job)
165
+ : NOOP
166
+
167
+ try {
168
+ for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
169
+ const job = queue[flushIndex]
170
+ if (job && job.active !== false) {
171
+ if (__DEV__ && check(job)) {
172
+ continue
173
+ }
174
+ callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
175
+ }
176
+ }
177
+ } finally {
178
+ flushIndex = 0
179
+ queue.length = 0
180
+
181
+ isFlushing = false
182
+ currentFlushPromise = null
183
+ // some postFlushCb queued jobs!
184
+ // keep flushing until it drains.
185
+ if (queue.length) {
186
+ flushJobs(seen)
187
+ }
188
+ }
189
+ }
190
+
191
+ function checkRecursiveUpdates(seen: CountMap, fn: SchedulerJob) {
192
+ if (!seen.has(fn)) {
193
+ seen.set(fn, 1)
194
+ } else {
195
+ const count = seen.get(fn)!
196
+ if (count > RECURSION_LIMIT) {
197
+ handleError(
198
+ `Maximum recursive updates exceeded. ` +
199
+ `This means you have a reactive effect that is mutating its own ` +
200
+ `dependencies and thus recursively triggering itself. Possible sources ` +
201
+ `include component template, render function, updated hook or ` +
202
+ `watcher source function.`,
203
+ null,
204
+ ErrorCodes.APP_ERROR_HANDLER,
205
+ )
206
+ return true
207
+ } else {
208
+ seen.set(fn, count + 1)
209
+ }
210
+ }
211
211
  }