1- import { TSESLint , TSESTree as T } from "@typescript-eslint/utils" ;
2- import { ProgramOrFunctionNode , FunctionNode , trackImports , isFunctionNode } from "../../utils" ;
1+ import { TSESLint , TSESTree as T , ASTUtils } from "@typescript-eslint/utils" ;
2+ import invariant from 'tiny-invariant'
3+ import {
4+ ProgramOrFunctionNode ,
5+ FunctionNode ,
6+ trackImports ,
7+ isFunctionNode ,
8+ ignoreTransparentWrappers ,
9+ } from "../../utils" ;
310import { ReactivityScope , VirtualReference } from "./analyze" ;
4- import type { ExprPath , ReactivityPlugin , ReactivityPluginApi } from "./pluginApi" ;
11+ import type { ReactivityPlugin , ReactivityPluginApi } from "./pluginApi" ;
12+
13+ const { findVariable } = ASTUtils ;
14+
15+ function parsePath ( path : string ) : Array < string > | null {
16+ if ( path ) {
17+ const regex = / \( \) | \[ \d * \] | \. (?: \w + | * * ? ) / g;
18+ const matches = path . match ( regex ) ;
19+ // ensure the whole string is matched
20+ if ( matches && matches . reduce ( ( acc , match ) => acc + match . length , 0 ) === path . length ) {
21+ return matches ;
22+ }
23+ }
24+ return null ;
25+ }
526
627type MessageIds =
728 | "noWrite"
@@ -11,7 +32,8 @@ type MessageIds =
1132 | "badUnnamedDerivedSignal"
1233 | "shouldDestructure"
1334 | "shouldAssign"
14- | "noAsyncTrackedScope" ;
35+ | "noAsyncTrackedScope"
36+ | "jsxReactiveVariable" ;
1537
1638const rule : TSESLint . RuleModule < MessageIds , [ ] > = {
1739 meta : {
@@ -39,12 +61,13 @@ const rule: TSESLint.RuleModule<MessageIds, []> = {
3961 "For proper analysis, a variable should be used to capture the result of this function call." ,
4062 noAsyncTrackedScope :
4163 "This tracked scope should not be async. Solid's reactivity only tracks synchronously." ,
64+ jsxReactiveVariable : "This variable should not be used as a JSX element." ,
4265 } ,
4366 } ,
4467 create ( context ) {
4568 const sourceCode = context . getSourceCode ( ) ;
4669
47- const { handleImportDeclaration, matchImport, matchLocalToModule } = trackImports ( / ^ / ) ;
70+ const { handleImportDeclaration, matchImport, matchLocalToModule } = trackImports ( ) ;
4871
4972 const root = new ReactivityScope ( sourceCode . ast , null ) ;
5073 const syncCallbacks = new Set < FunctionNode > ( ) ;
@@ -92,22 +115,64 @@ const rule: TSESLint.RuleModule<MessageIds, []> = {
92115 }
93116 }
94117
95- function getReferences ( node : T . Expression , path : ExprPath ) {
96- if ( node . parent ?. type === "VariableDeclarator" && node . parent . init === node ) {
97-
118+ function VirtualReference ( node : T . Node ) : VirtualReference {
119+ return { node, declarationScope : }
120+ }
121+
122+ /**
123+ * Given what's usually a CallExpression and a description of how the expression must be used
124+ * in order to be accessed reactively, return a list of virtual references for each place where
125+ * a reactive expression is accessed.
126+ * `path` is a string formatted according to `pluginApi`.
127+ */
128+ function * getReferences ( node : T . Expression , path : string , allowMutable = false ) : Generator < VirtualReference > {
129+ node = ignoreTransparentWrappers ( node , "up" ) ;
130+ if ( ! path ) {
131+ yield VirtualReference ( node ) ;
132+ } else if ( node . parent ?. type === "VariableDeclarator" && node . parent . init === node ) {
133+ const { id } = node . parent ;
134+ if ( id . type === "Identifier" ) {
135+ const variable = findVariable ( context . getScope ( ) , id ) ;
136+ if ( variable ) {
137+ for ( const reference of variable . references ) {
138+ if ( reference . init ) {
139+ // ignore
140+ } else if ( reference . identifier . type === "JSXIdentifier" ) {
141+ context . report ( { node : reference . identifier , messageId : "jsxReactiveVariable" } ) ;
142+ } else if ( reference . isWrite ( ) ) {
143+ if ( ! allowMutable ) {
144+ context . report ( { node : reference . identifier , messageId : "noWrite" } ) ;
145+ }
146+ } else {
147+ yield * getReferences ( reference . identifier , path ) ;
148+ }
149+ }
150+ }
151+ } 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+
160+ }
161+ }
162+
163+ }
98164 }
99165 }
100166
101167 function distributeReferences ( root : ReactivityScope , references : Array < VirtualReference > ) {
102168 references . forEach ( ( ref ) => {
103- const range =
104- "range" in ref . reference ? ref . reference . range : ref . reference . identifier . range ;
105- const scope = root . deepestScopeContaining ( range ) ! ;
169+ const range = ref . node . range ;
170+ const scope = root . deepestScopeContaining ( range ) ;
171+ invariant ( scope != null )
106172 scope . references . push ( ref ) ;
107173 } ) ;
108174 }
109175
110-
111176 const pluginApi : ReactivityPluginApi = {
112177 calledFunction ( node ) {
113178 currentScope . trackedScopes . push ( { node, expect : "called-function" } ) ;
@@ -121,19 +186,21 @@ const rule: TSESLint.RuleModule<MessageIds, []> = {
121186 provideErrorContext ( node ) {
122187 currentScope . errorContexts . push ( node ) ;
123188 } ,
124- signal ( node , path ) {
125-
126- const references = [ ] ; // TODO generate virtual signal references
127- undistributedReferences . push ( ...references ) ;
128- } ,
129- store ( node , path , options ) {
130- const references = [ ] ; // TODO generate virtual store references
131- undistributedReferences . push ( ...references ) ;
132- } ,
189+ // signal(node, path) {
190+ // const references = []; // TODO generate virtual signal references
191+ // undistributedReferences.push(...references);
192+ // },
193+ // store(node, path, options) {
194+ // const references = []; // TODO generate virtual store references
195+ // undistributedReferences.push(...references);
196+ // },
133197 reactive ( node , path ) {
134198 const references = [ ] ; // TODO generate virtual reactive references
135199 undistributedReferences . push ( ...references ) ;
136200 } ,
201+ getReferences ( node , path ) {
202+ return Array . from ( getReferences ( node , path ) ) ;
203+ } ,
137204 isCall ( node , primitive ) : node is T . CallExpression {
138205 return (
139206 node . type === "CallExpression" &&
0 commit comments