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