@@ -4,6 +4,13 @@ import type { TSESTree } from '@typescript-eslint/types';
44import { findVariable , isIn } from '../utils/ast-utils.js' ;
55import { getSvelteContext } from '../utils/svelte-context.js' ;
66
7+ type FunctionLike =
8+ | TSESTree . ArrowFunctionExpression
9+ | TSESTree . FunctionDeclaration
10+ | TSESTree . MethodDefinition ;
11+
12+ type VariableLike = TSESTree . VariableDeclarator | TSESTree . PropertyDefinition ;
13+
714export default createRule ( 'prefer-svelte-reactivity' , {
815 meta : {
916 docs : {
@@ -44,10 +51,7 @@ export default createRule('prefer-svelte-reactivity', {
4451 create ( context ) {
4552 const ignoreEncapsulatedLocalVariables =
4653 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 ( ) ;
5155 const exportedVars : TSESTree . Node [ ] = [ ] ;
5256 return {
5357 ...( getSvelteContext ( context ) ?. svelteFileType === '.svelte.[js|ts]' && {
@@ -85,18 +89,35 @@ export default createRule('prefer-svelte-reactivity', {
8589 if ( enclosingFunction === null ) {
8690 return ;
8791 }
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 ) {
94115 return ;
95116 }
96117 if ( ! returnedVariables . has ( enclosingFunction ) ) {
97118 returnedVariables . set ( enclosingFunction , [ ] ) ;
98119 }
99- returnedVariables . get ( enclosingFunction ) ?. push ( variable . identifiers [ 0 ] . parent ) ;
120+ returnedVariables . get ( enclosingFunction ) ?. push ( variableDeclaration ) ;
100121 } ,
101122 'Program:exit' ( ) {
102123 const referenceTracker = new ReferenceTracker ( context . sourceCode . scopeManager . globalScope ! ) ;
@@ -135,17 +156,25 @@ export default createRule('prefer-svelte-reactivity', {
135156 } ) ;
136157 }
137158 }
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+ for ( const returnedVar of fnReturnVars ) {
161+ if ( fn . type === 'MethodDefinition' && returnedVar . type === 'PropertyDefinition' ) {
162+ continue ;
163+ }
164+ if ( isIn ( node , returnedVar ) ) {
165+ context . report ( {
166+ messageId,
167+ node
168+ } ) ;
169+ }
144170 }
145171 }
172+ const enclosingPropertyDefinition = findEnclosingPropertyDefinition ( node ) ;
146173 if (
147174 findEnclosingReturn ( node ) !== null ||
148- findEnclosingPropertyDefinition ( node ) ?. accessibility === 'public'
175+ ( enclosingPropertyDefinition !== null &&
176+ ( ! ignoreEncapsulatedLocalVariables ||
177+ ! isPropertyEncapsulated ( enclosingPropertyDefinition , returnedVariables ) ) )
149178 ) {
150179 context . report ( {
151180 messageId,
@@ -207,10 +236,18 @@ function findAncestorOfTypes<T extends string>(
207236 return findAncestorOfTypes ( node . parent , types ) ;
208237}
209238
239+ function findEnclosingClassBody ( node : TSESTree . Node ) : TSESTree . ClassBody | null {
240+ return findAncestorOfTypes ( node , [ 'ClassBody' ] ) ;
241+ }
242+
210243function findEnclosingFunction (
211244 node : TSESTree . Node
212245) : TSESTree . ArrowFunctionExpression | TSESTree . FunctionDeclaration | null {
213- return findAncestorOfTypes ( node , [ 'ArrowFunctionExpression' , 'FunctionDeclaration' ] ) ;
246+ return findAncestorOfTypes ( node , [
247+ 'ArrowFunctionExpression' ,
248+ 'FunctionDeclaration' ,
249+ 'MethodDefinition'
250+ ] ) ;
214251}
215252
216253function findEnclosingPropertyDefinition ( node : TSESTree . Node ) : TSESTree . PropertyDefinition | null {
@@ -222,10 +259,7 @@ function findEnclosingReturn(node: TSESTree.Node): TSESTree.ReturnStatement | nu
222259}
223260
224261function isLocalVarEncapsulated (
225- returnedVariables : Map <
226- TSESTree . ArrowFunctionExpression | TSESTree . FunctionDeclaration ,
227- TSESTree . VariableDeclarator [ ]
228- > ,
262+ returnedVariables : Map < FunctionLike , VariableLike [ ] > ,
229263 node : TSESTree . Node
230264) : boolean {
231265 const enclosingFunction = findEnclosingFunction ( node ) ;
@@ -237,6 +271,33 @@ function isLocalVarEncapsulated(
237271 ) ;
238272}
239273
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+
240301function isDateMutable ( referenceTracker : ReferenceTracker , ctorNode : TSESTree . Expression ) : boolean {
241302 return ! referenceTracker
242303 . iteratePropertyReferences ( ctorNode , {
0 commit comments