@@ -18,6 +18,7 @@ import { Ast, CompileOptions, Var, Warning } from '../interfaces';
18
18
import error from '../utils/error' ;
19
19
import get_code_frame from '../utils/get_code_frame' ;
20
20
import flatten_reference from './utils/flatten_reference' ;
21
+ import is_used_as_reference from './utils/is_used_as_reference' ;
21
22
import is_reference from 'is-reference' ;
22
23
import TemplateScope from './nodes/shared/TemplateScope' ;
23
24
import fuzzymatch from '../utils/fuzzymatch' ;
@@ -168,12 +169,13 @@ export default class Component {
168
169
this . tag = this . name . name ;
169
170
}
170
171
171
- this . walk_module_js ( ) ;
172
+ this . walk_module_js_pre_template ( ) ;
172
173
this . walk_instance_js_pre_template ( ) ;
173
174
174
175
this . fragment = new Fragment ( this , ast . html ) ;
175
176
this . name = this . get_unique_name ( name ) ;
176
177
178
+ this . walk_module_js_post_template ( ) ;
177
179
this . walk_instance_js_post_template ( ) ;
178
180
179
181
if ( ! compile_options . customElement ) this . stylesheet . reify ( ) ;
@@ -346,6 +348,7 @@ export default class Component {
346
348
reassigned : v . reassigned || false ,
347
349
referenced : v . referenced || false ,
348
350
writable : v . writable || false ,
351
+ referenced_from_script : v . referenced_from_script || false ,
349
352
} ) ) ,
350
353
stats : this . stats . render ( ) ,
351
354
} ;
@@ -447,63 +450,64 @@ export default class Component {
447
450
} ) ;
448
451
}
449
452
450
- extract_imports ( content ) {
451
- for ( let i = 0 ; i < content . body . length ; i += 1 ) {
452
- const node = content . body [ i ] ;
453
-
454
- if ( node . type === 'ImportDeclaration' ) {
455
- content . body . splice ( i -- , 1 ) ;
456
- this . imports . push ( node ) ;
457
- }
458
- }
453
+ extract_imports ( node ) {
454
+ this . imports . push ( node ) ;
459
455
}
460
456
461
- extract_exports ( content ) {
462
- let i = content . body . length ;
463
- while ( i -- ) {
464
- const node = content . body [ i ] ;
457
+ extract_exports ( node ) {
458
+ if ( node . type === 'ExportDefaultDeclaration' ) {
459
+ this . error ( node , {
460
+ code : `default-export` ,
461
+ message : `A component cannot have a default export` ,
462
+ } ) ;
463
+ }
465
464
466
- if ( node . type === 'ExportDefaultDeclaration' ) {
465
+ if ( node . type === 'ExportNamedDeclaration' ) {
466
+ if ( node . source ) {
467
467
this . error ( node , {
468
- code : `default-export ` ,
469
- message : `A component cannot have a default export` ,
468
+ code : `not-implemented ` ,
469
+ message : `A component currently cannot have an export ... from ` ,
470
470
} ) ;
471
471
}
472
-
473
- if ( node . type === 'ExportNamedDeclaration' ) {
474
- if ( node . source ) {
475
- this . error ( node , {
476
- code : `not-implemented` ,
477
- message : `A component currently cannot have an export ... from` ,
472
+ if ( node . declaration ) {
473
+ if ( node . declaration . type === 'VariableDeclaration' ) {
474
+ node . declaration . declarations . forEach ( declarator => {
475
+ extract_names ( declarator . id ) . forEach ( name => {
476
+ const variable = this . var_lookup . get ( name ) ;
477
+ variable . export_name = name ;
478
+ if ( variable . writable && ! ( variable . referenced || variable . referenced_from_script ) ) {
479
+ this . warn ( declarator , {
480
+ code : `unused-export-let` ,
481
+ message : `${ this . name . name } has unused export property '${ name } '. If it is for external reference only, please consider using \`export const '${ name } '\``
482
+ } ) ;
483
+ }
484
+ } ) ;
478
485
} ) ;
486
+ } else {
487
+ const { name } = node . declaration . id ;
488
+
489
+ const variable = this . var_lookup . get ( name ) ;
490
+ variable . export_name = name ;
479
491
}
480
- if ( node . declaration ) {
481
- if ( node . declaration . type === 'VariableDeclaration' ) {
482
- node . declaration . declarations . forEach ( declarator => {
483
- extract_names ( declarator . id ) . forEach ( name => {
484
- const variable = this . var_lookup . get ( name ) ;
485
- variable . export_name = name ;
486
- } ) ;
487
- } ) ;
488
- } else {
489
- const { name } = node . declaration . id ;
490
492
491
- const variable = this . var_lookup . get ( name ) ;
492
- variable . export_name = name ;
493
- }
493
+ return node . declaration ;
494
+ } else {
495
+ node . specifiers . forEach ( specifier => {
496
+ const variable = this . var_lookup . get ( specifier . local . name ) ;
494
497
495
- content . body [ i ] = node . declaration ;
496
- } else {
497
- node . specifiers . forEach ( specifier => {
498
- const variable = this . var_lookup . get ( specifier . local . name ) ;
498
+ if ( variable ) {
499
+ variable . export_name = specifier . exported . name ;
499
500
500
- if ( variable ) {
501
- variable . export_name = specifier . exported . name ;
501
+ if ( variable . writable && ! ( variable . referenced || variable . referenced_from_script ) ) {
502
+ this . warn ( specifier , {
503
+ code : `unused-export-let` ,
504
+ message : `${ this . name . name } has unused export property '${ specifier . exported . name } '. If it is for external reference only, please consider using \`export const '${ specifier . exported . name } '\``
505
+ } ) ;
502
506
}
503
- } ) ;
507
+ }
508
+ } ) ;
504
509
505
- content . body . splice ( i , 1 ) ;
506
- }
510
+ return null ;
507
511
}
508
512
}
509
513
}
@@ -522,7 +526,7 @@ export default class Component {
522
526
} ) ;
523
527
}
524
528
525
- walk_module_js ( ) {
529
+ walk_module_js_pre_template ( ) {
526
530
const component = this ;
527
531
const script = this . ast . module ;
528
532
if ( ! script ) return ;
@@ -573,9 +577,6 @@ export default class Component {
573
577
} ) ;
574
578
}
575
579
} ) ;
576
-
577
- this . extract_imports ( script . content ) ;
578
- this . extract_exports ( script . content ) ;
579
580
}
580
581
581
582
walk_instance_js_pre_template ( ) {
@@ -657,7 +658,10 @@ export default class Component {
657
658
this . add_reference ( name . slice ( 1 ) ) ;
658
659
659
660
const variable = this . var_lookup . get ( name . slice ( 1 ) ) ;
660
- if ( variable ) variable . subscribable = true ;
661
+ if ( variable ) {
662
+ variable . subscribable = true ;
663
+ variable . referenced_from_script = true ;
664
+ }
661
665
} else {
662
666
this . add_var ( {
663
667
name,
@@ -667,46 +671,83 @@ export default class Component {
667
671
}
668
672
} ) ;
669
673
670
- this . extract_imports ( script . content ) ;
671
- this . extract_exports ( script . content ) ;
672
- this . track_mutations ( ) ;
674
+ this . track_references_and_mutations ( ) ;
675
+ }
676
+
677
+ walk_module_js_post_template ( ) {
678
+ const script = this . ast . module ;
679
+ if ( ! script ) return ;
680
+
681
+ const { body } = script . content ;
682
+ let i = body . length ;
683
+ while ( -- i >= 0 ) {
684
+ const node = body [ i ] ;
685
+ if ( node . type === 'ImportDeclaration' ) {
686
+ this . extract_imports ( node ) ;
687
+ body . splice ( i , 1 ) ;
688
+ }
689
+
690
+ if ( / ^ E x p o r t / . test ( node . type ) ) {
691
+ const replacement = this . extract_exports ( node ) ;
692
+ if ( replacement ) {
693
+ body [ i ] = replacement ;
694
+ } else {
695
+ body . splice ( i , 1 ) ;
696
+ }
697
+ }
698
+ }
673
699
}
674
700
675
701
walk_instance_js_post_template ( ) {
676
702
const script = this . ast . instance ;
677
703
if ( ! script ) return ;
678
704
679
- this . warn_on_undefined_store_value_references ( ) ;
705
+ this . post_template_walk ( ) ;
706
+
680
707
this . hoist_instance_declarations ( ) ;
681
708
this . extract_reactive_declarations ( ) ;
682
709
}
683
710
684
- // TODO merge this with other walks that are independent
685
- track_mutations ( ) {
711
+ post_template_walk ( ) {
712
+ const script = this . ast . instance ;
713
+ if ( ! script ) return ;
714
+
686
715
const component = this ;
716
+ const { content } = script ;
687
717
const { instance_scope, instance_scope_map : map } = this ;
688
718
689
719
let scope = instance_scope ;
690
720
691
- walk ( this . ast . instance . content , {
692
- enter ( node ) {
721
+ const toRemove = [ ] ;
722
+ const remove = ( parent , prop , index ) => {
723
+ toRemove . unshift ( [ parent , prop , index ] ) ;
724
+ } ;
725
+
726
+ walk ( content , {
727
+ enter ( node , parent , prop , index ) {
693
728
if ( map . has ( node ) ) {
694
729
scope = map . get ( node ) ;
695
730
}
696
731
697
- if ( node . type === 'AssignmentExpression' || node . type === 'UpdateExpression' ) {
698
- const assignee = node . type === 'AssignmentExpression' ? node . left : node . argument ;
699
- const names = extract_names ( assignee ) ;
700
-
701
- const deep = assignee . type === 'MemberExpression' ;
732
+ if ( node . type === 'ImportDeclaration' ) {
733
+ component . extract_imports ( node ) ;
734
+ // TODO: to use actual remove
735
+ remove ( parent , prop , index ) ;
736
+ return this . skip ( ) ;
737
+ }
702
738
703
- names . forEach ( name => {
704
- if ( scope . find_owner ( name ) === instance_scope ) {
705
- const variable = component . var_lookup . get ( name ) ;
706
- variable [ deep ? 'mutated' : 'reassigned' ] = true ;
707
- }
708
- } ) ;
739
+ if ( / ^ E x p o r t / . test ( node . type ) ) {
740
+ const replacement = component . extract_exports ( node ) ;
741
+ if ( replacement ) {
742
+ this . replace ( replacement ) ;
743
+ } else {
744
+ // TODO: to use actual remove
745
+ remove ( parent , prop , index ) ;
746
+ }
747
+ return this . skip ( ) ;
709
748
}
749
+
750
+ component . warn_on_undefined_store_value_references ( node , parent , scope ) ;
710
751
} ,
711
752
712
753
leave ( node ) {
@@ -715,37 +756,53 @@ export default class Component {
715
756
}
716
757
} ,
717
758
} ) ;
759
+
760
+ for ( const [ parent , prop , index ] of toRemove ) {
761
+ if ( parent ) {
762
+ if ( index !== null ) {
763
+ parent [ prop ] . splice ( index , 1 ) ;
764
+ } else {
765
+ delete parent [ prop ] ;
766
+ }
767
+ }
768
+ }
718
769
}
719
770
720
- warn_on_undefined_store_value_references ( ) {
721
- // TODO this pattern happens a lot... can we abstract it
722
- // (or better still, do fewer AST walks)?
771
+ track_references_and_mutations ( ) {
772
+ const script = this . ast . instance ;
773
+ if ( ! script ) return ;
774
+
723
775
const component = this ;
724
- let { instance_scope : scope , instance_scope_map : map } = this ;
776
+ const { content } = script ;
777
+ const { instance_scope, instance_scope_map : map } = this ;
725
778
726
- walk ( this . ast . instance . content , {
779
+ let scope = instance_scope ;
780
+
781
+ walk ( content , {
727
782
enter ( node , parent ) {
728
783
if ( map . has ( node ) ) {
729
784
scope = map . get ( node ) ;
730
785
}
731
786
732
- if (
733
- node . type === 'LabeledStatement' &&
734
- node . label . name === '$' &&
735
- parent . type !== 'Program'
736
- ) {
737
- component . warn ( node as any , {
738
- code : 'non-top-level-reactive-declaration' ,
739
- message : '$: has no effect outside of the top-level' ,
787
+ if ( node . type === 'AssignmentExpression' || node . type === 'UpdateExpression' ) {
788
+ const assignee = node . type === 'AssignmentExpression' ? node . left : node . argument ;
789
+ const names = extract_names ( assignee ) ;
790
+
791
+ const deep = assignee . type === 'MemberExpression' ;
792
+
793
+ names . forEach ( name => {
794
+ if ( scope . find_owner ( name ) === instance_scope ) {
795
+ const variable = component . var_lookup . get ( name ) ;
796
+ variable [ deep ? 'mutated' : 'reassigned' ] = true ;
797
+ }
740
798
} ) ;
741
799
}
742
800
743
- if ( is_reference ( node as Node , parent as Node ) ) {
801
+ if ( is_used_as_reference ( node , parent ) ) {
744
802
const object = get_object ( node ) ;
745
- const { name } = object ;
746
-
747
- if ( name [ 0 ] === '$' && ! scope . has ( name ) ) {
748
- component . warn_if_undefined ( name , object , null ) ;
803
+ if ( scope . find_owner ( object . name ) === instance_scope ) {
804
+ const variable = component . var_lookup . get ( object . name ) ;
805
+ variable . referenced_from_script = true ;
749
806
}
750
807
}
751
808
} ,
@@ -758,6 +815,28 @@ export default class Component {
758
815
} ) ;
759
816
}
760
817
818
+ warn_on_undefined_store_value_references ( node , parent , scope ) {
819
+ if (
820
+ node . type === 'LabeledStatement' &&
821
+ node . label . name === '$' &&
822
+ parent . type !== 'Program'
823
+ ) {
824
+ this . warn ( node as any , {
825
+ code : 'non-top-level-reactive-declaration' ,
826
+ message : '$: has no effect outside of the top-level' ,
827
+ } ) ;
828
+ }
829
+
830
+ if ( is_reference ( node as Node , parent as Node ) ) {
831
+ const object = get_object ( node ) ;
832
+ const { name } = object ;
833
+
834
+ if ( name [ 0 ] === '$' && ! scope . has ( name ) ) {
835
+ this . warn_if_undefined ( name , object , null ) ;
836
+ }
837
+ }
838
+ }
839
+
761
840
invalidate ( name , value ?) {
762
841
const variable = this . var_lookup . get ( name ) ;
763
842
0 commit comments