@@ -81,6 +81,7 @@ export function migrate(source, { filename } = {}) {
8181 props : [ ] ,
8282 props_insertion_point : parsed . instance ?. content . start ?? 0 ,
8383 has_props_rune : false ,
84+ has_type_or_fallback : false ,
8485 end : source . length ,
8586 names : {
8687 props : analysis . root . unique ( 'props' ) . name ,
@@ -202,30 +203,39 @@ export function migrate(source, { filename } = {}) {
202203 ) ;
203204 const type_name = state . scope . root . unique ( 'Props' ) . name ;
204205 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 } */`;
214226 }
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 } */`;
222227 }
228+
223229 let props_declaration = `let {${ props_separator } ${ props } ${ has_many_props ? `\n${ indent } ` : ' ' } }` ;
224230 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+ }
226234 props_declaration = `${ props_declaration } ${ type ? `: ${ type_name } ` : '' } = $props();` ;
227235 } 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+ }
229239 props_declaration = `${ props_declaration } = $props();` ;
230240 }
231241
@@ -326,6 +336,7 @@ export function migrate(source, { filename } = {}) {
326336 * 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; }>;
327337 * props_insertion_point: number;
328338 * has_props_rune: boolean;
339+ * has_type_or_fallback: boolean;
329340 * end: number;
330341 * names: Record<string, string>;
331342 * legacy_imports: Set<string>;
@@ -517,7 +528,7 @@ const instance_script = {
517528 : '' ,
518529 optional : ! ! declarator . init ,
519530 bindable : binding . updated ,
520- ...extract_type_and_comment ( declarator , state . str , path )
531+ ...extract_type_and_comment ( declarator , state , path )
521532 } ) ;
522533 }
523534
@@ -1253,10 +1264,11 @@ function migrate_slot_usage(node, path, state) {
12531264
12541265/**
12551266 * @param {VariableDeclarator } declarator
1256- * @param {MagicString } str
1267+ * @param {State } state
12571268 * @param {SvelteNode[] } path
12581269 */
1259- function extract_type_and_comment ( declarator , str , path ) {
1270+ function extract_type_and_comment ( declarator , state , path ) {
1271+ const str = state . str ;
12601272 const parent = path . at ( - 1 ) ;
12611273
12621274 // Try to find jsdoc above the declaration
@@ -1271,6 +1283,7 @@ function extract_type_and_comment(declarator, str, path) {
12711283 }
12721284
12731285 if ( declarator . id . typeAnnotation ) {
1286+ state . has_type_or_fallback = true ;
12741287 let start = declarator . id . typeAnnotation . start + 1 ; // skip the colon
12751288 while ( str . original [ start ] === ' ' ) {
12761289 start ++ ;
@@ -1300,6 +1313,7 @@ function extract_type_and_comment(declarator, str, path) {
13001313
13011314 // try to find a comment with a type annotation, hinting at jsdoc
13021315 if ( parent ?. type === 'ExportNamedDeclaration' && comment_node ) {
1316+ state . has_type_or_fallback = true ;
13031317 const match = / @ t y p e { ( .+ ) } / . exec ( comment_node . value ) ;
13041318 if ( match ) {
13051319 return { type : match [ 1 ] , comment } ;
@@ -1308,6 +1322,7 @@ function extract_type_and_comment(declarator, str, path) {
13081322
13091323 // try to infer it from the init
13101324 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
13111326 const type = typeof declarator . init . value ;
13121327 if ( type === 'string' || type === 'number' || type === 'boolean' ) {
13131328 return { type, comment } ;
@@ -1533,6 +1548,8 @@ function handle_identifier(node, state, path) {
15331548 parent . type === 'TSInterfaceDeclaration' ? parent . body . body : parent . typeAnnotation ?. members ;
15341549 if ( Array . isArray ( members ) ) {
15351550 if ( node . name === '$$Props' ) {
1551+ state . has_type_or_fallback = true ;
1552+
15361553 for ( const member of members ) {
15371554 const prop = state . props . find ( ( prop ) => prop . exported === member . key . name ) ;
15381555
0 commit comments