OLD | NEW |
1 import * as ts from 'typescript'; | 1 import * as ts from 'typescript'; |
| 2 |
2 import * as base from './base'; | 3 import * as base from './base'; |
| 4 import {FacadeConverter} from './facade_converter'; |
3 import {Transpiler} from './main'; | 5 import {Transpiler} from './main'; |
4 import {FacadeConverter} from './facade_converter'; | 6 import {MergedParameter, MergedType} from './merge'; |
| 7 |
| 8 export function isFunctionLikeProperty( |
| 9 decl: ts.PropertyDeclaration|ts.ParameterDeclaration, tc: ts.TypeChecker): b
oolean { |
| 10 if (!decl.type) return false; |
| 11 let name = base.ident(decl.name); |
| 12 if (name.match(/^on[A-Z]/)) return false; |
| 13 return base.isFunctionType(decl.type, tc); |
| 14 } |
5 | 15 |
6 export default class DeclarationTranspiler extends base.TranspilerBase { | 16 export default class DeclarationTranspiler extends base.TranspilerBase { |
| 17 private tc: ts.TypeChecker; |
| 18 |
| 19 private moduleStack: string[] = []; |
| 20 private extendsClass: boolean = false; |
| 21 |
| 22 static NUM_FAKE_REST_PARAMETERS = 5; |
| 23 |
| 24 setTypeChecker(tc: ts.TypeChecker) { this.tc = tc; } |
| 25 setFacadeConverter(fc: FacadeConverter) { this.fc = fc; } |
| 26 |
| 27 getJsPath(node: ts.Node): string { |
| 28 let path = [].concat(this.moduleStack); |
| 29 let classDecl = base.getEnclosingClass(node); |
| 30 if (classDecl) { |
| 31 path.push(classDecl.name.text); |
| 32 } |
| 33 |
| 34 switch (node.kind) { |
| 35 case ts.SyntaxKind.ModuleDeclaration: |
| 36 break; |
| 37 case ts.SyntaxKind.ClassDeclaration: |
| 38 case ts.SyntaxKind.InterfaceDeclaration: |
| 39 path.push((<base.ClassLike>node).name.text); |
| 40 break; |
| 41 case ts.SyntaxKind.EnumDeclaration: |
| 42 path.push((<ts.EnumDeclaration>node).name.text); |
| 43 break; |
| 44 case ts.SyntaxKind.PropertyDeclaration: |
| 45 case ts.SyntaxKind.VariableDeclaration: |
| 46 case ts.SyntaxKind.MethodDeclaration: |
| 47 case ts.SyntaxKind.FunctionDeclaration: |
| 48 case ts.SyntaxKind.GetAccessor: |
| 49 case ts.SyntaxKind.SetAccessor: |
| 50 case ts.SyntaxKind.PropertySignature: |
| 51 let memberName = base.ident((<base.NamedDeclaration>node).name); |
| 52 if (!base.isStatic(node) && classDecl != null) return memberName; |
| 53 path.push(memberName); |
| 54 break; |
| 55 default: |
| 56 throw 'Internal error. Unexpected node kind:' + node.kind; |
| 57 } |
| 58 if (path.length === 1) { |
| 59 // No need to specify the path if is simply the node name. |
| 60 return ''; |
| 61 } |
| 62 return path.join('.'); |
| 63 } |
| 64 |
| 65 private isAnonymousInterface(node: ts.Node): boolean { |
| 66 if (node.kind !== ts.SyntaxKind.InterfaceDeclaration) return false; |
| 67 // This is a bit of a hack but for the purposes of Dart codegen, |
| 68 // interfaces with static members or constructors have a known class name |
| 69 // at least for the purposes of resolving static members. |
| 70 // Example case that triggers this case: |
| 71 // interface Foo { |
| 72 // bar(); |
| 73 // } |
| 74 // declare let Foo: { |
| 75 // new(): Foo, |
| 76 // SOME_STATIC : number; |
| 77 // } |
| 78 return (<ts.InterfaceDeclaration>node).members.every((m: ts.Declaration) =>
{ |
| 79 return m.kind !== ts.SyntaxKind.Constructor && !base.isStatic(m); |
| 80 }); |
| 81 } |
| 82 |
| 83 maybeEmitJsAnnotation(node: ts.Node) { |
| 84 if (this.isAnonymousInterface(node)) { |
| 85 this.emit('@anonymous'); |
| 86 this.emit('@JS()'); |
| 87 return; |
| 88 } |
| 89 let name: String = this.getJsPath(node); |
| 90 this.emit('@JS('); |
| 91 if (name.length > 0) { |
| 92 this.emit('"' + name + '"'); |
| 93 } |
| 94 this.emit(')'); |
| 95 } |
| 96 |
| 97 /** |
| 98 * Emit fake constructors to placate the Dart Analyzer for JS Interop classes. |
| 99 */ |
| 100 maybeEmitFakeConstructors(decl: base.ClassLike) { |
| 101 if (decl.kind === ts.SyntaxKind.ClassDeclaration) { |
| 102 // Required to avoid spurious dart errors involving base classes without |
| 103 // default constructors. |
| 104 this.emit('// @Ignore\n'); |
| 105 this.fc.visitTypeName(decl.name); |
| 106 this.emit('.fakeConstructor$()'); |
| 107 if (this.extendsClass) { |
| 108 // Required to keep the Dart Analyzer happy when a class has subclasses. |
| 109 this.emit(': super.fakeConstructor$()'); |
| 110 } |
| 111 this.emit(';\n'); |
| 112 } |
| 113 } |
| 114 |
| 115 private visitName(name: ts.Node) { |
| 116 if (base.getEnclosingClass(name) != null) { |
| 117 this.visit(name); |
| 118 return; |
| 119 } |
| 120 // Have to rewrite names in this case as we could have conflicts |
| 121 // due to needing to support multiple JS modules in a single JS module |
| 122 if (name.kind !== ts.SyntaxKind.Identifier) { |
| 123 throw 'Internal error: unexpected function name kind:' + name.kind; |
| 124 } |
| 125 let entry = this.fc.lookupCustomDartTypeName(<ts.Identifier>name); |
| 126 if (entry) { |
| 127 this.emit(entry.name); |
| 128 return; |
| 129 } |
| 130 |
| 131 this.visit(name); |
| 132 } |
| 133 |
| 134 private notSimpleBagOfProperties(type: ts.Type): boolean { |
| 135 if (this.tc.getSignaturesOfType(type, ts.SignatureKind.Call).length > 0) ret
urn true; |
| 136 if (this.tc.getSignaturesOfType(type, ts.SignatureKind.Construct).length > 0
) return true; |
| 137 if (type.symbol) { |
| 138 let declaration = <ts.InterfaceDeclaration>type.symbol.declarations[0]; |
| 139 // We have to check the actual declaration as |
| 140 if (declaration && declaration.members) { |
| 141 let members = declaration.members; |
| 142 for (let i = 0; i < members.length; ++i) { |
| 143 let node = members[i]; |
| 144 if (base.isStatic(node)) return true; |
| 145 switch (node.kind) { |
| 146 case ts.SyntaxKind.PropertyDeclaration: |
| 147 case ts.SyntaxKind.PropertySignature: |
| 148 case ts.SyntaxKind.VariableDeclaration: |
| 149 break; |
| 150 default: |
| 151 return true; |
| 152 } |
| 153 } |
| 154 } |
| 155 } |
| 156 return false; |
| 157 } |
| 158 |
| 159 /** |
| 160 * Returns whether all members of the class and all base classes |
| 161 */ |
| 162 hasOnlyProperties(decl: ts.InterfaceDeclaration, outProperties: ts.PropertyDec
laration[]): |
| 163 boolean { |
| 164 let type = <ts.InterfaceType>this.tc.getTypeAtLocation(decl); |
| 165 |
| 166 let properties = this.tc.getPropertiesOfType(type); |
| 167 let baseTypes = this.tc.getBaseTypes(type); |
| 168 if (this.notSimpleBagOfProperties(type)) return false; |
| 169 for (let i = 0; i < baseTypes.length; ++i) { |
| 170 let baseType = baseTypes[i]; |
| 171 if (this.notSimpleBagOfProperties(baseType)) return false; |
| 172 } |
| 173 |
| 174 for (let i = 0; i < properties.length; ++i) { |
| 175 let symbol = properties[i]; |
| 176 let node = symbol.valueDeclaration; |
| 177 switch (node.kind) { |
| 178 case ts.SyntaxKind.PropertyDeclaration: |
| 179 case ts.SyntaxKind.PropertySignature: |
| 180 case ts.SyntaxKind.VariableDeclaration: |
| 181 let prop = <ts.PropertyDeclaration>node; |
| 182 if (this.promoteFunctionLikeMembers && isFunctionLikeProperty(prop, th
is.tc)) { |
| 183 return false; |
| 184 } |
| 185 outProperties.push(prop); |
| 186 break; |
| 187 default: |
| 188 return false; |
| 189 } |
| 190 } |
| 191 return outProperties.length > 0; |
| 192 } |
| 193 |
| 194 visitClassBody(decl: base.ClassLike) { |
| 195 let properties: ts.PropertyDeclaration[] = []; |
| 196 let isPropertyBag = decl.kind === ts.SyntaxKind.InterfaceDeclaration && |
| 197 this.hasOnlyProperties(<ts.InterfaceDeclaration>decl, properties); |
| 198 this.visitMergingOverloads(decl.members); |
| 199 |
| 200 if (isPropertyBag) { |
| 201 this.emit('external factory'); |
| 202 this.fc.visitTypeName(decl.name); |
| 203 this.emit('({'); |
| 204 for (let i = 0; i < properties.length; i++) { |
| 205 if (i > 0) this.emit(','); |
| 206 let p = properties[i]; |
| 207 this.visit(p.type); |
| 208 this.visit(p.name); |
| 209 } |
| 210 this.emit('});'); |
| 211 } |
| 212 } |
| 213 |
| 214 visitMergingOverloads(members: Array<ts.Node>) { |
| 215 // TODO(jacobr): merge method overloads. |
| 216 let groups: {[name: string]: Array<ts.Node>} = {}; |
| 217 let orderedGroups: Array<Array<ts.Node>> = []; |
| 218 members.forEach((node) => { |
| 219 let name = ''; |
| 220 switch (node.kind) { |
| 221 case ts.SyntaxKind.Block: |
| 222 // For JS interop we always skip the contents of a block. |
| 223 break; |
| 224 case ts.SyntaxKind.PropertyDeclaration: |
| 225 case ts.SyntaxKind.PropertySignature: |
| 226 case ts.SyntaxKind.VariableDeclaration: { |
| 227 let propertyDecl = <ts.PropertyDeclaration|ts.VariableDeclaration>node
; |
| 228 // We need to emit these as properties not fields. |
| 229 if (!this.promoteFunctionLikeMembers || !isFunctionLikeProperty(proper
tyDecl, this.tc)) { |
| 230 orderedGroups.push([node]); |
| 231 return; |
| 232 } |
| 233 // Convert to a Method. |
| 234 let type = propertyDecl.type; |
| 235 let funcDecl = <ts.FunctionLikeDeclaration>ts.createNode(ts.SyntaxKind
.MethodDeclaration); |
| 236 funcDecl.parent = node.parent; |
| 237 funcDecl.name = propertyDecl.name as ts.Identifier; |
| 238 switch (type.kind) { |
| 239 case ts.SyntaxKind.FunctionType: |
| 240 let callSignature = <ts.SignatureDeclaration>(<ts.Node>type); |
| 241 funcDecl.parameters = <ts.NodeArray<ts.ParameterDeclaration>>callS
ignature.parameters; |
| 242 funcDecl.type = callSignature.type; |
| 243 // Fall through to the function case using this node |
| 244 node = funcDecl; |
| 245 break; |
| 246 case ts.SyntaxKind.UnionType: |
| 247 case ts.SyntaxKind.TypeLiteral: |
| 248 throw 'Not supported yet'; |
| 249 default: |
| 250 throw 'Unexpected case'; |
| 251 } |
| 252 name = base.ident((<ts.FunctionLikeDeclaration>node).name); |
| 253 } break; |
| 254 case ts.SyntaxKind.FunctionDeclaration: |
| 255 case ts.SyntaxKind.MethodDeclaration: |
| 256 case ts.SyntaxKind.MethodSignature: |
| 257 case ts.SyntaxKind.FunctionExpression: |
| 258 name = base.ident((<ts.FunctionLikeDeclaration>node).name); |
| 259 break; |
| 260 case ts.SyntaxKind.CallSignature: |
| 261 name = 'call'; |
| 262 break; |
| 263 case ts.SyntaxKind.Constructor: |
| 264 break; |
| 265 case ts.SyntaxKind.ConstructSignature: |
| 266 break; |
| 267 case ts.SyntaxKind.IndexSignature: |
| 268 name = '[]'; |
| 269 break; |
| 270 case ts.SyntaxKind.ClassDeclaration: |
| 271 case ts.SyntaxKind.InterfaceDeclaration: |
| 272 case ts.SyntaxKind.VariableStatement: |
| 273 orderedGroups.push([node]); |
| 274 return; |
| 275 case ts.SyntaxKind.GetAccessor: |
| 276 case ts.SyntaxKind.SetAccessor: |
| 277 case ts.SyntaxKind.SemicolonClassElement: |
| 278 case ts.SyntaxKind.ModuleDeclaration: |
| 279 orderedGroups.push([node]); |
| 280 return; |
| 281 default: |
| 282 console.log('Warning: unexpected type... overloads: ' + node.kind + '
' + node.getText()); |
| 283 orderedGroups.push([node]); |
| 284 return; |
| 285 } |
| 286 let group: Array<ts.Node>; |
| 287 if (Object.prototype.hasOwnProperty.call(groups, name)) { |
| 288 group = groups[name]; |
| 289 } else { |
| 290 group = []; |
| 291 groups[name] = group; |
| 292 orderedGroups.push(group); |
| 293 } |
| 294 group.push(node); |
| 295 }); |
| 296 |
| 297 orderedGroups.forEach((group: Array<ts.Node>) => { |
| 298 if (group.length === 1) { |
| 299 this.visit(group[0]); |
| 300 return; |
| 301 } |
| 302 group.forEach((fn: ts.Node) => { |
| 303 // Emit overrides in a comment that the Dart analyzer can at some point |
| 304 // use to improve autocomplete. |
| 305 this.enterCodeComment(); |
| 306 this.visit(fn); |
| 307 this.exitCodeComment(); |
| 308 this.emit('\n'); |
| 309 }); |
| 310 // TODO: actually merge. |
| 311 let first = <ts.SignatureDeclaration>group[0]; |
| 312 let kind = first.kind; |
| 313 let merged = <ts.SignatureDeclaration>ts.createNode(kind); |
| 314 merged.parent = first.parent; |
| 315 base.copyLocation(first, merged); |
| 316 switch (kind) { |
| 317 case ts.SyntaxKind.FunctionDeclaration: |
| 318 case ts.SyntaxKind.MethodDeclaration: |
| 319 case ts.SyntaxKind.MethodSignature: |
| 320 case ts.SyntaxKind.FunctionExpression: |
| 321 let fn = <ts.FunctionLikeDeclaration>first; |
| 322 merged.name = fn.name; |
| 323 break; |
| 324 case ts.SyntaxKind.CallSignature: |
| 325 break; |
| 326 case ts.SyntaxKind.Constructor: |
| 327 break; |
| 328 case ts.SyntaxKind.ConstructSignature: |
| 329 break; |
| 330 case ts.SyntaxKind.IndexSignature: |
| 331 break; |
| 332 default: |
| 333 throw 'Unexpected kind:' + kind; |
| 334 } |
| 335 let mergedParams = first.parameters.map( |
| 336 (param: ts.ParameterDeclaration) => new MergedParameter(param, this.fc
)); |
| 337 let mergedType = new MergedType(this.fc); |
| 338 mergedType.merge(first.type); |
| 339 |
| 340 for (let i = 1; i < group.length; ++i) { |
| 341 let signature = <ts.SignatureDeclaration>group[i]; |
| 342 mergedType.merge(signature.type); |
| 343 let overlap = Math.min(signature.parameters.length, mergedParams.length)
; |
| 344 for (let j = 0; j < overlap; ++j) { |
| 345 mergedParams[j].merge(signature.parameters[j]); |
| 346 } |
| 347 for (let j = overlap; j < mergedParams.length; ++j) { |
| 348 mergedParams[j].setOptional(); |
| 349 } |
| 350 for (let j = mergedParams.length; j < signature.parameters.length; ++j)
{ |
| 351 let param = new MergedParameter(signature.parameters[j], this.fc); |
| 352 param.setOptional(); |
| 353 mergedParams.push(param); |
| 354 } |
| 355 } |
| 356 merged.parameters = <ts.NodeArray<ts.ParameterDeclaration>>mergedParams.ma
p( |
| 357 (p) => p.toParameterDeclaration()); |
| 358 merged.type = mergedType.toTypeNode(); |
| 359 |
| 360 this.fc.visit(merged); |
| 361 }); |
| 362 } |
| 363 |
| 364 |
7 constructor( | 365 constructor( |
8 tr: Transpiler, private fc: FacadeConverter, private enforceUnderscoreConv
entions: boolean) { | 366 tr: Transpiler, private fc: FacadeConverter, private enforceUnderscoreConv
entions: boolean, |
| 367 private promoteFunctionLikeMembers: boolean) { |
9 super(tr); | 368 super(tr); |
10 } | 369 } |
11 | 370 |
12 visitNode(node: ts.Node): boolean { | 371 visitNode(node: ts.Node): boolean { |
13 switch (node.kind) { | 372 switch (node.kind) { |
14 case ts.SyntaxKind.VariableDeclarationList: | 373 case ts.SyntaxKind.ModuleDeclaration: |
15 // Note: VariableDeclarationList can only occur as part of a for loop. | 374 let moduleDecl = <ts.ModuleDeclaration>node; |
16 let varDeclList = <ts.VariableDeclarationList>node; | 375 this.emit('\n// Module ' + moduleDecl.name.text + '\n'); |
17 this.visitList(varDeclList.declarations); | 376 this.moduleStack.push(moduleDecl.name.text); |
18 break; | 377 |
19 case ts.SyntaxKind.VariableDeclaration: | 378 this.visit(moduleDecl.body); |
20 let varDecl = <ts.VariableDeclaration>node; | 379 this.emit('\n// End module ' + moduleDecl.name.text + '\n'); |
21 this.visitVariableDeclarationType(varDecl); | 380 this.moduleStack.pop(); |
22 this.visit(varDecl.name); | 381 break; |
23 if (varDecl.initializer) { | 382 case ts.SyntaxKind.ExportKeyword: |
24 this.emit('='); | 383 // TODO(jacobr): perhaps add a specific Dart annotation to indicate |
25 this.visit(varDecl.initializer); | 384 // exported members or provide a flag to only generate code for exported |
26 } | 385 // members. |
27 break; | 386 break; |
28 | 387 case ts.SyntaxKind.EnumDeclaration: { |
29 case ts.SyntaxKind.ClassDeclaration: | |
30 let classDecl = <ts.ClassDeclaration>node; | |
31 if (classDecl.modifiers && (classDecl.modifiers.flags & ts.NodeFlags.Abs
tract)) { | |
32 this.visitClassLike('abstract class', classDecl); | |
33 } else { | |
34 this.visitClassLike('class', classDecl); | |
35 } | |
36 break; | |
37 case ts.SyntaxKind.InterfaceDeclaration: | |
38 let ifDecl = <ts.InterfaceDeclaration>node; | |
39 // Function type interface in an interface with a single declaration | |
40 // of a call signature (http://goo.gl/ROC5jN). | |
41 if (ifDecl.members.length === 1 && ifDecl.members[0].kind === ts.SyntaxK
ind.CallSignature) { | |
42 let member = <ts.CallSignatureDeclaration>ifDecl.members[0]; | |
43 this.visitFunctionTypedefInterface(ifDecl.name.text, member, ifDecl.ty
peParameters); | |
44 } else { | |
45 this.visitClassLike('abstract class', ifDecl); | |
46 } | |
47 break; | |
48 case ts.SyntaxKind.HeritageClause: | |
49 let heritageClause = <ts.HeritageClause>node; | |
50 if (heritageClause.token === ts.SyntaxKind.ExtendsKeyword && | |
51 heritageClause.parent.kind !== ts.SyntaxKind.InterfaceDeclaration) { | |
52 this.emit('extends'); | |
53 } else { | |
54 this.emit('implements'); | |
55 } | |
56 // Can only have one member for extends clauses. | |
57 this.visitList(heritageClause.types); | |
58 break; | |
59 case ts.SyntaxKind.ExpressionWithTypeArguments: | |
60 let exprWithTypeArgs = <ts.ExpressionWithTypeArguments>node; | |
61 this.visit(exprWithTypeArgs.expression); | |
62 this.maybeVisitTypeArguments(exprWithTypeArgs); | |
63 break; | |
64 case ts.SyntaxKind.EnumDeclaration: | |
65 let decl = <ts.EnumDeclaration>node; | 388 let decl = <ts.EnumDeclaration>node; |
66 // The only legal modifier for an enum decl is const. | 389 // The only legal modifier for an enum decl is const. |
67 let isConst = decl.modifiers && (decl.modifiers.flags & ts.NodeFlags.Con
st); | 390 let isConst = decl.modifiers && (decl.modifiers.flags & ts.NodeFlags.Con
st); |
68 if (isConst) { | 391 if (isConst) { |
69 this.reportError(node, 'const enums are not supported'); | 392 this.reportError(node, 'const enums are not supported'); |
70 } | 393 } |
71 this.emit('enum'); | 394 // In JS interop mode we have to treat enums as JavaScript classes |
72 this.fc.visitTypeName(decl.name); | 395 // with static members for each enum constant instead of as first |
| 396 // class enums. |
| 397 this.maybeEmitJsAnnotation(decl); |
| 398 this.emit('class'); |
| 399 this.emit(decl.name.text); |
73 this.emit('{'); | 400 this.emit('{'); |
74 // Enums can be empty in TS ... | 401 let nodes = decl.members; |
75 if (decl.members.length === 0) { | 402 for (let i = 0; i < nodes.length; i++) { |
76 // ... but not in Dart. | 403 this.emit('external static num get'); |
77 this.reportError(node, 'empty enums are not supported'); | 404 this.visit(nodes[i]); |
78 } | 405 this.emit(';'); |
79 this.visitList(decl.members); | 406 } |
80 this.emit('}'); | 407 this.emit('}'); |
81 break; | 408 } break; |
82 case ts.SyntaxKind.EnumMember: | 409 case ts.SyntaxKind.Parameter: { |
| 410 let paramDecl = <ts.ParameterDeclaration>node; |
| 411 if (paramDecl.type && paramDecl.type.kind === ts.SyntaxKind.FunctionType
) { |
| 412 // Dart uses "returnType paramName ( parameters )" syntax. |
| 413 let fnType = <ts.FunctionOrConstructorTypeNode>paramDecl.type; |
| 414 let hasRestParameter = fnType.parameters.some(p => !!p.dotDotDotToken)
; |
| 415 if (!hasRestParameter) { |
| 416 // Dart does not support rest parameters/varargs, degenerate to just
"Function". |
| 417 // TODO(jacobr): also consider faking 0 - NUM_FAKE_REST_PARAMETERS |
| 418 // instead. |
| 419 this.visit(fnType.type); |
| 420 this.visit(paramDecl.name); |
| 421 this.visitParameters(fnType.parameters); |
| 422 break; |
| 423 } |
| 424 } |
| 425 |
| 426 if (paramDecl.dotDotDotToken) { |
| 427 // Weak support of varargs that works ok if you have 5 of fewer args. |
| 428 let paramType: ts.TypeNode; |
| 429 let type = paramDecl.type; |
| 430 if (type) { |
| 431 if (type.kind === ts.SyntaxKind.ArrayType) { |
| 432 let arrayType = <ts.ArrayTypeNode>type; |
| 433 paramType = arrayType.elementType; |
| 434 } else if (type.kind !== ts.SyntaxKind.AnyKeyword) { |
| 435 throw 'Unexpected type for varargs: ' + type.kind; |
| 436 } |
| 437 } |
| 438 |
| 439 for (let i = 1; i <= DeclarationTranspiler.NUM_FAKE_REST_PARAMETERS; +
+i) { |
| 440 if (i > 1) { |
| 441 this.emit(','); |
| 442 } |
| 443 this.visit(paramType); |
| 444 this.emit(base.ident(paramDecl.name) + i); |
| 445 } |
| 446 break; |
| 447 } |
| 448 // TODO(jacobr): should we support |
| 449 if (paramDecl.name.kind === ts.SyntaxKind.ObjectBindingPattern) { |
| 450 this.emit('Object'); |
| 451 let pattern = paramDecl.name as ts.BindingPattern; |
| 452 let elements = pattern.elements; |
| 453 let name = elements.map((e) => base.ident(e.name)).join('_'); |
| 454 // Warning: this name is unlikely to but could possible overlap with |
| 455 // other parameter names. |
| 456 this.emit(name); |
| 457 this.emit('/* ' + pattern.getText() + ' */'); |
| 458 break; |
| 459 } |
| 460 |
| 461 if (paramDecl.name.kind !== ts.SyntaxKind.Identifier) { |
| 462 throw 'Unsupported parameter name kind: ' + paramDecl.name.kind; |
| 463 } |
| 464 this.visit(paramDecl.type); |
| 465 this.visit(paramDecl.name); |
| 466 } break; |
| 467 case ts.SyntaxKind.EnumMember: { |
83 let member = <ts.EnumMember>node; | 468 let member = <ts.EnumMember>node; |
84 this.visit(member.name); | 469 this.visit(member.name); |
85 if (member.initializer) { | 470 } break; |
86 this.reportError(node, 'enum initializers are not supported'); | 471 case ts.SyntaxKind.ModuleBlock: { |
87 } | 472 let block = <ts.ModuleBlock>node; |
88 break; | 473 this.visitMergingOverloads(block.statements); |
| 474 } break; |
| 475 case ts.SyntaxKind.VariableDeclarationList: { |
| 476 // We have to handle variable declaration lists differently in the case |
| 477 // of JS interop because Dart does not support external variables. |
| 478 let varDeclList = <ts.VariableDeclarationList>node; |
| 479 this.visitList(varDeclList.declarations, ';'); |
| 480 } break; |
| 481 case ts.SyntaxKind.VariableDeclaration: { |
| 482 // We have to handle variable declarations differently in the case of JS |
| 483 // interop because Dart does not support external variables. |
| 484 let varDecl = <ts.VariableDeclaration>node; |
| 485 this.maybeEmitJsAnnotation(varDecl); |
| 486 this.emit('external'); |
| 487 this.visit(varDecl.type); |
| 488 this.emit('get'); |
| 489 this.visitName(varDecl.name); |
| 490 if (!this.hasFlag(varDecl.parent, ts.NodeFlags.Const)) { |
| 491 this.emit(';'); |
| 492 this.maybeEmitJsAnnotation(varDecl); |
| 493 this.emit('external'); |
| 494 this.emit('set'); |
| 495 this.visitName(varDecl.name); |
| 496 this.emit('('); |
| 497 this.visit(varDecl.type); |
| 498 this.emit('v)'); |
| 499 } |
| 500 } break; |
| 501 case ts.SyntaxKind.StringLiteral: { |
| 502 this.emit('String'); |
| 503 this.emit('/*'); |
| 504 let sLit = <ts.LiteralExpression>node; |
| 505 let text = JSON.stringify(sLit.text); |
| 506 this.emit(text); |
| 507 this.emit('*/'); |
| 508 } break; |
| 509 case ts.SyntaxKind.CallSignature: { |
| 510 let fn = <ts.SignatureDeclaration>node; |
| 511 this.emit('external'); |
| 512 this.visit(fn.type); |
| 513 this.emit('call'); |
| 514 this.visitParameters(fn.parameters); |
| 515 this.emit(';'); |
| 516 } break; |
| 517 case ts.SyntaxKind.IndexSignature: |
| 518 this.emit('/* Index signature is not yet supported by JavaScript interop
.'); |
| 519 break; |
| 520 case ts.SyntaxKind.ExportAssignment: |
| 521 // let exportAssignment = <ts.ExportAssignment>node; |
| 522 this.emit('/* WARNING: export assignment not yet supported. */\n'); |
| 523 break; |
| 524 case ts.SyntaxKind.TypeAliasDeclaration: |
| 525 // Dart does not provide special syntax for definning type alais |
| 526 // declarations so we do not emit anything here and resolve alaises |
| 527 // to their original types at each usage site. |
| 528 break; |
| 529 case ts.SyntaxKind.ClassDeclaration: |
| 530 case ts.SyntaxKind.InterfaceDeclaration: { |
| 531 this.extendsClass = false; |
| 532 let classDecl = <ts.ClassDeclaration|ts.InterfaceDeclaration>node; |
| 533 let isInterface = node.kind === ts.SyntaxKind.InterfaceDeclaration; |
| 534 if (isInterface && |
| 535 base.isFunctionTypedefLikeInterface(classDecl as ts.InterfaceDeclara
tion)) { |
| 536 let member = <ts.CallSignatureDeclaration>classDecl.members[0]; |
| 537 this.visitFunctionTypedefInterface(classDecl.name.text, member, classD
ecl.typeParameters); |
| 538 break; |
| 539 } |
| 540 |
| 541 let customName = this.fc.lookupCustomDartTypeName(classDecl.name); |
| 542 if (customName && !customName.keep) { |
| 543 this.emit('\n/* Skipping class ' + base.ident(classDecl.name) + '*/\n'
); |
| 544 break; |
| 545 } |
| 546 this.maybeEmitJsAnnotation(node); |
| 547 |
| 548 if (isInterface || |
| 549 (classDecl.modifiers && (classDecl.modifiers.flags & ts.NodeFlags.Ab
stract))) { |
| 550 this.visitClassLike('abstract class', classDecl); |
| 551 } else { |
| 552 this.visitClassLike('class', classDecl); |
| 553 } |
| 554 } break; |
| 555 case ts.SyntaxKind.HeritageClause: { |
| 556 let heritageClause = <ts.HeritageClause>node; |
| 557 if (base.isExtendsClause(<ts.HeritageClause>heritageClause)) { |
| 558 this.extendsClass = true; |
| 559 } |
| 560 |
| 561 if (base.isExtendsClause(heritageClause)) { |
| 562 this.emit('extends'); |
| 563 } else { |
| 564 this.emit('implements'); |
| 565 } |
| 566 // Can only have one member for extends clauses. |
| 567 this.visitList(heritageClause.types); |
| 568 } break; |
| 569 case ts.SyntaxKind.ExpressionWithTypeArguments: { |
| 570 let exprWithTypeArgs = <ts.ExpressionWithTypeArguments>node; |
| 571 this.visit(exprWithTypeArgs.expression); |
| 572 this.maybeVisitTypeArguments(exprWithTypeArgs); |
| 573 } break; |
89 case ts.SyntaxKind.Constructor: | 574 case ts.SyntaxKind.Constructor: |
| 575 case ts.SyntaxKind.ConstructSignature: { |
90 let ctorDecl = <ts.ConstructorDeclaration>node; | 576 let ctorDecl = <ts.ConstructorDeclaration>node; |
91 // Find containing class name. | 577 // Find containing class name. |
92 let className: ts.Identifier; | 578 let classDecl = base.getEnclosingClass(ctorDecl); |
93 for (let parent = ctorDecl.parent; parent; parent = parent.parent) { | 579 if (!classDecl) this.reportError(ctorDecl, 'cannot find outer class node
'); |
94 if (parent.kind === ts.SyntaxKind.ClassDeclaration) { | |
95 className = (<ts.ClassDeclaration>parent).name; | |
96 break; | |
97 } | |
98 } | |
99 if (!className) this.reportError(ctorDecl, 'cannot find outer class node
'); | |
100 this.visitDeclarationMetadata(ctorDecl); | 580 this.visitDeclarationMetadata(ctorDecl); |
101 if (this.isConst(<base.ClassLike>ctorDecl.parent)) { | 581 this.fc.visitTypeName(classDecl.name); |
102 this.emit('const'); | |
103 } | |
104 this.visit(className); | |
105 this.visitParameters(ctorDecl.parameters); | 582 this.visitParameters(ctorDecl.parameters); |
106 this.visit(ctorDecl.body); | 583 this.emit(';'); |
107 break; | 584 } break; |
108 case ts.SyntaxKind.PropertyDeclaration: | 585 case ts.SyntaxKind.PropertyDeclaration: |
109 this.visitProperty(<ts.PropertyDeclaration>node); | 586 this.visitProperty(<ts.PropertyDeclaration>node); |
110 break; | 587 break; |
111 case ts.SyntaxKind.SemicolonClassElement: | 588 case ts.SyntaxKind.SemicolonClassElement: |
112 // No-op, don't emit useless declarations. | 589 // No-op, don't emit useless declarations. |
113 break; | 590 break; |
114 case ts.SyntaxKind.MethodDeclaration: | 591 case ts.SyntaxKind.MethodDeclaration: |
115 this.visitDeclarationMetadata(<ts.MethodDeclaration>node); | 592 this.visitDeclarationMetadata(<ts.MethodDeclaration>node); |
116 this.visitFunctionLike(<ts.MethodDeclaration>node); | 593 this.visitFunctionLike(<ts.MethodDeclaration>node); |
117 break; | 594 break; |
118 case ts.SyntaxKind.GetAccessor: | 595 case ts.SyntaxKind.GetAccessor: |
119 this.visitDeclarationMetadata(<ts.MethodDeclaration>node); | 596 this.visitDeclarationMetadata(<ts.MethodDeclaration>node); |
120 this.visitFunctionLike(<ts.AccessorDeclaration>node, 'get'); | 597 this.visitFunctionLike(<ts.AccessorDeclaration>node, 'get'); |
121 break; | 598 break; |
122 case ts.SyntaxKind.SetAccessor: | 599 case ts.SyntaxKind.SetAccessor: |
123 this.visitDeclarationMetadata(<ts.MethodDeclaration>node); | 600 this.visitDeclarationMetadata(<ts.MethodDeclaration>node); |
124 this.visitFunctionLike(<ts.AccessorDeclaration>node, 'set'); | 601 this.visitFunctionLike(<ts.AccessorDeclaration>node, 'set'); |
125 break; | 602 break; |
126 case ts.SyntaxKind.FunctionDeclaration: | 603 case ts.SyntaxKind.FunctionDeclaration: |
127 let funcDecl = <ts.FunctionDeclaration>node; | 604 let funcDecl = <ts.FunctionDeclaration>node; |
128 this.visitDecorators(funcDecl.decorators); | 605 this.visitDeclarationMetadata(funcDecl); |
129 this.visitFunctionLike(funcDecl); | 606 this.visitFunctionLike(funcDecl); |
130 break; | 607 break; |
131 case ts.SyntaxKind.ArrowFunction: | |
132 let arrowFunc = <ts.FunctionExpression>node; | |
133 // Dart only allows expressions following the fat arrow operator. | |
134 // If the body is a block, we have to drop the fat arrow and emit an | |
135 // anonymous function instead. | |
136 if (arrowFunc.body.kind === ts.SyntaxKind.Block) { | |
137 this.visitFunctionLike(arrowFunc); | |
138 } else { | |
139 this.visitParameters(arrowFunc.parameters); | |
140 this.emit('=>'); | |
141 this.visit(arrowFunc.body); | |
142 } | |
143 break; | |
144 case ts.SyntaxKind.FunctionExpression: | 608 case ts.SyntaxKind.FunctionExpression: |
145 let funcExpr = <ts.FunctionExpression>node; | 609 let funcExpr = <ts.FunctionExpression>node; |
146 this.visitFunctionLike(funcExpr); | 610 this.visitFunctionLike(funcExpr); |
147 break; | 611 break; |
148 case ts.SyntaxKind.PropertySignature: | 612 case ts.SyntaxKind.PropertySignature: |
149 let propSig = <ts.PropertyDeclaration>node; | 613 let propSig = <ts.PropertyDeclaration>node; |
150 this.visitProperty(propSig); | 614 this.visitProperty(propSig); |
151 break; | 615 break; |
152 case ts.SyntaxKind.MethodSignature: | 616 case ts.SyntaxKind.MethodSignature: |
153 let methodSignatureDecl = <ts.FunctionLikeDeclaration>node; | 617 let methodSignatureDecl = <ts.FunctionLikeDeclaration>node; |
154 this.visitEachIfPresent(methodSignatureDecl.modifiers); | 618 this.visitDeclarationMetadata(methodSignatureDecl); |
155 this.visitFunctionLike(methodSignatureDecl); | 619 this.visitFunctionLike(methodSignatureDecl); |
156 break; | 620 break; |
157 case ts.SyntaxKind.Parameter: | |
158 let paramDecl = <ts.ParameterDeclaration>node; | |
159 // Property parameters will have an explicit property declaration, so we
just | |
160 // need the dart assignment shorthand to reference the property. | |
161 if (this.hasFlag(paramDecl.modifiers, ts.NodeFlags.Public) || | |
162 this.hasFlag(paramDecl.modifiers, ts.NodeFlags.Private) || | |
163 this.hasFlag(paramDecl.modifiers, ts.NodeFlags.Protected)) { | |
164 this.visitDeclarationMetadata(paramDecl); | |
165 this.emit('this .'); | |
166 this.visit(paramDecl.name); | |
167 if (paramDecl.initializer) { | |
168 this.emit('='); | |
169 this.visit(paramDecl.initializer); | |
170 } | |
171 break; | |
172 } | |
173 if (paramDecl.dotDotDotToken) this.reportError(node, 'rest parameters ar
e unsupported'); | |
174 if (paramDecl.name.kind === ts.SyntaxKind.ObjectBindingPattern) { | |
175 this.visitNamedParameter(paramDecl); | |
176 break; | |
177 } | |
178 this.visitDecorators(paramDecl.decorators); | |
179 | |
180 if (paramDecl.type && paramDecl.type.kind === ts.SyntaxKind.FunctionType
) { | |
181 // Dart uses "returnType paramName ( parameters )" syntax. | |
182 let fnType = <ts.FunctionOrConstructorTypeNode>paramDecl.type; | |
183 let hasRestParameter = fnType.parameters.some(p => !!p.dotDotDotToken)
; | |
184 if (hasRestParameter) { | |
185 // Dart does not support rest parameters/varargs, degenerate to just
"Function". | |
186 this.emit('Function'); | |
187 this.visit(paramDecl.name); | |
188 } else { | |
189 this.visit(fnType.type); | |
190 this.visit(paramDecl.name); | |
191 this.visitParameters(fnType.parameters); | |
192 } | |
193 } else { | |
194 if (paramDecl.type) this.visit(paramDecl.type); | |
195 this.visit(paramDecl.name); | |
196 } | |
197 if (paramDecl.initializer) { | |
198 this.emit('='); | |
199 this.visit(paramDecl.initializer); | |
200 } | |
201 break; | |
202 case ts.SyntaxKind.StaticKeyword: | 621 case ts.SyntaxKind.StaticKeyword: |
203 this.emit('static'); | 622 // n-op, handled in `visitFunctionLike` and `visitProperty` below. |
204 break; | 623 break; |
205 case ts.SyntaxKind.AbstractKeyword: | 624 case ts.SyntaxKind.AbstractKeyword: |
206 // Abstract methods in Dart simply lack implementation, | 625 // Abstract methods in Dart simply lack implementation, |
207 // and don't use the 'abstract' modifier | 626 // and don't use the 'abstract' modifier |
208 // Abstract classes are handled in `case ts.SyntaxKind.ClassDeclaration`
above. | 627 // Abstract classes are handled in `case ts.SyntaxKind.ClassDeclaration`
above. |
209 break; | 628 break; |
210 case ts.SyntaxKind.PrivateKeyword: | 629 case ts.SyntaxKind.PrivateKeyword: |
211 // no-op, handled through '_' naming convention in Dart. | 630 // no-op, handled through '_' naming convention in Dart. |
212 break; | 631 break; |
213 case ts.SyntaxKind.PublicKeyword: | 632 case ts.SyntaxKind.PublicKeyword: |
214 // Handled in `visitDeclarationMetadata` below. | 633 // Handled in `visitDeclarationMetadata` below. |
215 break; | 634 break; |
216 case ts.SyntaxKind.ProtectedKeyword: | 635 case ts.SyntaxKind.ProtectedKeyword: |
217 // Handled in `visitDeclarationMetadata` below. | 636 // Handled in `visitDeclarationMetadata` below. |
218 break; | 637 break; |
219 | 638 case ts.SyntaxKind.VariableStatement: |
| 639 let variableStmt = <ts.VariableStatement>node; |
| 640 this.visit(variableStmt.declarationList); |
| 641 this.emit(';'); |
| 642 break; |
| 643 case ts.SyntaxKind.SwitchStatement: |
| 644 case ts.SyntaxKind.ArrayLiteralExpression: |
| 645 case ts.SyntaxKind.ExpressionStatement: |
| 646 case ts.SyntaxKind.EmptyStatement: |
| 647 // No need to emit anything for these cases. |
| 648 break; |
220 default: | 649 default: |
221 return false; | 650 return false; |
222 } | 651 } |
223 return true; | 652 return true; |
224 } | 653 } |
225 | 654 |
226 private visitVariableDeclarationType(varDecl: ts.VariableDeclaration) { | |
227 /* Note: VariableDeclarationList can only occur as part of a for loop. This
helper method | |
228 * is meant for processing for-loop variable declaration types only. | |
229 * | |
230 * In Dart, all variables in a variable declaration list must have the same
type. Since | |
231 * we are doing syntax directed translation, we cannot reliably determine if
distinct | |
232 * variables are declared with the same type or not. Hence we support the fo
llowing cases: | |
233 * | |
234 * - A variable declaration list with a single variable can be explicitly ty
ped. | |
235 * - When more than one variable is in the list, all must be implicitly type
d. | |
236 */ | |
237 let firstDecl = varDecl.parent.declarations[0]; | |
238 let msg = 'Variables in a declaration list of more than one variable cannot
by typed'; | |
239 let isFinal = this.hasFlag(varDecl.parent, ts.NodeFlags.Const); | |
240 let isConst = false; | |
241 if (isFinal && varDecl.initializer) { | |
242 // "const" in TypeScript/ES6 corresponds to "final" in Dart, i.e. referenc
e constness. | |
243 // If a "const" variable is immediately initialized to a CONST_EXPR(), spe
cial case it to be | |
244 // a deeply const constant, and generate "const ...". | |
245 isConst = varDecl.initializer.kind === ts.SyntaxKind.StringLiteral || | |
246 varDecl.initializer.kind === ts.SyntaxKind.NumericLiteral || | |
247 this.fc.isConstCall(varDecl.initializer); | |
248 } | |
249 if (firstDecl === varDecl) { | |
250 if (isConst) { | |
251 this.emit('const'); | |
252 } else if (isFinal) { | |
253 this.emit('final'); | |
254 } | |
255 if (!varDecl.type) { | |
256 if (!isFinal) this.emit('var'); | |
257 } else if (varDecl.parent.declarations.length > 1) { | |
258 this.reportError(varDecl, msg); | |
259 } else { | |
260 this.visit(varDecl.type); | |
261 } | |
262 } else if (varDecl.type) { | |
263 this.reportError(varDecl, msg); | |
264 } | |
265 } | |
266 | |
267 private visitFunctionLike(fn: ts.FunctionLikeDeclaration, accessor?: string) { | 655 private visitFunctionLike(fn: ts.FunctionLikeDeclaration, accessor?: string) { |
268 this.fc.pushTypeParameterNames(fn); | 656 this.fc.pushTypeParameterNames(fn); |
| 657 if (base.isStatic(fn)) { |
| 658 this.emit('static'); |
| 659 } |
| 660 |
269 try { | 661 try { |
270 if (fn.type) { | 662 this.visit(fn.type); |
271 if (fn.kind === ts.SyntaxKind.ArrowFunction || | 663 if (accessor) this.emit(accessor); |
272 fn.kind === ts.SyntaxKind.FunctionExpression) { | 664 let name = fn.name; |
273 // The return type is silently dropped for function expressions (inclu
ding arrow | 665 if (name) { |
274 // functions), it is not supported in Dart. | 666 if (name.kind !== ts.SyntaxKind.Identifier) { |
275 this.emit('/*'); | 667 this.reportError(name, 'Unexpected name kind:' + name.kind); |
276 this.visit(fn.type); | |
277 this.emit('*/'); | |
278 } else { | |
279 this.visit(fn.type); | |
280 } | 668 } |
| 669 this.fc.visitTypeName(<ts.Identifier>name); |
281 } | 670 } |
282 if (accessor) this.emit(accessor); | 671 |
283 if (fn.name) this.visit(fn.name); | |
284 if (fn.typeParameters) { | 672 if (fn.typeParameters) { |
285 this.emit('/*<'); | 673 this.emit('/*<'); |
286 // Emit the names literally instead of visiting, otherwise they will be
replaced with the | 674 // Emit the names literally instead of visiting, otherwise they will be
replaced with the |
287 // comment hack themselves. | 675 // comment hack themselves. |
288 this.emit(fn.typeParameters.map(p => base.ident(p.name)).join(', ')); | 676 this.emit(fn.typeParameters.map(p => base.ident(p.name)).join(', ')); |
289 this.emit('>*/'); | 677 this.emit('>*/'); |
290 } | 678 } |
291 // Dart does not even allow the parens of an empty param list on getter | 679 // Dart does not even allow the parens of an empty param list on getter |
292 if (accessor !== 'get') { | 680 if (accessor !== 'get') { |
293 this.visitParameters(fn.parameters); | 681 this.visitParameters(fn.parameters); |
294 } else { | 682 } else { |
295 if (fn.parameters && fn.parameters.length > 0) { | 683 if (fn.parameters && fn.parameters.length > 0) { |
296 this.reportError(fn, 'getter should not accept parameters'); | 684 this.reportError(fn, 'getter should not accept parameters'); |
297 } | 685 } |
298 } | 686 } |
299 if (fn.body) { | 687 this.emit(';'); |
300 this.visit(fn.body); | |
301 } else { | |
302 this.emit(';'); | |
303 } | |
304 } finally { | 688 } finally { |
305 this.fc.popTypeParameterNames(fn); | 689 this.fc.popTypeParameterNames(fn); |
306 } | 690 } |
307 } | 691 } |
308 | 692 |
309 private visitParameters(parameters: ts.ParameterDeclaration[]) { | |
310 this.emit('('); | |
311 let firstInitParamIdx = 0; | |
312 for (; firstInitParamIdx < parameters.length; firstInitParamIdx++) { | |
313 // ObjectBindingPatterns are handled within the parameter visit. | |
314 let isOpt = | |
315 parameters[firstInitParamIdx].initializer || parameters[firstInitParam
Idx].questionToken; | |
316 if (isOpt && parameters[firstInitParamIdx].name.kind !== ts.SyntaxKind.Obj
ectBindingPattern) { | |
317 break; | |
318 } | |
319 } | |
320 | |
321 if (firstInitParamIdx !== 0) { | |
322 let requiredParams = parameters.slice(0, firstInitParamIdx); | |
323 this.visitList(requiredParams); | |
324 } | |
325 | |
326 if (firstInitParamIdx !== parameters.length) { | |
327 if (firstInitParamIdx !== 0) this.emit(','); | |
328 let positionalOptional = parameters.slice(firstInitParamIdx, parameters.le
ngth); | |
329 this.emit('['); | |
330 this.visitList(positionalOptional); | |
331 this.emit(']'); | |
332 } | |
333 | |
334 this.emit(')'); | |
335 } | |
336 | |
337 /** | 693 /** |
338 * Visit a property declaration. | 694 * Visit a property declaration. |
| 695 * In the special case of property parameters in a constructor, we also allow |
| 696 * a parameter to be emitted as a property. |
| 697 * We have to emit properties as getter setter pairs as Dart does not support |
| 698 * external fields. |
339 * In the special case of property parameters in a constructor, we also allow
a parameter to be | 699 * In the special case of property parameters in a constructor, we also allow
a parameter to be |
340 * emitted as a property. | 700 * emitted as a property. |
341 */ | 701 */ |
342 private visitProperty(decl: ts.PropertyDeclaration|ts.ParameterDeclaration, is
Parameter = false) { | 702 private visitProperty(decl: ts.PropertyDeclaration|ts.ParameterDeclaration, is
Parameter = false) { |
343 if (!isParameter) this.visitDeclarationMetadata(decl); | 703 let isStatic = base.isStatic(decl); |
344 let containingClass = <base.ClassLike>(isParameter ? decl.parent.parent : de
cl.parent); | 704 this.emit('external'); |
345 let isConstField = this.hasAnnotation(decl.decorators, 'CONST'); | 705 if (isStatic) this.emit('static'); |
346 let hasConstCtor = this.isConst(containingClass); | 706 this.visit(decl.type); |
347 if (isConstField) { | 707 this.emit('get'); |
348 // const implies final | 708 this.visitName(decl.name); |
349 this.emit('const'); | 709 this.emit(';'); |
350 } else { | 710 |
351 if (hasConstCtor) { | 711 this.emit('external'); |
352 this.emit('final'); | 712 if (isStatic) this.emit('static'); |
353 } | 713 this.emit('set'); |
354 } | 714 this.visitName(decl.name); |
355 if (decl.type) { | 715 this.emit('('); |
356 this.visit(decl.type); | 716 this.visit(decl.type); |
357 } else if (!isConstField && !hasConstCtor) { | 717 this.emit('v'); |
358 this.emit('var'); | 718 this.emit(')'); |
359 } | |
360 this.visit(decl.name); | |
361 if (decl.initializer && !isParameter) { | |
362 this.emit('='); | |
363 this.visit(decl.initializer); | |
364 } | |
365 this.emit(';'); | 719 this.emit(';'); |
366 } | 720 } |
367 | 721 |
368 private visitClassLike(keyword: string, decl: base.ClassLike) { | 722 private visitClassLike(keyword: string, decl: base.ClassLike) { |
369 this.visitDecorators(decl.decorators); | |
370 this.emit(keyword); | 723 this.emit(keyword); |
371 this.fc.visitTypeName(decl.name); | 724 this.fc.visitTypeName(decl.name); |
372 if (decl.typeParameters) { | 725 if (decl.typeParameters) { |
373 this.emit('<'); | 726 this.emit('<'); |
374 this.visitList(decl.typeParameters); | 727 this.visitList(decl.typeParameters); |
375 this.emit('>'); | 728 this.emit('>'); |
376 } | 729 } |
| 730 |
377 this.visitEachIfPresent(decl.heritageClauses); | 731 this.visitEachIfPresent(decl.heritageClauses); |
378 this.emit('{'); | 732 this.emit('{'); |
379 | 733 |
| 734 this.maybeEmitFakeConstructors(decl); |
| 735 |
380 // Synthesize explicit properties for ctor with 'property parameters' | 736 // Synthesize explicit properties for ctor with 'property parameters' |
381 let synthesizePropertyParam = (param: ts.ParameterDeclaration) => { | 737 let synthesizePropertyParam = (param: ts.ParameterDeclaration) => { |
382 if (this.hasFlag(param.modifiers, ts.NodeFlags.Public) || | 738 if (this.hasFlag(param.modifiers, ts.NodeFlags.Public) || |
383 this.hasFlag(param.modifiers, ts.NodeFlags.Private) || | 739 this.hasFlag(param.modifiers, ts.NodeFlags.Private) || |
384 this.hasFlag(param.modifiers, ts.NodeFlags.Protected)) { | 740 this.hasFlag(param.modifiers, ts.NodeFlags.Protected)) { |
385 // TODO: we should enforce the underscore prefix on privates | 741 // TODO: we should enforce the underscore prefix on privates |
386 this.visitProperty(param, true); | 742 this.visitProperty(param, true); |
387 } | 743 } |
388 }; | 744 }; |
389 (<ts.NodeArray<ts.Declaration>>decl.members) | 745 (decl.members as ts.NodeArray<ts.Declaration>) |
390 .filter((m) => m.kind === ts.SyntaxKind.Constructor) | 746 .filter(base.isConstructor) |
391 .forEach( | 747 .forEach( |
392 (ctor) => | 748 (ctor) => |
393 (<ts.ConstructorDeclaration>ctor).parameters.forEach(synthesizeP
ropertyParam)); | 749 (<ts.ConstructorDeclaration>ctor).parameters.forEach(synthesizeP
ropertyParam)); |
394 this.visitEachIfPresent(decl.members); | |
395 | 750 |
396 // Generate a constructor to host the const modifier, if needed | 751 this.visitClassBody(decl); |
397 if (this.isConst(decl) && | |
398 !(<ts.NodeArray<ts.Declaration>>decl.members) | |
399 .some((m) => m.kind === ts.SyntaxKind.Constructor)) { | |
400 this.emit('const'); | |
401 this.fc.visitTypeName(decl.name); | |
402 this.emit('();'); | |
403 } | |
404 this.emit('}'); | 752 this.emit('}'); |
405 } | 753 } |
406 | 754 |
407 private visitDecorators(decorators: ts.NodeArray<ts.Decorator>) { | |
408 if (!decorators) return; | |
409 | |
410 decorators.forEach((d) => { | |
411 // Special case @CONST | |
412 let name = base.ident(d.expression); | |
413 if (!name && d.expression.kind === ts.SyntaxKind.CallExpression) { | |
414 // Unwrap @CONST() | |
415 let callExpr = (<ts.CallExpression>d.expression); | |
416 name = base.ident(callExpr.expression); | |
417 } | |
418 // Make sure these match IGNORED_ANNOTATIONS below. | |
419 if (name === 'CONST') { | |
420 // Ignore @CONST - it is handled above in visitClassLike. | |
421 return; | |
422 } | |
423 this.emit('@'); | |
424 this.visit(d.expression); | |
425 }); | |
426 } | |
427 | |
428 private visitDeclarationMetadata(decl: ts.Declaration) { | 755 private visitDeclarationMetadata(decl: ts.Declaration) { |
429 this.visitDecorators(decl.decorators); | |
430 this.visitEachIfPresent(decl.modifiers); | 756 this.visitEachIfPresent(decl.modifiers); |
431 | 757 |
432 if (this.hasFlag(decl.modifiers, ts.NodeFlags.Protected)) { | 758 switch (decl.kind) { |
433 this.reportError(decl, 'protected declarations are unsupported'); | 759 case ts.SyntaxKind.Constructor: |
434 return; | 760 case ts.SyntaxKind.ConstructSignature: |
435 } | 761 this.emit('external factory'); |
436 if (!this.enforceUnderscoreConventions) return; | 762 break; |
437 // Early return in case this is a decl with no name, such as a constructor | 763 case ts.SyntaxKind.ArrowFunction: |
438 if (!decl.name) return; | 764 case ts.SyntaxKind.CallSignature: |
439 let name = base.ident(decl.name); | 765 case ts.SyntaxKind.MethodDeclaration: |
440 if (!name) return; | 766 case ts.SyntaxKind.SetAccessor: |
441 let isPrivate = this.hasFlag(decl.modifiers, ts.NodeFlags.Private); | 767 case ts.SyntaxKind.GetAccessor: |
442 let matchesPrivate = !!name.match(/^_/); | 768 case ts.SyntaxKind.MethodSignature: |
443 if (isPrivate && !matchesPrivate) { | 769 case ts.SyntaxKind.PropertySignature: |
444 this.reportError(decl, 'private members must be prefixed with "_"'); | 770 case ts.SyntaxKind.FunctionDeclaration: |
445 } | 771 if (!base.getEnclosingClass(decl)) { |
446 if (!isPrivate && matchesPrivate) { | 772 this.maybeEmitJsAnnotation(decl); |
447 this.reportError(decl, 'public members must not be prefixed with "_"'); | 773 } |
| 774 this.emit('external'); |
| 775 break; |
| 776 default: |
| 777 throw 'Unexpected declaration kind:' + decl.kind; |
448 } | 778 } |
449 } | 779 } |
450 | 780 |
451 private visitNamedParameter(paramDecl: ts.ParameterDeclaration) { | |
452 this.visitDecorators(paramDecl.decorators); | |
453 let bp = <ts.BindingPattern>paramDecl.name; | |
454 let propertyTypes = this.fc.resolvePropertyTypes(paramDecl.type); | |
455 let initMap = this.getInitializers(paramDecl); | |
456 this.emit('{'); | |
457 for (let i = 0; i < bp.elements.length; i++) { | |
458 let elem = bp.elements[i]; | |
459 let propDecl = propertyTypes[base.ident(elem.name)]; | |
460 if (propDecl && propDecl.type) this.visit(propDecl.type); | |
461 this.visit(elem.name); | |
462 if (elem.initializer && initMap[base.ident(elem.name)]) { | |
463 this.reportError(elem, 'cannot have both an inner and outer initializer'
); | |
464 } | |
465 let init = elem.initializer || initMap[base.ident(elem.name)]; | |
466 if (init) { | |
467 this.emit(':'); | |
468 this.visit(init); | |
469 } | |
470 if (i + 1 < bp.elements.length) this.emit(','); | |
471 } | |
472 this.emit('}'); | |
473 } | |
474 | |
475 private getInitializers(paramDecl: ts.ParameterDeclaration) { | |
476 let res: ts.Map<ts.Expression> = {}; | |
477 if (!paramDecl.initializer) return res; | |
478 if (paramDecl.initializer.kind !== ts.SyntaxKind.ObjectLiteralExpression) { | |
479 this.reportError(paramDecl, 'initializers for named parameters must be obj
ect literals'); | |
480 return res; | |
481 } | |
482 for (let i of (<ts.ObjectLiteralExpression>paramDecl.initializer).properties
) { | |
483 if (i.kind !== ts.SyntaxKind.PropertyAssignment) { | |
484 this.reportError(i, 'named parameter initializers must be properties, go
t ' + i.kind); | |
485 continue; | |
486 } | |
487 let ole = <ts.PropertyAssignment>i; | |
488 res[base.ident(ole.name)] = ole.initializer; | |
489 } | |
490 return res; | |
491 } | |
492 | |
493 /** | 781 /** |
494 * Handles a function typedef-like interface, i.e. an interface that only decl
ares a single | 782 * Handles a function typedef-like interface, i.e. an interface that only decl
ares a single |
495 * call signature, by translating to a Dart `typedef`. | 783 * call signature, by translating to a Dart `typedef`. |
496 */ | 784 */ |
497 private visitFunctionTypedefInterface( | 785 private visitFunctionTypedefInterface( |
498 name: string, signature: ts.CallSignatureDeclaration, | 786 name: string, signature: ts.CallSignatureDeclaration, |
499 typeParameters: ts.NodeArray<ts.TypeParameterDeclaration>) { | 787 typeParameters: ts.NodeArray<ts.TypeParameterDeclaration>) { |
500 this.emit('typedef'); | 788 this.emit('typedef'); |
501 if (signature.type) { | 789 if (signature.type) { |
502 this.visit(signature.type); | 790 this.visit(signature.type); |
503 } | 791 } |
504 this.emit(name); | 792 this.emit(name); |
505 if (typeParameters) { | 793 if (typeParameters) { |
506 this.emit('<'); | 794 this.emit('<'); |
507 this.visitList(typeParameters); | 795 this.visitList(typeParameters); |
508 this.emit('>'); | 796 this.emit('>'); |
509 } | 797 } |
510 this.visitParameters(signature.parameters); | 798 this.visitParameters(signature.parameters); |
511 this.emit(';'); | 799 this.emit(';'); |
512 } | 800 } |
513 } | 801 } |
OLD | NEW |