| 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 |