3
3
* @author Michał Sajnóg
4
4
*/
5
5
'use strict'
6
-
6
+ const { ReferenceTracker , findVariable } = require ( 'eslint-utils' )
7
7
const utils = require ( '../utils' )
8
8
9
9
/**
@@ -31,6 +31,8 @@ module.exports = {
31
31
create ( context ) {
32
32
/** @type {Map<ObjectExpression, ComponentComputedProperty[]> } */
33
33
const computedPropertiesMap = new Map ( )
34
+ /** @type {Array<FunctionExpression | ArrowFunctionExpression> } */
35
+ const computedCallNodes = [ ]
34
36
35
37
/**
36
38
* @typedef {object } ScopeStack
@@ -54,56 +56,130 @@ module.exports = {
54
56
scopeStack = scopeStack && scopeStack . upper
55
57
}
56
58
57
- return utils . defineVueVisitor ( context , {
58
- onVueObjectEnter ( node ) {
59
- computedPropertiesMap . set ( node , utils . getComputedProperties ( node ) )
60
- } ,
61
- ':function' : onFunctionEnter ,
62
- ':function:exit' : onFunctionExit ,
63
-
64
- /**
65
- * @param {(Identifier | ThisExpression) & {parent: MemberExpression} } node
66
- * @param {VueObjectData } data
67
- */
68
- 'MemberExpression > :matches(Identifier, ThisExpression)' (
69
- node ,
70
- { node : vueNode }
71
- ) {
72
- if ( ! scopeStack ) {
73
- return
74
- }
75
- const targetBody = scopeStack . body
76
- const computedProperty = /** @type {ComponentComputedProperty[] } */ ( computedPropertiesMap . get (
77
- vueNode
78
- ) ) . find ( ( cp ) => {
79
- return (
80
- cp . value &&
81
- node . loc . start . line >= cp . value . loc . start . line &&
82
- node . loc . end . line <= cp . value . loc . end . line &&
83
- targetBody === cp . value
84
- )
85
- } )
86
- if ( ! computedProperty ) {
87
- return
88
- }
59
+ return Object . assign (
60
+ {
61
+ Program ( ) {
62
+ const tracker = new ReferenceTracker ( context . getScope ( ) )
63
+ const traceMap = utils . createCompositionApiTraceMap ( {
64
+ [ ReferenceTracker . ESM ] : true ,
65
+ computed : {
66
+ [ ReferenceTracker . CALL ] : true
67
+ }
68
+ } )
89
69
90
- if ( ! utils . isThis ( node , context ) ) {
91
- return
92
- }
93
- const mem = node . parent
94
- if ( mem . object !== node ) {
95
- return
70
+ for ( const { node } of tracker . iterateEsmReferences ( traceMap ) ) {
71
+ if ( node . type !== 'CallExpression' ) {
72
+ continue
73
+ }
74
+
75
+ const getterBody = utils . getGetterBodyFromComputedFunction ( node )
76
+ if ( getterBody ) {
77
+ computedCallNodes . push ( getterBody )
78
+ }
79
+ }
96
80
}
81
+ } ,
82
+ utils . defineVueVisitor ( context , {
83
+ onVueObjectEnter ( node ) {
84
+ computedPropertiesMap . set ( node , utils . getComputedProperties ( node ) )
85
+ } ,
86
+ ':function' : onFunctionEnter ,
87
+ ':function:exit' : onFunctionExit ,
88
+
89
+ /**
90
+ * @param {(Identifier | ThisExpression) & {parent: MemberExpression} } node
91
+ * @param {VueObjectData } data
92
+ */
93
+ 'MemberExpression > :matches(Identifier, ThisExpression)' (
94
+ node ,
95
+ { node : vueNode }
96
+ ) {
97
+ if ( ! scopeStack ) {
98
+ return
99
+ }
100
+ const targetBody = scopeStack . body
97
101
98
- const invalid = utils . findMutating ( mem )
99
- if ( invalid ) {
100
- context . report ( {
101
- node : invalid . node ,
102
- message : 'Unexpected side effect in "{{key}}" computed property.' ,
103
- data : { key : computedProperty . key || 'Unknown' }
102
+ const computedProperty = /** @type {ComponentComputedProperty[] } */ ( computedPropertiesMap . get (
103
+ vueNode
104
+ ) ) . find ( ( cp ) => {
105
+ return (
106
+ cp . value &&
107
+ node . loc . start . line >= cp . value . loc . start . line &&
108
+ node . loc . end . line <= cp . value . loc . end . line &&
109
+ targetBody === cp . value
110
+ )
104
111
} )
112
+ if ( computedProperty ) {
113
+ if ( ! utils . isThis ( node , context ) ) {
114
+ return
115
+ }
116
+ const mem = node . parent
117
+ if ( mem . object !== node ) {
118
+ return
119
+ }
120
+
121
+ const invalid = utils . findMutating ( mem )
122
+ if ( invalid ) {
123
+ context . report ( {
124
+ node : invalid . node ,
125
+ message :
126
+ 'Unexpected side effect in "{{key}}" computed property.' ,
127
+ data : { key : computedProperty . key || 'Unknown' }
128
+ } )
129
+ }
130
+ return
131
+ }
132
+
133
+ // ignore `this` for computed functions
134
+ if ( node . type === 'ThisExpression' ) {
135
+ return
136
+ }
137
+
138
+ const computedFunction = computedCallNodes . find (
139
+ ( c ) =>
140
+ node . loc . start . line >= c . loc . start . line &&
141
+ node . loc . end . line <= c . loc . end . line
142
+ )
143
+ if ( ! computedFunction ) {
144
+ return
145
+ }
146
+
147
+ const mem = node . parent
148
+ if ( mem . object !== node ) {
149
+ return
150
+ }
151
+
152
+ const variable = findVariable ( context . getScope ( ) , node )
153
+ if ( variable ) {
154
+ if ( variable . defs . length !== 1 ) {
155
+ return
156
+ }
157
+
158
+ const def = variable . defs [ 0 ]
159
+ if (
160
+ def . type !== 'ImplicitGlobalVariable' &&
161
+ def . type !== 'TDZ' &&
162
+ def . type !== 'ImportBinding'
163
+ ) {
164
+ if (
165
+ def . node . loc . start . line >= computedFunction . loc . start . line &&
166
+ def . node . loc . end . line <= computedFunction . loc . end . line
167
+ ) {
168
+ // mutating local variables are accepted
169
+ return
170
+ }
171
+ }
172
+ }
173
+
174
+ const invalid = utils . findMutating ( mem )
175
+ if ( invalid ) {
176
+ context . report ( {
177
+ node : invalid . node ,
178
+ message : 'Unexpected side effect in computed function.'
179
+ } )
180
+ }
105
181
}
106
- }
107
- } )
182
+ } )
183
+ )
108
184
}
109
185
}
0 commit comments