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" ;
3
10
import { 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
+ }
5
26
6
27
type MessageIds =
7
28
| "noWrite"
@@ -11,7 +32,8 @@ type MessageIds =
11
32
| "badUnnamedDerivedSignal"
12
33
| "shouldDestructure"
13
34
| "shouldAssign"
14
- | "noAsyncTrackedScope" ;
35
+ | "noAsyncTrackedScope"
36
+ | "jsxReactiveVariable" ;
15
37
16
38
const rule : TSESLint . RuleModule < MessageIds , [ ] > = {
17
39
meta : {
@@ -39,12 +61,13 @@ const rule: TSESLint.RuleModule<MessageIds, []> = {
39
61
"For proper analysis, a variable should be used to capture the result of this function call." ,
40
62
noAsyncTrackedScope :
41
63
"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." ,
42
65
} ,
43
66
} ,
44
67
create ( context ) {
45
68
const sourceCode = context . getSourceCode ( ) ;
46
69
47
- const { handleImportDeclaration, matchImport, matchLocalToModule } = trackImports ( / ^ / ) ;
70
+ const { handleImportDeclaration, matchImport, matchLocalToModule } = trackImports ( ) ;
48
71
49
72
const root = new ReactivityScope ( sourceCode . ast , null ) ;
50
73
const syncCallbacks = new Set < FunctionNode > ( ) ;
@@ -92,22 +115,64 @@ const rule: TSESLint.RuleModule<MessageIds, []> = {
92
115
}
93
116
}
94
117
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
+ }
98
164
}
99
165
}
100
166
101
167
function distributeReferences ( root : ReactivityScope , references : Array < VirtualReference > ) {
102
168
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 )
106
172
scope . references . push ( ref ) ;
107
173
} ) ;
108
174
}
109
175
110
-
111
176
const pluginApi : ReactivityPluginApi = {
112
177
calledFunction ( node ) {
113
178
currentScope . trackedScopes . push ( { node, expect : "called-function" } ) ;
@@ -121,19 +186,21 @@ const rule: TSESLint.RuleModule<MessageIds, []> = {
121
186
provideErrorContext ( node ) {
122
187
currentScope . errorContexts . push ( node ) ;
123
188
} ,
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
+ // },
133
197
reactive ( node , path ) {
134
198
const references = [ ] ; // TODO generate virtual reactive references
135
199
undistributedReferences . push ( ...references ) ;
136
200
} ,
201
+ getReferences ( node , path ) {
202
+ return Array . from ( getReferences ( node , path ) ) ;
203
+ } ,
137
204
isCall ( node , primitive ) : node is T . CallExpression {
138
205
return (
139
206
node . type === "CallExpression" &&
0 commit comments