@@ -190,7 +190,7 @@ export class Assembler implements Emitter {
190190 *
191191 * @returns the de-referenced type, if it was found, otherwise ``undefined``.
192192 */
193- private _dereference ( ref : spec . NamedTypeReference , referencingNode : ts . Node ) : spec . Type | undefined {
193+ private _dereference ( ref : spec . NamedTypeReference , referencingNode : ts . Node | null ) : spec . Type | undefined {
194194 const [ assm , ] = ref . fqn . split ( '.' ) ;
195195 let type ;
196196 if ( assm === this . projectInfo . name ) {
@@ -615,11 +615,13 @@ export class Assembler implements Emitter {
615615 this . _diagnostic ( declaration , ts . DiagnosticCategory . Error , `Unable to compute signature for ${ type . fqn } #${ symbol . name } ` ) ;
616616 return ;
617617 }
618+ const parameters = await Promise . all ( signature . getParameters ( ) . map ( p => this . _toParameter ( p ) ) ) ;
619+
618620 const returnType = signature . getReturnType ( ) ;
619621 const method : spec . Method = {
620622 abstract : _isAbstract ( symbol , type ) ,
621623 name : symbol . name ,
622- parameters : await Promise . all ( signature . getParameters ( ) . map ( p => this . _toParameter ( p ) ) ) ,
624+ parameters,
623625 protected : _isProtected ( symbol ) ,
624626 returns : _isVoid ( returnType ) ? undefined : await this . _typeReference ( returnType , declaration ) ,
625627 static : _isStatic ( symbol ) ,
@@ -628,6 +630,29 @@ export class Assembler implements Emitter {
628630
629631 this . _visitDocumentation ( symbol , method ) ;
630632
633+ // If the last parameter is a datatype, verify that it does not share any field names with
634+ // other function arguments, so that it can be turned into keyword arguments by jsii frontends
635+ // that support such.
636+ const lastParamTypeRef = apply ( last ( parameters ) , x => x . type ) ;
637+ const lastParamSymbol = last ( signature . getParameters ( ) ) ;
638+ if ( lastParamTypeRef && spec . isNamedTypeReference ( lastParamTypeRef ) ) {
639+ this . _deferUntilTypesAvailable ( symbol . name , [ lastParamTypeRef ] , lastParamSymbol ! . declarations [ 0 ] , ( lastParamType ) => {
640+ if ( ! spec . isInterfaceType ( lastParamType ) || ! lastParamType . datatype ) { return ; }
641+
642+ // Liftable datatype, make sure no parameter names match any of the properties in the datatype
643+ const propNames = this . allProperties ( lastParamType ) ;
644+ const paramNames = new Set ( parameters . slice ( 0 , parameters . length - 1 ) . map ( x => x . name ) ) ;
645+ const sharedNames = intersection ( propNames , paramNames ) ;
646+
647+ if ( sharedNames . size > 0 ) {
648+ this . _diagnostic (
649+ declaration ,
650+ ts . DiagnosticCategory . Error ,
651+ `Name occurs in both function arguments and in datatype properties, rename one: ${ Array . from ( sharedNames ) . join ( ', ' ) } ` ) ;
652+ }
653+ } ) ;
654+ }
655+
631656 type . methods = type . methods || [ ] ;
632657 type . methods . push ( method ) ;
633658 }
@@ -867,6 +892,31 @@ export class Assembler implements Emitter {
867892 def . dependedFqns = def . dependedFqns . filter ( fqns . has . bind ( fqns ) ) ;
868893 }
869894 }
895+
896+ /**
897+ * Return the set of all (inherited) properties of an interface
898+ */
899+ private allProperties ( root : spec . InterfaceType ) : Set < string > {
900+ const self = this ;
901+
902+ const ret = new Set < string > ( ) ;
903+ recurse ( root ) ;
904+ return ret ;
905+
906+ function recurse ( int : spec . InterfaceType ) {
907+ for ( const property of int . properties || [ ] ) {
908+ ret . add ( property . name ) ;
909+ }
910+
911+ for ( const baseRef of int . interfaces || [ ] ) {
912+ const base = self . _dereference ( baseRef , null ) ;
913+ if ( ! base ) { throw new Error ( 'Impossible to have unresolvable base in allProperties()' ) ; }
914+ if ( ! spec . isInterfaceType ( base ) ) { throw new Error ( 'Impossible to have non-interface base in allProperties()' ) ; }
915+
916+ recurse ( base ) ;
917+ }
918+ }
919+ }
870920}
871921
872922/**
@@ -1024,6 +1074,33 @@ interface DeferredRecord {
10241074 cb : ( ) => void ;
10251075}
10261076
1077+ /**
1078+ * Return the last element from a list
1079+ */
1080+ function last < T > ( xs : T [ ] ) : T | undefined {
1081+ return xs . length > 0 ? xs [ xs . length - 1 ] : undefined ;
1082+ }
1083+
1084+ /**
1085+ * Apply a function to a value if it's not equal to undefined
1086+ */
1087+ function apply < T , U > ( x : T | undefined , fn : ( x : T ) => U | undefined ) : U | undefined {
1088+ return x !== undefined ? fn ( x ) : undefined ;
1089+ }
1090+
1091+ /**
1092+ * Return the intersection of two sets
1093+ */
1094+ function intersection < T > ( xs : Set < T > , ys : Set < T > ) : Set < T > {
1095+ const ret = new Set < T > ( ) ;
1096+ for ( const x of xs ) {
1097+ if ( ys . has ( x ) ) {
1098+ ret . add ( x ) ;
1099+ }
1100+ }
1101+ return ret ;
1102+ }
1103+
10271104/**
10281105 * Whether or not the given name is conventionally an interface name
10291106 *
@@ -1032,4 +1109,4 @@ interface DeferredRecord {
10321109 */
10331110function isInterfaceName ( name : string ) {
10341111 return name . length >= 2 && name . charAt ( 0 ) === 'I' && name . charAt ( 1 ) . toUpperCase ( ) === name . charAt ( 1 ) ;
1035- }
1112+ }
0 commit comments