@@ -81,6 +81,7 @@ export function migrate(source, { filename } = {}) {
81
81
props : [ ] ,
82
82
props_insertion_point : parsed . instance ?. content . start ?? 0 ,
83
83
has_props_rune : false ,
84
+ has_type_or_fallback : false ,
84
85
end : source . length ,
85
86
names : {
86
87
props : analysis . root . unique ( 'props' ) . name ,
@@ -202,30 +203,39 @@ export function migrate(source, { filename } = {}) {
202
203
) ;
203
204
const type_name = state . scope . root . unique ( 'Props' ) . name ;
204
205
let type = '' ;
205
- if ( uses_ts ) {
206
- type = `interface ${ type_name } {${ newline_separator } ${ state . props
207
- . map ( ( prop ) => {
208
- const comment = prop . comment ? `${ prop . comment } ${ newline_separator } ` : '' ;
209
- return `${ comment } ${ prop . exported } ${ prop . optional ? '?' : '' } : ${ prop . type } ;` ;
210
- } )
211
- . join ( newline_separator ) } `;
212
- if ( analysis . uses_props || analysis . uses_rest_props ) {
213
- type += `${ state . props . length > 0 ? newline_separator : '' } [key: string]: any` ;
206
+
207
+ // Try to infer when we don't want to add types (e.g. user doesn't use types, or this is a zero-types +page.svelte)
208
+ if ( state . has_type_or_fallback || state . props . every ( ( prop ) => prop . slot_name ) ) {
209
+ if ( uses_ts ) {
210
+ type = `interface ${ type_name } {${ newline_separator } ${ state . props
211
+ . map ( ( prop ) => {
212
+ const comment = prop . comment ? `${ prop . comment } ${ newline_separator } ` : '' ;
213
+ return `${ comment } ${ prop . exported } ${ prop . optional ? '?' : '' } : ${ prop . type } ;` ;
214
+ } )
215
+ . join ( newline_separator ) } `;
216
+ if ( analysis . uses_props || analysis . uses_rest_props ) {
217
+ type += `${ state . props . length > 0 ? newline_separator : '' } [key: string]: any` ;
218
+ }
219
+ type += `\n${ indent } }` ;
220
+ } else {
221
+ type = `/**\n${ indent } * @typedef {Object} ${ type_name } ${ state . props
222
+ . map ( ( prop ) => {
223
+ return `\n${ indent } * @property {${ prop . type } } ${ prop . optional ? `[${ prop . exported } ]` : prop . exported } ${ prop . comment ? ` - ${ prop . comment } ` : '' } ` ;
224
+ } )
225
+ . join ( `` ) } \n${ indent } */`;
214
226
}
215
- type += `\n${ indent } }` ;
216
- } else {
217
- type = `/**\n${ indent } * @typedef {Object} ${ type_name } ${ state . props
218
- . map ( ( prop ) => {
219
- return `\n${ indent } * @property {${ prop . type } } ${ prop . optional ? `[${ prop . exported } ]` : prop . exported } ${ prop . comment ? ` - ${ prop . comment } ` : '' } ` ;
220
- } )
221
- . join ( `` ) } \n${ indent } */`;
222
227
}
228
+
223
229
let props_declaration = `let {${ props_separator } ${ props } ${ has_many_props ? `\n${ indent } ` : ' ' } }` ;
224
230
if ( uses_ts ) {
225
- props_declaration = `${ type } \n\n${ indent } ${ props_declaration } ` ;
231
+ if ( type ) {
232
+ props_declaration = `${ type } \n\n${ indent } ${ props_declaration } ` ;
233
+ }
226
234
props_declaration = `${ props_declaration } ${ type ? `: ${ type_name } ` : '' } = $props();` ;
227
235
} else {
228
- props_declaration = `${ type && state . props . length > 0 ? `${ type } \n\n${ indent } ` : '' } /** @type {${ state . props . length > 0 ? type_name : '' } ${ analysis . uses_props || analysis . uses_rest_props ? `${ state . props . length > 0 ? ' & ' : '' } { [key: string]: any }` : '' } } */\n${ indent } ${ props_declaration } ` ;
236
+ if ( type ) {
237
+ props_declaration = `${ state . props . length > 0 ? `${ type } \n\n${ indent } ` : '' } /** @type {${ state . props . length > 0 ? type_name : '' } ${ analysis . uses_props || analysis . uses_rest_props ? `${ state . props . length > 0 ? ' & ' : '' } { [key: string]: any }` : '' } } */\n${ indent } ${ props_declaration } ` ;
238
+ }
229
239
props_declaration = `${ props_declaration } = $props();` ;
230
240
}
231
241
@@ -326,6 +336,7 @@ export function migrate(source, { filename } = {}) {
326
336
* props: Array<{ local: string; exported: string; init: string; bindable: boolean; slot_name?: string; optional: boolean; type: string; comment?: string; type_only?: boolean; needs_refine_type?: boolean; }>;
327
337
* props_insertion_point: number;
328
338
* has_props_rune: boolean;
339
+ * has_type_or_fallback: boolean;
329
340
* end: number;
330
341
* names: Record<string, string>;
331
342
* legacy_imports: Set<string>;
@@ -517,7 +528,7 @@ const instance_script = {
517
528
: '' ,
518
529
optional : ! ! declarator . init ,
519
530
bindable : binding . updated ,
520
- ...extract_type_and_comment ( declarator , state . str , path )
531
+ ...extract_type_and_comment ( declarator , state , path )
521
532
} ) ;
522
533
}
523
534
@@ -1253,10 +1264,11 @@ function migrate_slot_usage(node, path, state) {
1253
1264
1254
1265
/**
1255
1266
* @param {VariableDeclarator } declarator
1256
- * @param {MagicString } str
1267
+ * @param {State } state
1257
1268
* @param {SvelteNode[] } path
1258
1269
*/
1259
- function extract_type_and_comment ( declarator , str , path ) {
1270
+ function extract_type_and_comment ( declarator , state , path ) {
1271
+ const str = state . str ;
1260
1272
const parent = path . at ( - 1 ) ;
1261
1273
1262
1274
// Try to find jsdoc above the declaration
@@ -1271,6 +1283,7 @@ function extract_type_and_comment(declarator, str, path) {
1271
1283
}
1272
1284
1273
1285
if ( declarator . id . typeAnnotation ) {
1286
+ state . has_type_or_fallback = true ;
1274
1287
let start = declarator . id . typeAnnotation . start + 1 ; // skip the colon
1275
1288
while ( str . original [ start ] === ' ' ) {
1276
1289
start ++ ;
@@ -1300,6 +1313,7 @@ function extract_type_and_comment(declarator, str, path) {
1300
1313
1301
1314
// try to find a comment with a type annotation, hinting at jsdoc
1302
1315
if ( parent ?. type === 'ExportNamedDeclaration' && comment_node ) {
1316
+ state . has_type_or_fallback = true ;
1303
1317
const match = / @ t y p e { ( .+ ) } / . exec ( comment_node . value ) ;
1304
1318
if ( match ) {
1305
1319
return { type : match [ 1 ] , comment } ;
@@ -1308,6 +1322,7 @@ function extract_type_and_comment(declarator, str, path) {
1308
1322
1309
1323
// try to infer it from the init
1310
1324
if ( declarator . init ?. type === 'Literal' ) {
1325
+ state . has_type_or_fallback = true ; // only assume type if it's trivial to infer - else someone would've added a type annotation
1311
1326
const type = typeof declarator . init . value ;
1312
1327
if ( type === 'string' || type === 'number' || type === 'boolean' ) {
1313
1328
return { type, comment } ;
@@ -1533,6 +1548,8 @@ function handle_identifier(node, state, path) {
1533
1548
parent . type === 'TSInterfaceDeclaration' ? parent . body . body : parent . typeAnnotation ?. members ;
1534
1549
if ( Array . isArray ( members ) ) {
1535
1550
if ( node . name === '$$Props' ) {
1551
+ state . has_type_or_fallback = true ;
1552
+
1536
1553
for ( const member of members ) {
1537
1554
const prop = state . props . find ( ( prop ) => prop . exported === member . key . name ) ;
1538
1555
0 commit comments