@@ -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 ! ) ;
@@ -143,9 +164,12 @@ export default createRule('prefer-svelte-reactivity', {
143
164
} ) ;
144
165
}
145
166
}
167
+ const enclosingPropertyDefinition = findEnclosingPropertyDefinition ( node ) ;
146
168
if (
147
169
findEnclosingReturn ( node ) !== null ||
148
- findEnclosingPropertyDefinition ( node ) ?. accessibility === 'public'
170
+ ( enclosingPropertyDefinition !== null &&
171
+ ( ! ignoreEncapsulatedLocalVariables ||
172
+ ! isPropertyEncapsulated ( enclosingPropertyDefinition , returnedVariables ) ) )
149
173
) {
150
174
context . report ( {
151
175
messageId,
@@ -207,10 +231,18 @@ function findAncestorOfTypes<T extends string>(
207
231
return findAncestorOfTypes ( node . parent , types ) ;
208
232
}
209
233
234
+ function findEnclosingClassBody ( node : TSESTree . Node ) : TSESTree . ClassBody | null {
235
+ return findAncestorOfTypes ( node , [ 'ClassBody' ] ) ;
236
+ }
237
+
210
238
function findEnclosingFunction (
211
239
node : TSESTree . Node
212
240
) : TSESTree . ArrowFunctionExpression | TSESTree . FunctionDeclaration | null {
213
- return findAncestorOfTypes ( node , [ 'ArrowFunctionExpression' , 'FunctionDeclaration' ] ) ;
241
+ return findAncestorOfTypes ( node , [
242
+ 'ArrowFunctionExpression' ,
243
+ 'FunctionDeclaration' ,
244
+ 'MethodDefinition'
245
+ ] ) ;
214
246
}
215
247
216
248
function findEnclosingPropertyDefinition ( node : TSESTree . Node ) : TSESTree . PropertyDefinition | null {
@@ -222,10 +254,7 @@ function findEnclosingReturn(node: TSESTree.Node): TSESTree.ReturnStatement | nu
222
254
}
223
255
224
256
function isLocalVarEncapsulated (
225
- returnedVariables : Map <
226
- TSESTree . ArrowFunctionExpression | TSESTree . FunctionDeclaration ,
227
- TSESTree . VariableDeclarator [ ]
228
- > ,
257
+ returnedVariables : Map < FunctionLike , VariableLike [ ] > ,
229
258
node : TSESTree . Node
230
259
) : boolean {
231
260
const enclosingFunction = findEnclosingFunction ( node ) ;
@@ -237,6 +266,32 @@ function isLocalVarEncapsulated(
237
266
) ;
238
267
}
239
268
269
+ function methodReturnsProperty (
270
+ method : TSESTree . MethodDefinition ,
271
+ property : TSESTree . PropertyDefinition ,
272
+ returnedVariables : Map < FunctionLike , VariableLike [ ] >
273
+ ) : boolean {
274
+ return returnedVariables . get ( method ) ?. includes ( property ) ?? false ;
275
+ }
276
+
277
+ function isPropertyEncapsulated (
278
+ node : TSESTree . PropertyDefinition ,
279
+ returnedVariables : Map < FunctionLike , VariableLike [ ] >
280
+ ) : boolean {
281
+ if ( node . accessibility === 'public' ) {
282
+ return false ;
283
+ }
284
+ for ( const classElement of node . parent . body ) {
285
+ if (
286
+ classElement . type === 'MethodDefinition' &&
287
+ methodReturnsProperty ( classElement , node , returnedVariables )
288
+ ) {
289
+ return false ;
290
+ }
291
+ }
292
+ return true ;
293
+ }
294
+
240
295
function isDateMutable ( referenceTracker : ReferenceTracker , ctorNode : TSESTree . Expression ) : boolean {
241
296
return ! referenceTracker
242
297
. iteratePropertyReferences ( ctorNode , {
0 commit comments