1- import { TSESLint , TSESTree as T , ASTUtils } from "@typescript-eslint/utils" ;
2- import invariant from ' tiny-invariant'
1+ import { TSESLint , TSESTree as T , ESLintUtils , ASTUtils } from "@typescript-eslint/utils" ;
2+ import invariant from " tiny-invariant" ;
33import {
44 ProgramOrFunctionNode ,
55 FunctionNode ,
@@ -10,6 +10,7 @@ import {
1010import { ReactivityScope , VirtualReference } from "./analyze" ;
1111import type { ReactivityPlugin , ReactivityPluginApi } from "./pluginApi" ;
1212
13+ const createRule = ESLintUtils . RuleCreator . withoutDocs ;
1314const { findVariable } = ASTUtils ;
1415
1516function parsePath ( path : string ) : Array < string > | null {
@@ -24,18 +25,7 @@ function parsePath(path: string): Array<string> | null {
2425 return null ;
2526}
2627
27- type MessageIds =
28- | "noWrite"
29- | "untrackedReactive"
30- | "expectedFunctionGotExpression"
31- | "badSignal"
32- | "badUnnamedDerivedSignal"
33- | "shouldDestructure"
34- | "shouldAssign"
35- | "noAsyncTrackedScope"
36- | "jsxReactiveVariable" ;
37-
38- const rule : TSESLint . RuleModule < MessageIds , [ ] > = {
28+ export default createRule ( {
3929 meta : {
4030 type : "problem" ,
4131 docs : {
@@ -64,10 +54,11 @@ const rule: TSESLint.RuleModule<MessageIds, []> = {
6454 jsxReactiveVariable : "This variable should not be used as a JSX element." ,
6555 } ,
6656 } ,
57+ defaultOptions : [ ] ,
6758 create ( context ) {
6859 const sourceCode = context . getSourceCode ( ) ;
6960
70- const { handleImportDeclaration , matchImport, matchLocalToModule } = trackImports ( ) ;
61+ const { matchImport, matchLocalToModule } = trackImports ( sourceCode . ast ) ;
7162
7263 const root = new ReactivityScope ( sourceCode . ast , null ) ;
7364 const syncCallbacks = new Set < FunctionNode > ( ) ;
@@ -116,20 +107,40 @@ const rule: TSESLint.RuleModule<MessageIds, []> = {
116107 }
117108
118109 function VirtualReference ( node : T . Node ) : VirtualReference {
119- return { node, declarationScope : }
110+ return { node, declarationScope : null } ;
120111 }
121112
122113 /**
123114 * Given what's usually a CallExpression and a description of how the expression must be used
124115 * in order to be accessed reactively, return a list of virtual references for each place where
125116 * a reactive expression is accessed.
126- * `path` is a string formatted according to `pluginApi`.
117+ * `path` is a array of segments parsed by `parsePath` according to `pluginApi`.
127118 */
128- function * getReferences ( node : T . Expression , path : string , allowMutable = false ) : Generator < VirtualReference > {
119+ function getReferences (
120+ node : T . Node ,
121+ path : string | null ,
122+ allowMutable = false
123+ ) : Array < VirtualReference > {
129124 node = ignoreTransparentWrappers ( node , "up" ) ;
130- if ( ! path ) {
125+ const parsedPathOuter = path != null ? parsePath ( path ) : null ;
126+ const eqCount = parsedPathOuter ?. reduce ( ( c , segment ) => c + + ( segment === '=' ) , 0 ) ?? 0 ;
127+ if ( eqCount > 1 ) {
128+ throw new Error ( `'${ path } ' must have 0 or 1 '=' characters, has ${ eqCount } ` )
129+ }
130+ const hasEq = eqCount === 1 ;
131+
132+ let declarationScope = hasEq ? null : context . getScope ( ) ;
133+
134+ function * recursiveGenerator ( node : T . Node , parsedPath : Array < string > | null ) {
135+
136+
137+ if ( ! parsedPath ) {
131138 yield VirtualReference ( node ) ;
132139 } else if ( node . parent ?. type === "VariableDeclarator" && node . parent . init === node ) {
140+ yield getReferences ( node . parent . id ) ;
141+ } else if ( node . type === "Identifier" ) {
142+
143+ }
133144 const { id } = node . parent ;
134145 if ( id . type === "Identifier" ) {
135146 const variable = findVariable ( context . getScope ( ) , id ) ;
@@ -144,31 +155,34 @@ const rule: TSESLint.RuleModule<MessageIds, []> = {
144155 context . report ( { node : reference . identifier , messageId : "noWrite" } ) ;
145156 }
146157 } else {
147- yield * getReferences ( reference . identifier , path ) ;
158+ yield * getReferences ( reference . identifier , parsedPath , allowMutable ) ;
148159 }
149160 }
150161 }
151162 } else if ( id . type === "ArrayPattern" ) {
152- const parsedPath = parsePath ( path )
153- if ( parsedPath ) {
154- const newPath = path . substring ( match [ 0 ] . length ) ;
155- const index = match [ 1 ]
156- if ( index === '*' ) {
157-
158- } else {
159-
163+ if ( parsedPath [ 0 ] === "[]" ) {
164+ for ( const el of id . elements ) {
165+ if ( ! el ) {
166+ // ignore
167+ } else if ( el . type === "Identifier" ) {
168+ yield * getReferences ( el , parsedPath . slice ( 1 ) , allowMutable ) ;
169+ } else if ( el . type === "RestElement" ) {
170+ yield * getReferences ( el . argument , parsedPath , allowMutable ) ;
171+ }
160172 }
173+ } else {
161174 }
162-
163175 }
164- }
176+
177+
178+ return Array . from ( recursiveGenerator ( node , parsePath ( path ) ) ) ;
165179 }
166180
167181 function distributeReferences ( root : ReactivityScope , references : Array < VirtualReference > ) {
168182 references . forEach ( ( ref ) => {
169183 const range = ref . node . range ;
170184 const scope = root . deepestScopeContaining ( range ) ;
171- invariant ( scope != null )
185+ invariant ( scope != null ) ;
172186 scope . references . push ( ref ) ;
173187 } ) ;
174188 }
@@ -238,4 +252,4 @@ const rule: TSESLint.RuleModule<MessageIds, []> = {
238252 } ,
239253 } ;
240254 } ,
241- } ;
255+ } ) ;
0 commit comments