@@ -5,6 +5,21 @@ import {FacadeConverter} from './facade_converter';
5
5
import { Transpiler } from './main' ;
6
6
import { MergedMember , MergedParameter , MergedType , MergedTypeParameters } from './merge' ;
7
7
8
+ const PRIVATE_CLASS_INSTANCE_IN_EXTENSIONS = 'tt' ;
9
+
10
+ const enum emitPropertyMode {
11
+ getter ,
12
+ setter
13
+ }
14
+ type emitPropertyOptions = {
15
+ mode : emitPropertyMode ,
16
+ declaration : ts . PropertyDeclaration | ts . PropertySignature | ts . VariableDeclaration |
17
+ ts . ParameterDeclaration ,
18
+ emitJsAnnotation : boolean ,
19
+ isExternal : boolean ,
20
+ emitBody ?: ( declaration : ts . PropertyDeclaration | ts . PropertySignature ) => void
21
+ } ;
22
+
8
23
export function isFunctionLikeProperty (
9
24
decl : ts . VariableDeclaration | ts . ParameterDeclaration | ts . PropertyDeclaration |
10
25
ts . PropertySignature ,
@@ -22,7 +37,7 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
22
37
private extendsClass = false ;
23
38
private visitPromises = false ;
24
39
private containsPromises = false ;
25
- private promiseMethods : ts . SignatureDeclaration [ ] = [ ] ;
40
+ private promiseMembers : ts . SignatureDeclaration [ ] = [ ] ;
26
41
27
42
static NUM_FAKE_REST_PARAMETERS = 5 ;
28
43
@@ -128,13 +143,13 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
128
143
}
129
144
}
130
145
131
- private visitName ( name : ts . Node ) {
146
+ private visitName ( name : ts . PropertyName | ts . BindingName ) {
132
147
if ( base . getEnclosingClass ( name ) != null ) {
133
148
this . visit ( name ) ;
134
149
return ;
135
150
}
136
151
// Have to rewrite names in this case as we could have conflicts
137
- // due to needing to support multiple JS modules in a single JS module
152
+ // due to needing to support multiple JS modules in a single Dart module
138
153
if ( ! ts . isIdentifier ( name ) ) {
139
154
throw 'Internal error: unexpected function name kind:' + name . kind ;
140
155
}
@@ -327,16 +342,17 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
327
342
328
343
orderedGroups . forEach ( ( group : Array < ts . SignatureDeclaration > ) => {
329
344
const first = group [ 0 ] ;
330
- // If the methods in this group return Promises and this.visitPromises is false, skip
331
- // visiting these methods and add them to this.promiseMethods. If the methods in this group
332
- // return Promises and this.visitPromises is true, it means that this function is being called
333
- // from emitMethodsAsExtensions and the methods should now be visited.
345
+ // If the members in this group are Promise properties or Promise-returning methods and
346
+ // this.visitPromises is false, skip visiting these members and add them to
347
+ // this.promiseMembers. If the members in this group are/return Promises and
348
+ // this.visitPromises is true, it means that this function is being called from
349
+ // emitMembersAsExtensions and the members should now be visited.
334
350
if ( ! this . visitPromises && base . isPromise ( first . type ) ) {
335
351
if ( ! this . containsPromises ) {
336
352
this . containsPromises = true ;
337
353
}
338
354
group . forEach ( ( declaration : ts . SignatureDeclaration ) => {
339
- this . promiseMethods . push ( declaration ) ;
355
+ this . promiseMembers . push ( declaration ) ;
340
356
} ) ;
341
357
return ;
342
358
}
@@ -529,7 +545,12 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
529
545
this . visitMergingOverloads ( sourceFile . statements ) ;
530
546
if ( this . containsPromises ) {
531
547
this . addImport ( 'package:js/js_util.dart' , 'promiseToFuture' ) ;
532
- this . emit ( `@JS() abstract class Promise<T> {}\n` ) ;
548
+ // TODO(derekx): Move this definition of the Promise class to a package
549
+ this . emit ( `@JS()
550
+ abstract class Promise<T> {
551
+ external factory Promise(void executor(void resolve(T result), Function reject));
552
+ external Promise then(void onFulfilled(T result), [Function onRejected]);
553
+ }\n` ) ;
533
554
}
534
555
break ;
535
556
case ts . SyntaxKind . ModuleBlock : {
@@ -540,26 +561,25 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
540
561
// We have to handle variable declaration lists differently in the case
541
562
// of JS interop because Dart does not support external variables.
542
563
let varDeclList = < ts . VariableDeclarationList > node ;
543
- this . visitList ( varDeclList . declarations , '; ' ) ;
564
+ this . visitList ( varDeclList . declarations , ' ' ) ;
544
565
} break ;
545
566
case ts . SyntaxKind . VariableDeclaration : {
546
567
// We have to handle variable declarations differently in the case of JS
547
568
// interop because Dart does not support external variables.
548
569
let varDecl = < ts . VariableDeclaration > node ;
549
- this . maybeEmitJsAnnotation ( varDecl ) ;
550
- this . emit ( 'external' ) ;
551
- this . visit ( varDecl . type ) ;
552
- this . emit ( 'get' ) ;
553
- this . visitName ( varDecl . name ) ;
570
+ this . emitProperty ( {
571
+ mode : emitPropertyMode . getter ,
572
+ declaration : varDecl ,
573
+ emitJsAnnotation : true ,
574
+ isExternal : true
575
+ } ) ;
554
576
if ( ! this . hasNodeFlag ( varDecl , ts . NodeFlags . Const ) ) {
555
- this . emitNoSpace ( ';' ) ;
556
- this . maybeEmitJsAnnotation ( varDecl ) ;
557
- this . emit ( 'external' ) ;
558
- this . emit ( 'set' ) ;
559
- this . visitName ( varDecl . name ) ;
560
- this . emitNoSpace ( '(' ) ;
561
- this . visit ( varDecl . type ) ;
562
- this . emit ( 'v)' ) ;
577
+ this . emitProperty ( {
578
+ mode : emitPropertyMode . setter ,
579
+ declaration : varDecl ,
580
+ emitJsAnnotation : true ,
581
+ isExternal : true
582
+ } ) ;
563
583
}
564
584
} break ;
565
585
case ts . SyntaxKind . StringLiteral : {
@@ -651,9 +671,9 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
651
671
case ts . SyntaxKind . ExpressionWithTypeArguments : {
652
672
let exprWithTypeArgs = < ts . ExpressionWithTypeArguments > node ;
653
673
let expr = exprWithTypeArgs . expression ;
654
- if ( expr . kind === ts . SyntaxKind . Identifier || expr . kind === ts . SyntaxKind . QualifiedName ||
655
- expr . kind === ts . SyntaxKind . PropertyAccessExpression ) {
656
- this . fc . visitTypeName ( expr as ( ts . EntityName | ts . PropertyAccessExpression ) ) ;
674
+ if ( ts . isIdentifier ( expr ) || ts . isQualifiedName ( expr ) ||
675
+ ts . isPropertyAccessExpression ( expr ) ) {
676
+ this . fc . visitTypeName ( expr ) ;
657
677
} else {
658
678
this . visit ( expr ) ;
659
679
}
@@ -743,7 +763,6 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
743
763
case ts . SyntaxKind . VariableStatement :
744
764
let variableStmt = < ts . VariableStatement > node ;
745
765
this . visit ( variableStmt . declarationList ) ;
746
- this . emitNoSpace ( ';' ) ;
747
766
break ;
748
767
case ts . SyntaxKind . SwitchStatement :
749
768
case ts . SyntaxKind . ArrayLiteralExpression :
@@ -771,7 +790,7 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
771
790
if ( name . kind !== ts . SyntaxKind . Identifier ) {
772
791
this . reportError ( name , 'Unexpected name kind:' + name . kind ) ;
773
792
}
774
- this . fc . visitTypeName ( < ts . Identifier > name ) ;
793
+ this . fc . visitTypeName ( name ) ;
775
794
}
776
795
777
796
if ( fn . typeParameters ) {
@@ -815,30 +834,26 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
815
834
decl : ts . PropertyDeclaration | ts . ParameterDeclaration , isParameter ?: boolean ) {
816
835
const hasValidName =
817
836
! ts . isStringLiteral ( decl . name ) || base . isValidDartIdentifier ( decl . name . text ) ;
818
- const isStatic = base . isStatic ( decl ) ;
819
837
820
838
// TODO(derekx): Properties with names that contain special characters are currently ignored by
821
839
// commenting them out. Determine a way to rename these properties in the future.
822
840
this . maybeWrapInCodeComment ( { shouldWrap : ! hasValidName , newLine : true } , ( ) => {
823
- this . emit ( 'external' ) ;
824
- if ( isStatic ) this . emit ( 'static' ) ;
825
- this . visit ( decl . type ) ;
826
- this . emit ( 'get' ) ;
827
- this . visitName ( decl . name ) ;
828
- this . emitNoSpace ( ';' ) ;
841
+ this . emitProperty ( {
842
+ mode : emitPropertyMode . getter ,
843
+ declaration : decl ,
844
+ emitJsAnnotation : false ,
845
+ isExternal : true
846
+ } ) ;
829
847
} ) ;
830
848
831
849
if ( ! base . isReadonly ( decl ) ) {
832
850
this . maybeWrapInCodeComment ( { shouldWrap : ! hasValidName , newLine : true } , ( ) => {
833
- this . emit ( 'external' ) ;
834
- if ( isStatic ) this . emit ( 'static' ) ;
835
- this . emit ( 'set' ) ;
836
- this . visitName ( decl . name ) ;
837
- this . emitNoSpace ( '(' ) ;
838
- this . visit ( decl . type ) ;
839
- this . emit ( 'v' ) ;
840
- this . emitNoSpace ( ')' ) ;
841
- this . emitNoSpace ( ';' ) ;
851
+ this . emitProperty ( {
852
+ mode : emitPropertyMode . setter ,
853
+ declaration : decl ,
854
+ emitJsAnnotation : false ,
855
+ isExternal : true
856
+ } ) ;
842
857
} ) ;
843
858
}
844
859
}
@@ -881,15 +896,15 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
881
896
882
897
this . visitClassBody ( decl , name ) ;
883
898
this . emit ( '}\n' ) ;
884
- if ( this . promiseMethods . length ) {
899
+ if ( this . promiseMembers . length ) {
885
900
const visitName = ( ) => {
886
901
this . visitClassLikeName ( name , typeParameters , ts . createNodeArray ( ) , false ) ;
887
902
} ;
888
903
const visitNameOfExtensions = ( ) => {
889
904
this . visitClassLikeName ( name , typeParameters , ts . createNodeArray ( ) , true ) ;
890
905
} ;
891
- this . emitMethodsAsExtensions ( name , visitName , visitNameOfExtensions , this . promiseMethods ) ;
892
- this . promiseMethods = [ ] ;
906
+ this . emitMembersAsExtensions ( name , visitName , visitNameOfExtensions , this . promiseMembers ) ;
907
+ this . promiseMembers = [ ] ;
893
908
}
894
909
this . emit ( '\n' ) ;
895
910
}
@@ -963,48 +978,106 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
963
978
this . emitNoSpace ( ';' ) ;
964
979
}
965
980
966
- private emitMethodsAsExtensions (
967
- className : ts . Identifier , visitName : ( ) => void , visitNameOfExtensions : ( ) => void ,
981
+ private emitProperty ( { mode, declaration, emitJsAnnotation, isExternal, emitBody} :
982
+ emitPropertyOptions ) {
983
+ const { name, type} = declaration ;
984
+
985
+ if ( emitJsAnnotation ) {
986
+ this . maybeEmitJsAnnotation ( declaration ) ;
987
+ }
988
+ if ( isExternal ) {
989
+ this . emit ( 'external' ) ;
990
+ }
991
+ if ( base . isStatic ( declaration ) ) {
992
+ this . emit ( 'static' ) ;
993
+ }
994
+ if ( mode === emitPropertyMode . getter ) {
995
+ this . visit ( type ) ;
996
+ this . emit ( 'get' ) ;
997
+ this . visitName ( name ) ;
998
+ } else if ( mode === emitPropertyMode . setter ) {
999
+ this . emit ( 'set' ) ;
1000
+ this . visitName ( name ) ;
1001
+ this . emitNoSpace ( '(' ) ;
1002
+ this . visit ( type ) ;
1003
+ this . emit ( 'v)' ) ;
1004
+ }
1005
+ if ( emitBody && ! ts . isVariableDeclaration ( declaration ) && ! ts . isParameter ( declaration ) ) {
1006
+ this . emit ( '{' ) ;
1007
+ emitBody ( declaration ) ;
1008
+ this . emit ( '}' ) ;
1009
+ } else {
1010
+ this . emitNoSpace ( ';' ) ;
1011
+ }
1012
+ }
1013
+
1014
+ private emitCastThisToPrivateClass ( visitClassName : ( ) => void ) {
1015
+ this . emit ( 'final Object t = this;' ) ;
1016
+ this . emit ( 'final _' ) ;
1017
+ visitClassName ( ) ;
1018
+ this . emit ( 'tt = t;\n' ) ;
1019
+ }
1020
+
1021
+ private emitMembersAsExtensions (
1022
+ className : ts . Identifier , visitClassName : ( ) => void , visitNameOfExtensions : ( ) => void ,
968
1023
methods : ts . SignatureDeclaration [ ] ) {
969
1024
this . visitPromises = true ;
1025
+ this . fc . emitPromisesAsFutures = false ;
970
1026
// Emit private class containing external methods
971
1027
this . emit ( `@JS('${ base . ident ( className ) } ')` ) ;
972
1028
this . emit ( `abstract class _` ) ;
973
- visitName ( ) ;
1029
+ visitClassName ( ) ;
974
1030
this . emit ( '{' ) ;
975
1031
const mergedMembers = this . visitMergingOverloads ( ts . createNodeArray ( Array . from ( methods ) ) ) ;
976
1032
this . emit ( '}\n' ) ;
1033
+ this . fc . emitPromisesAsFutures = true ;
977
1034
978
1035
// Emit extensions on public class to expose methods
979
1036
this . emit ( 'extension' ) ;
980
1037
visitNameOfExtensions ( ) ;
981
1038
this . emit ( 'on' ) ;
982
- visitName ( ) ;
1039
+ visitClassName ( ) ;
983
1040
this . emit ( '{' ) ;
984
1041
for ( const merged of mergedMembers ) {
985
1042
const declaration = merged . mergedDeclaration ;
986
- if ( ! base . isPromise ( declaration . type ) ) {
987
- continue ;
988
- }
989
- this . emit ( 'Future' ) ;
990
- if ( ts . isTypeReferenceNode ( declaration . type ) ) {
991
- this . maybeVisitTypeArguments ( declaration . type ) ;
1043
+ if ( ts . isPropertyDeclaration ( declaration ) || ts . isPropertySignature ( declaration ) ) {
1044
+ this . emitProperty ( {
1045
+ mode : emitPropertyMode . getter ,
1046
+ declaration,
1047
+ emitJsAnnotation : false ,
1048
+ isExternal : false ,
1049
+ emitBody : ( ) => {
1050
+ this . emitCastThisToPrivateClass ( visitClassName ) ;
1051
+ this . emitExtensionGetterBody ( declaration ) ;
1052
+ }
1053
+ } ) ;
1054
+ if ( ! base . isReadonly ( declaration ) ) {
1055
+ this . emitProperty ( {
1056
+ mode : emitPropertyMode . setter ,
1057
+ declaration,
1058
+ emitJsAnnotation : false ,
1059
+ isExternal : false ,
1060
+ emitBody : ( ) => {
1061
+ this . emitCastThisToPrivateClass ( visitClassName ) ;
1062
+ this . emitExtensionSetterBody ( declaration ) ;
1063
+ }
1064
+ } ) ;
1065
+ }
1066
+ } else if ( ts . isMethodDeclaration ( declaration ) || ts . isMethodSignature ( declaration ) ) {
1067
+ this . visit ( declaration . type ) ;
1068
+ this . visitName ( declaration . name ) ;
1069
+ this . visitParameters ( declaration . parameters , { namesOnly : false } ) ;
1070
+ this . emit ( '{' ) ;
1071
+ this . emitCastThisToPrivateClass ( visitClassName ) ;
1072
+ this . emitExtensionMethodBody ( merged ) ;
1073
+ this . emit ( '}\n' ) ;
992
1074
}
993
- this . emit ( base . ident ( declaration . name ) ) ;
994
- this . visitParameters ( declaration . parameters , { namesOnly : false } ) ;
995
- this . emit ( '{' ) ;
996
- this . emit ( 'final Object t = this;' ) ;
997
- this . emit ( 'final _' ) ;
998
- this . fc . visitTypeName ( className ) ;
999
- this . emit ( 'tt = t;\n' ) ;
1000
- this . emitExtensionBody ( merged ) ;
1001
- this . emit ( '}\n' ) ;
1002
1075
}
1003
1076
this . emit ( '}\n' ) ;
1004
1077
this . visitPromises = false ;
1005
1078
}
1006
1079
1007
- private emitExtensionBody ( { constituents, mergedDeclaration} : MergedMember ) {
1080
+ private emitExtensionMethodBody ( { constituents, mergedDeclaration} : MergedMember ) {
1008
1081
// Determine all valid arties of this method by going through the overloaded signatures
1009
1082
const arities : Set < number > = new Set ( ) ;
1010
1083
for ( const constituent of constituents ) {
@@ -1031,15 +1104,42 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
1031
1104
this . emit ( '== null' ) ;
1032
1105
}
1033
1106
this . emit ( ') {' ) ;
1034
- this . emit ( `return promiseToFuture(tt.${ base . ident ( mergedDeclaration . name ) } ` ) ;
1107
+ this . emit ( 'return promiseToFuture(' ) ;
1108
+ this . emit ( PRIVATE_CLASS_INSTANCE_IN_EXTENSIONS ) ;
1109
+ this . emit ( '.' ) ;
1110
+ this . visitName ( mergedDeclaration . name ) ;
1035
1111
this . visitParameters ( ts . createNodeArray ( suppliedParameters ) , { namesOnly : true } ) ;
1036
1112
this . emit ( '); }\n' ) ;
1037
1113
} else {
1038
1114
// No parameters were omitted, no null checks are necessary for this call
1039
- this . emit ( `return promiseToFuture(tt.${ base . ident ( mergedDeclaration . name ) } ` ) ;
1115
+ this . emit ( 'return promiseToFuture(' ) ;
1116
+ this . emit ( PRIVATE_CLASS_INSTANCE_IN_EXTENSIONS ) ;
1117
+ this . emit ( '.' ) ;
1118
+ this . visitName ( mergedDeclaration . name ) ;
1040
1119
this . visitParameters ( ts . createNodeArray ( mergedDeclaration . parameters ) , { namesOnly : true } ) ;
1041
1120
this . emit ( ');\n' ) ;
1042
1121
}
1043
1122
}
1044
1123
}
1124
+
1125
+ private emitExtensionGetterBody ( declaration : ts . PropertyDeclaration | ts . PropertySignature ) {
1126
+ this . emit ( 'return promiseToFuture(' ) ;
1127
+ this . emit ( PRIVATE_CLASS_INSTANCE_IN_EXTENSIONS ) ;
1128
+ this . emit ( '.' ) ;
1129
+ this . visitName ( declaration . name ) ;
1130
+ this . emit ( ');' ) ;
1131
+ }
1132
+
1133
+ private emitExtensionSetterBody ( declaration : ts . PropertyDeclaration | ts . PropertySignature ) {
1134
+ this . emit ( PRIVATE_CLASS_INSTANCE_IN_EXTENSIONS ) ;
1135
+ this . emit ( '.' ) ;
1136
+ this . visitName ( declaration . name ) ;
1137
+ this . emit ( '=' ) ;
1138
+ // To emit the call to the Promise constructor, we need to temporarily disable
1139
+ // this.fc.emitPromisesAsFutures
1140
+ this . fc . emitPromisesAsFutures = false ;
1141
+ this . visit ( declaration . type ) ;
1142
+ this . fc . emitPromisesAsFutures = true ;
1143
+ this . emit ( '(allowInterop((resolve, reject) { v.then(resolve, onError: reject); }));' ) ;
1144
+ }
1045
1145
}
0 commit comments