@@ -4,6 +4,13 @@ import type { TSESTree } from '@typescript-eslint/types';
4
4
import { findVariable , isIn } from '../utils/ast-utils.js' ;
5
5
import { getSvelteContext } from '../utils/svelte-context.js' ;
6
6
7
+ type FunctionLike =
8
+ | TSESTree . ArrowFunctionExpression
9
+ | TSESTree . FunctionDeclaration
10
+ | TSESTree . MethodDefinition ;
11
+
12
+ type VariableLike = TSESTree . VariableDeclarator | TSESTree . PropertyDefinition ;
13
+
7
14
export default createRule ( 'prefer-svelte-reactivity' , {
8
15
meta : {
9
16
docs : {
@@ -44,10 +51,7 @@ export default createRule('prefer-svelte-reactivity', {
44
51
create ( context ) {
45
52
const ignoreEncapsulatedLocalVariables =
46
53
context . options [ 0 ] ?. ignoreEncapsulatedLocalVariables ?? true ;
47
- const returnedVariables : Map <
48
- TSESTree . ArrowFunctionExpression | TSESTree . FunctionDeclaration ,
49
- TSESTree . VariableDeclarator [ ]
50
- > = new Map ( ) ;
54
+ const returnedVariables : Map < FunctionLike , VariableLike [ ] > = new Map ( ) ;
51
55
const exportedVars : TSESTree . Node [ ] = [ ] ;
52
56
return {
53
57
...( getSvelteContext ( context ) ?. svelteFileType === '.svelte.[js|ts]' && {
@@ -85,18 +89,35 @@ export default createRule('prefer-svelte-reactivity', {
85
89
if ( enclosingFunction === null ) {
86
90
return ;
87
91
}
88
- const variable = findVariable ( context , node ) ;
89
- if (
90
- variable === null ||
91
- variable . identifiers . length < 1 ||
92
- variable . identifiers [ 0 ] . parent . type !== 'VariableDeclarator'
93
- ) {
92
+ let variableDeclaration = null ;
93
+ if ( node . parent . type === 'MemberExpression' ) {
94
+ const enclosingClassBody = findEnclosingClassBody ( node ) ;
95
+ for ( const classElement of enclosingClassBody ?. body ?? [ ] ) {
96
+ if (
97
+ classElement . type === 'PropertyDefinition' &&
98
+ classElement . key . type === 'Identifier' &&
99
+ node . name === classElement . key . name
100
+ ) {
101
+ variableDeclaration = classElement ;
102
+ }
103
+ }
104
+ } else {
105
+ const variable = findVariable ( context , node ) ;
106
+ if (
107
+ variable !== null &&
108
+ variable . identifiers . length > 0 &&
109
+ variable . identifiers [ 0 ] . parent . type === 'VariableDeclarator'
110
+ ) {
111
+ variableDeclaration = variable . identifiers [ 0 ] . parent ;
112
+ }
113
+ }
114
+ if ( variableDeclaration === null ) {
94
115
return ;
95
116
}
96
117
if ( ! returnedVariables . has ( enclosingFunction ) ) {
97
118
returnedVariables . set ( enclosingFunction , [ ] ) ;
98
119
}
99
- returnedVariables . get ( enclosingFunction ) ?. push ( variable . identifiers [ 0 ] . parent ) ;
120
+ returnedVariables . get ( enclosingFunction ) ?. push ( variableDeclaration ) ;
100
121
} ,
101
122
'Program:exit' ( ) {
102
123
const referenceTracker = new ReferenceTracker ( context . sourceCode . scopeManager . globalScope ! ) ;
@@ -135,17 +156,25 @@ export default createRule('prefer-svelte-reactivity', {
135
156
} ) ;
136
157
}
137
158
}
138
- for ( const returnedVar of Array . from ( returnedVariables . values ( ) ) . flat ( ) ) {
139
- if ( isIn ( node , returnedVar ) ) {
140
- context . report ( {
141
- messageId,
142
- node
143
- } ) ;
159
+ for ( const [ fn , fnReturnVars ] of returnedVariables . entries ( ) ) {
160
+ if ( fn . type === 'MethodDefinition' ) {
161
+ continue ;
162
+ }
163
+ for ( const returnedVar of fnReturnVars ) {
164
+ if ( isIn ( node , returnedVar ) ) {
165
+ context . report ( {
166
+ messageId,
167
+ node
168
+ } ) ;
169
+ }
144
170
}
145
171
}
172
+ const enclosingPropertyDefinition = findEnclosingPropertyDefinition ( node ) ;
146
173
if (
147
174
findEnclosingReturn ( node ) !== null ||
148
- findEnclosingPropertyDefinition ( node ) ?. accessibility === 'public'
175
+ ( enclosingPropertyDefinition !== null &&
176
+ ( ! ignoreEncapsulatedLocalVariables ||
177
+ ! isPropertyEncapsulated ( enclosingPropertyDefinition , returnedVariables ) ) )
149
178
) {
150
179
context . report ( {
151
180
messageId,
@@ -207,10 +236,18 @@ function findAncestorOfTypes<T extends string>(
207
236
return findAncestorOfTypes ( node . parent , types ) ;
208
237
}
209
238
239
+ function findEnclosingClassBody ( node : TSESTree . Node ) : TSESTree . ClassBody | null {
240
+ return findAncestorOfTypes ( node , [ 'ClassBody' ] ) ;
241
+ }
242
+
210
243
function findEnclosingFunction (
211
244
node : TSESTree . Node
212
245
) : TSESTree . ArrowFunctionExpression | TSESTree . FunctionDeclaration | null {
213
- return findAncestorOfTypes ( node , [ 'ArrowFunctionExpression' , 'FunctionDeclaration' ] ) ;
246
+ return findAncestorOfTypes ( node , [
247
+ 'ArrowFunctionExpression' ,
248
+ 'FunctionDeclaration' ,
249
+ 'MethodDefinition'
250
+ ] ) ;
214
251
}
215
252
216
253
function findEnclosingPropertyDefinition ( node : TSESTree . Node ) : TSESTree . PropertyDefinition | null {
@@ -222,10 +259,7 @@ function findEnclosingReturn(node: TSESTree.Node): TSESTree.ReturnStatement | nu
222
259
}
223
260
224
261
function isLocalVarEncapsulated (
225
- returnedVariables : Map <
226
- TSESTree . ArrowFunctionExpression | TSESTree . FunctionDeclaration ,
227
- TSESTree . VariableDeclarator [ ]
228
- > ,
262
+ returnedVariables : Map < FunctionLike , VariableLike [ ] > ,
229
263
node : TSESTree . Node
230
264
) : boolean {
231
265
const enclosingFunction = findEnclosingFunction ( node ) ;
@@ -237,6 +271,33 @@ function isLocalVarEncapsulated(
237
271
) ;
238
272
}
239
273
274
+ function methodReturnsProperty (
275
+ method : TSESTree . MethodDefinition ,
276
+ property : TSESTree . PropertyDefinition ,
277
+ returnedVariables : Map < FunctionLike , VariableLike [ ] >
278
+ ) : boolean {
279
+ return returnedVariables . get ( method ) ?. includes ( property ) ?? false ;
280
+ }
281
+
282
+ function isPropertyEncapsulated (
283
+ node : TSESTree . PropertyDefinition ,
284
+ returnedVariables : Map < FunctionLike , VariableLike [ ] >
285
+ ) : boolean {
286
+ if ( node . accessibility === 'public' ) {
287
+ return false ;
288
+ }
289
+ for ( const classElement of node . parent . body ) {
290
+ if (
291
+ classElement . type === 'MethodDefinition' &&
292
+ classElement . accessibility === 'public' &&
293
+ methodReturnsProperty ( classElement , node , returnedVariables )
294
+ ) {
295
+ return false ;
296
+ }
297
+ }
298
+ return true ;
299
+ }
300
+
240
301
function isDateMutable ( referenceTracker : ReferenceTracker , ctorNode : TSESTree . Expression ) : boolean {
241
302
return ! referenceTracker
242
303
. iteratePropertyReferences ( ctorNode , {
0 commit comments