@@ -63,7 +63,8 @@ export function migrate(source) {
63
63
64
64
if ( state . props . length > 0 || analysis . uses_rest_props || analysis . uses_props ) {
65
65
const has_many_props = state . props . length > 3 ;
66
- const props_separator = has_many_props ? `\n${ indent } ${ indent } ` : ' ' ;
66
+ const newline_separator = `\n${ indent } ${ indent } ` ;
67
+ const props_separator = has_many_props ? newline_separator : ' ' ;
67
68
let props = '' ;
68
69
if ( analysis . uses_props ) {
69
70
props = `...${ state . props_name } ` ;
@@ -99,11 +100,12 @@ export function migrate(source) {
99
100
if ( analysis . uses_props || analysis . uses_rest_props ) {
100
101
type = `interface ${ type_name } { [key: string]: any }` ;
101
102
} else {
102
- type = `interface ${ type_name } {${ props_separator } ${ state . props
103
+ type = `interface ${ type_name } {${ newline_separator } ${ state . props
103
104
. map ( ( prop ) => {
104
- return `${ prop . exported } ${ prop . optional ? '?' : '' } : ${ prop . type } ` ;
105
+ const comment = prop . comment ? `${ prop . comment } ${ newline_separator } ` : '' ;
106
+ return `${ comment } ${ prop . exported } ${ prop . optional ? '?' : '' } : ${ prop . type } ;` ;
105
107
} )
106
- . join ( `, ${ props_separator } ` ) } ${ has_many_props ? ` \n${ indent } ` : ' ' } }`;
108
+ . join ( newline_separator ) } \n${ indent } }`;
107
109
}
108
110
} else {
109
111
if ( analysis . uses_props || analysis . uses_rest_props ) {
@@ -162,7 +164,7 @@ export function migrate(source) {
162
164
* str: MagicString;
163
165
* analysis: import('../phases/types.js').ComponentAnalysis;
164
166
* indent: string;
165
- * props: Array<{ local: string; exported: string; init: string; bindable: boolean; optional: boolean; type: string }>;
167
+ * props: Array<{ local: string; exported: string; init: string; bindable: boolean; slot_name?: string; optional: boolean; type: string; comment? : string }>;
166
168
* props_insertion_point: number;
167
169
* has_props_rune: boolean;
168
170
* props_name: string;
@@ -190,8 +192,11 @@ const instance_script = {
190
192
}
191
193
next ( ) ;
192
194
} ,
193
- Identifier ( node , { state } ) {
194
- handle_identifier ( node , state ) ;
195
+ Identifier ( node , { state, path } ) {
196
+ handle_identifier ( node , state , path ) ;
197
+ } ,
198
+ ImportDeclaration ( node , { state } ) {
199
+ state . props_insertion_point = node . end ?? state . props_insertion_point ;
195
200
} ,
196
201
ExportNamedDeclaration ( node , { state, next } ) {
197
202
if ( node . declaration ) {
@@ -299,8 +304,8 @@ const instance_script = {
299
304
)
300
305
: '' ,
301
306
optional : ! ! declarator . init ,
302
- type : extract_type ( declarator , state . str , path ) ,
303
- bindable : binding . mutated || binding . reassigned
307
+ bindable : binding . mutated || binding . reassigned ,
308
+ ... extract_type_and_comment ( declarator , state . str , path )
304
309
} ) ;
305
310
state . props_insertion_point = /** @type {number } */ ( declarator . end ) ;
306
311
state . str . update (
@@ -423,8 +428,8 @@ const instance_script = {
423
428
424
429
/** @type {import('zimmerframe').Visitors<import('../types/template.js').SvelteNode, State> } */
425
430
const template = {
426
- Identifier ( node , { state } ) {
427
- handle_identifier ( node , state ) ;
431
+ Identifier ( node , { state, path } ) {
432
+ handle_identifier ( node , state , path ) ;
428
433
} ,
429
434
RegularElement ( node , { state, next } ) {
430
435
handle_events ( node , state ) ;
@@ -468,14 +473,15 @@ const template = {
468
473
} ,
469
474
SlotElement ( node , { state, next } ) {
470
475
let name = 'children' ;
476
+ let slot_name = 'default' ;
471
477
let slot_props = '{ ' ;
472
478
473
479
for ( const attr of node . attributes ) {
474
480
if ( attr . type === 'SpreadAttribute' ) {
475
481
slot_props += `...${ state . str . original . substring ( /** @type {number } */ ( attr . expression . start ) , attr . expression . end ) } , ` ;
476
482
} else if ( attr . type === 'Attribute' ) {
477
483
if ( attr . name === 'name' ) {
478
- name = state . scope . generate ( /** @type {any } */ ( attr . value ) [ 0 ] . data ) ;
484
+ slot_name = /** @type {any } */ ( attr . value ) [ 0 ] . data ;
479
485
} else {
480
486
const value =
481
487
attr . value !== true
@@ -494,14 +500,24 @@ const template = {
494
500
slot_props = '' ;
495
501
}
496
502
497
- state . props . push ( {
498
- local : name ,
499
- exported : name ,
500
- init : '' ,
501
- bindable : false ,
502
- optional : true ,
503
- type : `import('svelte').${ slot_props ? 'Snippet<[any]>' : 'Snippet' } `
504
- } ) ;
503
+ const existing_prop = state . props . find ( ( prop ) => prop . slot_name === slot_name ) ;
504
+ if ( existing_prop ) {
505
+ name = existing_prop . local ;
506
+ } else if ( slot_name !== 'default' ) {
507
+ name = state . scope . generate ( slot_name ) ;
508
+ }
509
+
510
+ if ( ! existing_prop ) {
511
+ state . props . push ( {
512
+ local : name ,
513
+ exported : name ,
514
+ init : '' ,
515
+ bindable : false ,
516
+ optional : true ,
517
+ slot_name,
518
+ type : `import('svelte').${ slot_props ? 'Snippet<[any]>' : 'Snippet' } `
519
+ } ) ;
520
+ }
505
521
506
522
if ( node . fragment . nodes . length > 0 ) {
507
523
next ( ) ;
@@ -528,37 +544,46 @@ const template = {
528
544
* @param {MagicString } str
529
545
* @param {import('#compiler').SvelteNode[] } path
530
546
*/
531
- function extract_type ( declarator , str , path ) {
547
+ function extract_type_and_comment ( declarator , str , path ) {
548
+ const parent = path . at ( - 1 ) ;
549
+
550
+ // Try to find jsdoc above the declaration
551
+ let comment_node = /** @type {import('estree').Node } */ ( parent ) ?. leadingComments ?. at ( - 1 ) ;
552
+ if ( comment_node ?. type !== 'Block' ) comment_node = undefined ;
553
+
554
+ const comment_start = /** @type {any } */ ( comment_node ) ?. start ;
555
+ const comment_end = /** @type {any } */ ( comment_node ) ?. end ;
556
+ const comment = comment_node && str . original . substring ( comment_start , comment_end ) ;
557
+
558
+ if ( comment_node ) {
559
+ str . update ( comment_start , comment_end , '' ) ;
560
+ }
561
+
532
562
if ( declarator . id . typeAnnotation ) {
533
563
let start = declarator . id . typeAnnotation . start + 1 ; // skip the colon
534
564
while ( str . original [ start ] === ' ' ) {
535
565
start ++ ;
536
566
}
537
- return str . original . substring ( start , declarator . id . typeAnnotation . end ) ;
567
+ return { type : str . original . substring ( start , declarator . id . typeAnnotation . end ) , comment } ;
538
568
}
539
569
540
570
// try to find a comment with a type annotation, hinting at jsdoc
541
- const parent = path . at ( - 1 ) ;
542
- if ( parent ?. type === 'ExportNamedDeclaration' && parent . leadingComments ) {
543
- const last = parent . leadingComments [ parent . leadingComments . length - 1 ] ;
544
- if ( last . type === 'Block' ) {
545
- const match = / @ t y p e { ( [ ^ } ] + ) } / . exec ( last . value ) ;
546
- if ( match ) {
547
- str . update ( /** @type {any } */ ( last ) . start , /** @type {any } */ ( last ) . end , '' ) ;
548
- return match [ 1 ] ;
549
- }
571
+ if ( parent ?. type === 'ExportNamedDeclaration' && comment_node ) {
572
+ const match = / @ t y p e { ( .+ ) } / . exec ( comment_node . value ) ;
573
+ if ( match ) {
574
+ return { type : match [ 1 ] } ;
550
575
}
551
576
}
552
577
553
578
// try to infer it from the init
554
579
if ( declarator . init ?. type === 'Literal' ) {
555
580
const type = typeof declarator . init . value ;
556
581
if ( type === 'string' || type === 'number' || type === 'boolean' ) {
557
- return type ;
582
+ return { type, comment } ;
558
583
}
559
584
}
560
585
561
- return 'any' ;
586
+ return { type : 'any' , comment } ;
562
587
}
563
588
564
589
/**
@@ -694,14 +719,16 @@ function handle_events(node, state) {
694
719
}
695
720
696
721
const needs_curlies = last . expression . body . type !== 'BlockStatement' ;
697
- state . str . prependRight (
698
- /** @type {number } */ ( pos ) + ( needs_curlies ? 0 : 1 ) ,
699
- `${ needs_curlies ? '{' : '' } ${ prepend } ${ state . indent } `
700
- ) ;
701
- state . str . appendRight (
702
- /** @type {number } */ ( last . expression . body . end ) - ( needs_curlies ? 0 : 1 ) ,
703
- `\n${ needs_curlies ? '}' : '' } `
704
- ) ;
722
+ const end = /** @type {number } */ ( last . expression . body . end ) - ( needs_curlies ? 0 : 1 ) ;
723
+ pos = /** @type {number } */ ( pos ) + ( needs_curlies ? 0 : 1 ) ;
724
+ if ( needs_curlies && state . str . original [ pos - 1 ] === '(' ) {
725
+ // Prettier does something like on:click={() => (foo = true)}, we need to remove the braces in this case
726
+ state . str . update ( pos - 1 , pos , `{${ prepend } ${ state . indent } ` ) ;
727
+ state . str . update ( end , end + 1 , '\n}' ) ;
728
+ } else {
729
+ state . str . prependRight ( pos , `${ needs_curlies ? '{' : '' } ${ prepend } ${ state . indent } ` ) ;
730
+ state . str . appendRight ( end , `\n${ needs_curlies ? '}' : '' } ` ) ;
731
+ }
705
732
} else {
706
733
state . str . update (
707
734
/** @type {number } */ ( last . expression . start ) ,
@@ -763,8 +790,12 @@ function generate_event_name(last, state) {
763
790
/**
764
791
* @param {import('estree').Identifier } node
765
792
* @param {State } state
793
+ * @param {any[] } path
766
794
*/
767
- function handle_identifier ( node , state ) {
795
+ function handle_identifier ( node , state , path ) {
796
+ const parent = path . at ( - 1 ) ;
797
+ if ( parent ?. type === 'MemberExpression' && parent . property === node ) return ;
798
+
768
799
if ( state . analysis . uses_props ) {
769
800
if ( node . name === '$$props' || node . name === '$$restProps' ) {
770
801
// not 100% correct for $$restProps but it'll do
@@ -785,6 +816,11 @@ function handle_identifier(node, state) {
785
816
/** @type {number } */ ( node . end ) ,
786
817
state . rest_props_name
787
818
) ;
819
+ } else if ( node . name === '$$slots' && state . analysis . uses_slots ) {
820
+ if ( parent ?. type === 'MemberExpression' ) {
821
+ state . str . update ( /** @type {number } */ ( node . start ) , parent . property . start , '' ) ;
822
+ }
823
+ // else passed as identifier, we don't know what to do here, so let it error
788
824
}
789
825
}
790
826
0 commit comments