@@ -8,6 +8,7 @@ const utils = require('../utils')
8
8
9
9
/**
10
10
* @typedef {import('../utils').VueObjectData } VueObjectData
11
+ * @typedef {import('../utils').VueVisitor } VueVisitor
11
12
* @typedef {import('../utils').ComponentComputedProperty } ComponentComputedProperty
12
13
*/
13
14
@@ -32,8 +33,8 @@ module.exports = {
32
33
const computedPropertiesMap = new Map ( )
33
34
/** @type {Array<FunctionExpression | ArrowFunctionExpression> } */
34
35
const computedCallNodes = [ ]
35
- /** @type {Array<FunctionExpression | ArrowFunctionExpression | FunctionDeclaration> } */
36
- const setupFunctions = [ ]
36
+ /** @type {[number, number][] } */
37
+ const setupRanges = [ ]
37
38
38
39
/**
39
40
* @typedef {object } ScopeStack
@@ -57,7 +58,114 @@ module.exports = {
57
58
scopeStack = scopeStack && scopeStack . upper
58
59
}
59
60
60
- return Object . assign (
61
+ const nodeVisitor = {
62
+ ':function' : onFunctionEnter ,
63
+ ':function:exit' : onFunctionExit ,
64
+
65
+ /**
66
+ * @param {(Identifier | ThisExpression) & {parent: MemberExpression} } node
67
+ * @param {VueObjectData|undefined } [info]
68
+ */
69
+ 'MemberExpression > :matches(Identifier, ThisExpression)' ( node , info ) {
70
+ if ( ! scopeStack ) {
71
+ return
72
+ }
73
+ const targetBody = scopeStack . body
74
+
75
+ const computedProperty = (
76
+ info ? computedPropertiesMap . get ( info . node ) || [ ] : [ ]
77
+ ) . find ( ( cp ) => {
78
+ return (
79
+ cp . value &&
80
+ cp . value . range [ 0 ] <= node . range [ 0 ] &&
81
+ node . range [ 1 ] <= cp . value . range [ 1 ] &&
82
+ targetBody === cp . value
83
+ )
84
+ } )
85
+ if ( computedProperty ) {
86
+ if ( ! utils . isThis ( node , context ) ) {
87
+ return
88
+ }
89
+ const mem = node . parent
90
+ if ( mem . object !== node ) {
91
+ return
92
+ }
93
+
94
+ const invalid = utils . findMutating ( mem )
95
+ if ( invalid ) {
96
+ context . report ( {
97
+ node : invalid . node ,
98
+ message : 'Unexpected side effect in "{{key}}" computed property.' ,
99
+ data : { key : computedProperty . key || 'Unknown' }
100
+ } )
101
+ }
102
+ return
103
+ }
104
+
105
+ // ignore `this` for computed functions
106
+ if ( node . type === 'ThisExpression' ) {
107
+ return
108
+ }
109
+
110
+ const computedFunction = computedCallNodes . find (
111
+ ( c ) =>
112
+ c . range [ 0 ] <= node . range [ 0 ] &&
113
+ node . range [ 1 ] <= c . range [ 1 ] &&
114
+ targetBody === c . body
115
+ )
116
+ if ( ! computedFunction ) {
117
+ return
118
+ }
119
+
120
+ const mem = node . parent
121
+ if ( mem . object !== node ) {
122
+ return
123
+ }
124
+
125
+ const variable = findVariable ( context . getScope ( ) , node )
126
+ if ( ! variable || variable . defs . length !== 1 ) {
127
+ return
128
+ }
129
+
130
+ const def = variable . defs [ 0 ]
131
+ if (
132
+ def . type === 'ImplicitGlobalVariable' ||
133
+ def . type === 'TDZ' ||
134
+ def . type === 'ImportBinding'
135
+ ) {
136
+ return
137
+ }
138
+
139
+ const isDeclaredInsideSetup = setupRanges . some (
140
+ ( [ start , end ] ) =>
141
+ start <= def . node . range [ 0 ] && def . node . range [ 1 ] <= end
142
+ )
143
+ if ( ! isDeclaredInsideSetup ) {
144
+ return
145
+ }
146
+
147
+ if (
148
+ computedFunction . range [ 0 ] <= def . node . range [ 0 ] &&
149
+ def . node . range [ 1 ] <= computedFunction . range [ 1 ]
150
+ ) {
151
+ // mutating local variables are accepted
152
+ return
153
+ }
154
+
155
+ const invalid = utils . findMutating ( node )
156
+ if ( invalid ) {
157
+ context . report ( {
158
+ node : invalid . node ,
159
+ message : 'Unexpected side effect in computed function.'
160
+ } )
161
+ }
162
+ }
163
+ }
164
+ const scriptSetupNode = utils . getScriptSetupElement ( context )
165
+ if ( scriptSetupNode ) {
166
+ setupRanges . push ( scriptSetupNode . range )
167
+ }
168
+ return utils . compositingVisitors (
61
169
{
62
170
Program ( ) {
63
171
const tracker = new ReferenceTracker ( context . getScope ( ) )
@@ -80,120 +188,17 @@ module.exports = {
80
188
}
81
189
}
82
190
} ,
83
- utils . defineVueVisitor ( context , {
84
- onVueObjectEnter ( node ) {
85
- computedPropertiesMap . set ( node , utils . getComputedProperties ( node ) )
86
- } ,
87
- ':function' : onFunctionEnter ,
88
- ':function:exit' : onFunctionExit ,
89
- onSetupFunctionEnter ( node ) {
90
- setupFunctions . push ( node )
91
- } ,
92
-
93
- /**
94
- * @param {(Identifier | ThisExpression) & {parent: MemberExpression} } node
95
- * @param {VueObjectData } data
96
- */
97
- 'MemberExpression > :matches(Identifier, ThisExpression)' (
98
- node ,
99
- { node : vueNode }
100
- ) {
101
- if ( ! scopeStack ) {
102
- return
103
- }
104
- const targetBody = scopeStack . body
105
-
106
- const computedProperty = /** @type {ComponentComputedProperty[] } */ (
107
- computedPropertiesMap . get ( vueNode )
108
- ) . find ( ( cp ) => {
109
- return (
110
- cp . value &&
111
- node . loc . start . line >= cp . value . loc . start . line &&
112
- node . loc . end . line <= cp . value . loc . end . line &&
113
- targetBody === cp . value
114
- )
191
+ scriptSetupNode
192
+ ? utils . defineScriptSetupVisitor ( context , nodeVisitor )
193
+ : utils . defineVueVisitor ( context , {
194
+ onVueObjectEnter ( node ) {
195
+ computedPropertiesMap . set ( node , utils . getComputedProperties ( node ) )
196
+ } ,
197
+ onSetupFunctionEnter ( node ) {
198
+ setupRanges . push ( node . body . range )
199
+ } ,
200
+ ...nodeVisitor
115
201
} )
116
- if ( computedProperty ) {
117
- if ( ! utils . isThis ( node , context ) ) {
118
- return
119
- }
120
- const mem = node . parent
121
- if ( mem . object !== node ) {
122
- return
123
- }
124
-
125
- const invalid = utils . findMutating ( mem )
126
- if ( invalid ) {
127
- context . report ( {
128
- node : invalid . node ,
129
- message :
130
- 'Unexpected side effect in "{{key}}" computed property.' ,
131
- data : { key : computedProperty . key || 'Unknown' }
132
- } )
133
- }
134
- return
135
- }
136
-
137
- // ignore `this` for computed functions
138
- if ( node . type === 'ThisExpression' ) {
139
- return
140
- }
141
-
142
- const computedFunction = computedCallNodes . find (
143
- ( c ) =>
144
- node . loc . start . line >= c . loc . start . line &&
145
- node . loc . end . line <= c . loc . end . line &&
146
- targetBody === c . body
147
- )
148
- if ( ! computedFunction ) {
149
- return
150
- }
151
-
152
- const mem = node . parent
153
- if ( mem . object !== node ) {
154
- return
155
- }
156
-
157
- const variable = findVariable ( context . getScope ( ) , node )
158
- if ( ! variable || variable . defs . length !== 1 ) {
159
- return
160
- }
161
-
162
- const def = variable . defs [ 0 ]
163
- if (
164
- def . type === 'ImplicitGlobalVariable' ||
165
- def . type === 'TDZ' ||
166
- def . type === 'ImportBinding'
167
- ) {
168
- return
169
- }
170
-
171
- const isDeclaredInsideSetup = setupFunctions . some (
172
- ( setupFn ) =>
173
- def . node . loc . start . line >= setupFn . loc . start . line &&
174
- def . node . loc . end . line <= setupFn . loc . end . line
175
- )
176
- if ( ! isDeclaredInsideSetup ) {
177
- return
178
- }
179
-
180
- if (
181
- def . node . loc . start . line >= computedFunction . loc . start . line &&
182
- def . node . loc . end . line <= computedFunction . loc . end . line
183
- ) {
184
- // mutating local variables are accepted
185
- return
186
- }
187
-
188
- const invalid = utils . findMutating ( node )
189
- if ( invalid ) {
190
- context . report ( {
191
- node : invalid . node ,
192
- message : 'Unexpected side effect in computed function.'
193
- } )
194
- }
195
- }
196
- } )
197
202
)
198
203
}
199
204
}
0 commit comments