@@ -159,6 +159,11 @@ export class Assembler implements Emitter {
159
159
*
160
160
* Will not invoke the function with any 'undefined's; an error will already have been emitted in
161
161
* that case anyway.
162
+ *
163
+ * @param fqn FQN of the current type (the type that has a dependency on baseTypes)
164
+ * @param baseTypes Array of type references to be looked up
165
+ * @param referencingNode Node to report a diagnostic on if we fail to look up a t ype
166
+ * @param cb Callback to be invoked with the Types corresponding to the TypeReferences in baseTypes
162
167
*/
163
168
// tslint:disable-next-line:max-line-length
164
169
private _deferUntilTypesAvailable ( fqn : string , baseTypes : spec . NamedTypeReference [ ] , referencingNode : ts . Node , cb : ( ...xs : spec . Type [ ] ) => void ) {
@@ -585,9 +590,64 @@ export class Assembler implements Emitter {
585
590
jsiiType . initializer = { initializer : true } ;
586
591
}
587
592
593
+ this . _verifyNoStaticMixing ( jsiiType , type . symbol . valueDeclaration ) ;
594
+
588
595
return _sortMembers ( jsiiType ) ;
589
596
}
590
597
598
+ /**
599
+ * Check that this class doesn't declare any members that are of different staticness in itself or any of its bases
600
+ */
601
+ private _verifyNoStaticMixing ( klass : spec . ClassType , decl : ts . Declaration ) {
602
+ function stat ( s ?: boolean ) {
603
+ return s ? 'static' : 'non-static' ;
604
+ }
605
+
606
+ // Check class itself--may have two methods/props with the same name, so check the arrays
607
+ const statics = new Set ( ( klass . methods || [ ] ) . concat ( klass . properties || [ ] ) . filter ( x => x . static ) . map ( x => x . name ) ) ;
608
+ const nonStatics = new Set ( ( klass . methods || [ ] ) . concat ( klass . properties || [ ] ) . filter ( x => ! x . static ) . map ( x => x . name ) ) ;
609
+ // Intersect
610
+ for ( const member of intersect ( statics , nonStatics ) ) {
611
+ this . _diagnostic ( decl , ts . DiagnosticCategory . Error ,
612
+ `member '${ member } ' of class '${ klass . name } ' cannot be declared both statically and non-statically` ) ;
613
+ }
614
+
615
+ // Check against base classes. They will not contain duplicate member names so we can load
616
+ // the members into a map.
617
+ const classMembers = typeMembers ( klass ) ;
618
+ this . _withBaseClass ( klass , decl , ( base , recurse ) => {
619
+ for ( const [ name , baseMember ] of Object . entries ( typeMembers ( base ) ) ) {
620
+ const member = classMembers [ name ] ;
621
+ if ( ! member ) { continue ; }
622
+
623
+ if ( ! ! baseMember . static !== ! ! member . static ) {
624
+ this . _diagnostic ( decl , ts . DiagnosticCategory . Error ,
625
+ // tslint:disable-next-line:max-line-length
626
+ `${ stat ( member . static ) } member '${ name } ' of class '${ klass . name } ' conflicts with ${ stat ( baseMember . static ) } member in ancestor '${ base . name } '` ) ;
627
+ }
628
+ }
629
+
630
+ recurse ( ) ;
631
+ } ) ;
632
+ }
633
+
634
+ /**
635
+ * Wrapper around _deferUntilTypesAvailable, invoke the callback with the given classes' base type
636
+ *
637
+ * Does nothing if the given class doesn't have a base class.
638
+ *
639
+ * The second argument will be a `recurse` function for easy recursion up the inheritance tree
640
+ * (no messing around with binding 'self' and 'this' and doing multiple calls to _withBaseClass.)
641
+ */
642
+ private _withBaseClass ( klass : spec . ClassType , decl : ts . Declaration , cb : ( base : spec . ClassType , recurse : ( ) => void ) => void ) {
643
+ if ( klass . base ) {
644
+ this . _deferUntilTypesAvailable ( klass . fqn , [ klass . base ] , decl , ( base ) => {
645
+ if ( ! spec . isClassType ( base ) ) { throw new Error ( 'Oh no' ) ; }
646
+ cb ( base , ( ) => this . _withBaseClass ( base , decl , cb ) ) ;
647
+ } ) ;
648
+ }
649
+ }
650
+
591
651
/**
592
652
* @returns true if this member is internal and should be omitted from the type manifest
593
653
*/
@@ -791,13 +851,13 @@ export class Assembler implements Emitter {
791
851
792
852
// Check that no interface declares a member that's already declared
793
853
// in a base type (not allowed in C#).
794
- const memberNames = interfaceMemberNames ( jsiiType ) ;
854
+ const names = memberNames ( jsiiType ) ;
795
855
const checkNoIntersection = ( ...bases : spec . Type [ ] ) => {
796
856
for ( const base of bases ) {
797
857
if ( ! spec . isInterfaceType ( base ) ) { continue ; }
798
858
799
- const baseMembers = interfaceMemberNames ( base ) ;
800
- for ( const memberName of memberNames ) {
859
+ const baseMembers = memberNames ( base ) ;
860
+ for ( const memberName of names ) {
801
861
if ( baseMembers . includes ( memberName ) ) {
802
862
this . _diagnostic ( type . symbol . declarations [ 0 ] ,
803
863
ts . DiagnosticCategory . Error ,
@@ -1390,14 +1450,21 @@ function intersection<T>(xs: Set<T>, ys: Set<T>): Set<T> {
1390
1450
*
1391
1451
* Returns empty string for a non-interface type.
1392
1452
*/
1393
- function interfaceMemberNames ( jsiiType : spec . InterfaceType ) : string [ ] {
1394
- const ret = new Array < string > ( ) ;
1395
- if ( jsiiType . methods ) {
1396
- ret . push ( ...jsiiType . methods . map ( m => m . name ) . filter ( x => x !== undefined ) as string [ ] ) ;
1453
+ function memberNames ( jsiiType : spec . InterfaceType | spec . ClassType ) : string [ ] {
1454
+ return Object . keys ( typeMembers ( jsiiType ) ) . filter ( n => n !== '' ) ;
1455
+ }
1456
+
1457
+ function typeMembers ( jsiiType : spec . InterfaceType | spec . ClassType ) : { [ key : string ] : spec . Property | spec . Method } {
1458
+ const ret : { [ key : string ] : spec . Property | spec . Method } = { } ;
1459
+
1460
+ for ( const prop of jsiiType . properties || [ ] ) {
1461
+ ret [ prop . name ] = prop ;
1397
1462
}
1398
- if ( jsiiType . properties ) {
1399
- ret . push ( ...jsiiType . properties . map ( m => m . name ) ) ;
1463
+
1464
+ for ( const method of jsiiType . methods || [ ] ) {
1465
+ ret [ method . name || '' ] = method ;
1400
1466
}
1467
+
1401
1468
return ret ;
1402
1469
}
1403
1470
@@ -1415,3 +1482,11 @@ function getConstructor(type: ts.Type): ts.Symbol | undefined {
1415
1482
return type . symbol . members
1416
1483
&& type . symbol . members . get ( ts . InternalSymbolName . Constructor ) ;
1417
1484
}
1485
+
1486
+ function * intersect < T > ( xs : Set < T > , ys : Set < T > ) {
1487
+ for ( const x of xs ) {
1488
+ if ( ys . has ( x ) ) {
1489
+ yield x ;
1490
+ }
1491
+ }
1492
+ }
0 commit comments