@@ -15,6 +15,11 @@ import { parseScriptWithoutAnalyzeScope } from "../../script";
15
15
import { VirtualTypeScriptContext } from "../context" ;
16
16
import type { TSESParseForESLintResult } from "../types" ;
17
17
import type ESTree from "estree" ;
18
+ import type { SvelteAttribute , SvelteHTMLElement } from "../../../ast" ;
19
+
20
+ export type AnalyzeTypeScriptContext = {
21
+ slots : Set < SvelteHTMLElement > ;
22
+ } ;
18
23
19
24
const RESERVED_NAMES = new Set < string > ( [ "$$props" , "$$restProps" , "$$slots" ] ) ;
20
25
/**
@@ -25,7 +30,8 @@ const RESERVED_NAMES = new Set<string>(["$$props", "$$restProps", "$$slots"]);
25
30
export function analyzeTypeScript (
26
31
code : { script : string ; render : string } ,
27
32
attrs : Record < string , string | undefined > ,
28
- parserOptions : any
33
+ parserOptions : any ,
34
+ context : AnalyzeTypeScriptContext
29
35
) : VirtualTypeScriptContext {
30
36
const ctx = new VirtualTypeScriptContext ( code . script + code . render ) ;
31
37
ctx . appendOriginal ( / ^ \s * / u. exec ( code . script ) ! [ 0 ] . length ) ;
@@ -44,6 +50,8 @@ export function analyzeTypeScript(
44
50
45
51
analyzeStoreReferenceNames ( result , ctx ) ;
46
52
53
+ analyzeDollarDollarVariables ( result , ctx , context . slots ) ;
54
+
47
55
analyzeReactiveScopes ( result , ctx ) ;
48
56
49
57
analyzeRenderScopes ( code , ctx ) ;
@@ -138,6 +146,104 @@ function analyzeStoreReferenceNames(
138
146
}
139
147
}
140
148
149
+ /**
150
+ * Analyze `$$slots`, `$$props`, and `$$restProps` .
151
+ * Insert type definitions code to provide correct type information for `$$slots`, `$$props`, and `$$restProps`.
152
+ */
153
+ function analyzeDollarDollarVariables (
154
+ result : TSESParseForESLintResult ,
155
+ ctx : VirtualTypeScriptContext ,
156
+ slots : Set < SvelteHTMLElement >
157
+ ) {
158
+ const scopeManager = result . scopeManager ;
159
+
160
+ if (
161
+ scopeManager . globalScope ! . through . some (
162
+ ( reference ) => reference . identifier . name === "$$props"
163
+ )
164
+ ) {
165
+ appendDeclareVirtualScript ( "$$props" , `{ [index: string]: any }` ) ;
166
+ }
167
+ if (
168
+ scopeManager . globalScope ! . through . some (
169
+ ( reference ) => reference . identifier . name === "$$restProps"
170
+ )
171
+ ) {
172
+ appendDeclareVirtualScript ( "$$restProps" , `{ [index: string]: any }` ) ;
173
+ }
174
+ if (
175
+ scopeManager . globalScope ! . through . some (
176
+ ( reference ) => reference . identifier . name === "$$slots"
177
+ )
178
+ ) {
179
+ const nameTypes = new Set < string > ( ) ;
180
+ for ( const slot of slots ) {
181
+ const nameAttr = slot . startTag . attributes . find (
182
+ ( attr ) : attr is SvelteAttribute =>
183
+ attr . type === "SvelteAttribute" && attr . key . name === "name"
184
+ ) ;
185
+ if ( ! nameAttr || nameAttr . value . length === 0 ) {
186
+ nameTypes . add ( '"default"' ) ;
187
+ continue ;
188
+ }
189
+
190
+ if ( nameAttr . value . length === 1 ) {
191
+ const value = nameAttr . value [ 0 ] ;
192
+ if ( value . type === "SvelteLiteral" ) {
193
+ nameTypes . add ( JSON . stringify ( value . value ) ) ;
194
+ } else {
195
+ nameTypes . add ( "string" ) ;
196
+ }
197
+ continue ;
198
+ }
199
+ nameTypes . add (
200
+ `\`${ nameAttr . value
201
+ . map ( ( value ) =>
202
+ value . type === "SvelteLiteral"
203
+ ? value . value . replace ( / ( [ $ ` ] ) / gu, "\\$1" )
204
+ : "${string}"
205
+ )
206
+ . join ( "" ) } \``
207
+ ) ;
208
+ }
209
+
210
+ appendDeclareVirtualScript (
211
+ "$$slots" ,
212
+ `Record<${
213
+ nameTypes . size > 0 ? [ ...nameTypes ] . join ( " | " ) : "any"
214
+ } , boolean>`
215
+ ) ;
216
+ }
217
+
218
+ /** Append declare virtual script */
219
+ function appendDeclareVirtualScript ( name : string , type : string ) {
220
+ ctx . appendVirtualScript ( `declare let ${ name } : ${ type } ;` ) ;
221
+ ctx . restoreContext . addRestoreStatementProcess ( ( node , result ) => {
222
+ if (
223
+ node . type !== "VariableDeclaration" ||
224
+ ! node . declare ||
225
+ node . declarations . length !== 1 ||
226
+ node . declarations [ 0 ] . id . type !== "Identifier" ||
227
+ node . declarations [ 0 ] . id . name !== name
228
+ ) {
229
+ return false ;
230
+ }
231
+ const program = result . ast ;
232
+ program . body . splice ( program . body . indexOf ( node ) , 1 ) ;
233
+
234
+ const scopeManager = result . scopeManager as ScopeManager ;
235
+
236
+ // Remove `declare` variable
237
+ removeAllScopeAndVariableAndReference ( node , {
238
+ visitorKeys : result . visitorKeys ,
239
+ scopeManager,
240
+ } ) ;
241
+
242
+ return true ;
243
+ } ) ;
244
+ }
245
+ }
246
+
141
247
/**
142
248
* Analyze the reactive scopes.
143
249
* Transform source code to provide the correct type information in the `$:` statements.
0 commit comments