OLD | NEW |
(Empty) | |
| 1 import ts = require('typescript'); |
| 2 import base = require('./base'); |
| 3 import {FacadeConverter} from './facade_converter'; |
| 4 |
| 5 /** |
| 6 * To support arbitrary d.ts files in Dart we often have to merge two TypeScript |
| 7 * types into a single Dart type because Dart lacks features such as method |
| 8 * overloads, type aliases, and union types. |
| 9 */ |
| 10 export class MergedType { |
| 11 constructor(private fc: FacadeConverter) {} |
| 12 |
| 13 merge(t?: ts.TypeNode) { |
| 14 if (t) { |
| 15 // TODO(jacobr): get a better unique name for a type. |
| 16 switch (t.kind) { |
| 17 case ts.SyntaxKind.UnionType: |
| 18 let union = <ts.UnionTypeNode>t; |
| 19 union.types.forEach(this.merge.bind(this)); |
| 20 return; |
| 21 case ts.SyntaxKind.LastTypeNode: |
| 22 this.merge((t as ts.ParenthesizedTypeNode).type); |
| 23 return; |
| 24 case ts.SyntaxKind.TypePredicate: |
| 25 this.merge((t as ts.TypePredicateNode).type); |
| 26 return; |
| 27 case ts.SyntaxKind.TypeReference: |
| 28 // We need to follow Alais types as Dart does not support them for non |
| 29 // function types. TODO(jacobr): handle them for Function types? |
| 30 let typeRef = <ts.TypeReferenceNode>t; |
| 31 let decl = this.fc.getDeclaration(typeRef.typeName); |
| 32 if (decl !== null && decl.kind === ts.SyntaxKind.TypeAliasDeclaration)
{ |
| 33 let alias = <ts.TypeAliasDeclaration>decl; |
| 34 |
| 35 if (typeRef.typeArguments) { |
| 36 throw 'TypeReference with arguements not supported yet:' + t.getTe
xt(); |
| 37 } |
| 38 |
| 39 this.merge(alias.type); |
| 40 return; |
| 41 } |
| 42 break; |
| 43 default: |
| 44 break; |
| 45 } |
| 46 this.types[this.fc.generateDartTypeName(t, true)] = t; |
| 47 } |
| 48 } |
| 49 |
| 50 toTypeNode(): ts.TypeNode { |
| 51 let names = Object.getOwnPropertyNames(this.types); |
| 52 if (names.length === 0) { |
| 53 return null; |
| 54 } |
| 55 if (names.length === 1) { |
| 56 return this.types[names[0]]; |
| 57 } |
| 58 let union = <ts.UnionTypeNode>ts.createNode(ts.SyntaxKind.UnionType); |
| 59 base.copyLocation(this.types[names[0]], union); |
| 60 |
| 61 union.types = <ts.NodeArray<ts.TypeNode>>[]; |
| 62 for (let i = 0; i < names.length; ++i) { |
| 63 union.types.push(this.types[names[i]]); |
| 64 } |
| 65 return union; |
| 66 } |
| 67 |
| 68 private types: {[name: string]: ts.TypeNode} = {}; |
| 69 } |
| 70 |
| 71 /** |
| 72 * Handle a parameter that is the result of merging parameter declarations from |
| 73 * multiple method overloads. |
| 74 */ |
| 75 export class MergedParameter { |
| 76 constructor(param: ts.ParameterDeclaration, fc: FacadeConverter) { |
| 77 this.type = new MergedType(fc); |
| 78 this.textRange = param; |
| 79 this.merge(param); |
| 80 } |
| 81 |
| 82 merge(param: ts.ParameterDeclaration) { |
| 83 this.name[base.ident(param.name)] = true; |
| 84 if (!this.optional) { |
| 85 this.optional = !!param.questionToken; |
| 86 } |
| 87 this.type.merge(param.type); |
| 88 } |
| 89 |
| 90 toParameterDeclaration(): ts.ParameterDeclaration { |
| 91 let ret = <ts.ParameterDeclaration>ts.createNode(ts.SyntaxKind.Parameter); |
| 92 let nameIdentifier = <ts.Identifier>ts.createNode(ts.SyntaxKind.Identifier); |
| 93 nameIdentifier.text = Object.getOwnPropertyNames(this.name).join('_'); |
| 94 ret.name = nameIdentifier; |
| 95 if (this.optional) { |
| 96 ret.questionToken = ts.createNode(ts.SyntaxKind.QuestionToken); |
| 97 } |
| 98 base.copyLocation(this.textRange, ret); |
| 99 ret.type = this.type.toTypeNode(); |
| 100 return ret; |
| 101 } |
| 102 |
| 103 setOptional() { this.optional = true; } |
| 104 |
| 105 private name: {[s: string]: boolean} = {}; |
| 106 private type: MergedType; |
| 107 private optional: boolean = false; |
| 108 private textRange: ts.TextRange; |
| 109 } |
| 110 |
| 111 /** |
| 112 * Normalize a SourceFile |
| 113 */ |
| 114 export function normalizeSourceFile(f: ts.SourceFile) { |
| 115 let modules: {[name: string]: ts.ModuleDeclaration} = {}; |
| 116 |
| 117 // Merge top level modules. |
| 118 for (let i = 0; i < f.statements.length; ++i) { |
| 119 let statement = f.statements[i]; |
| 120 if (statement.kind !== ts.SyntaxKind.ModuleDeclaration) continue; |
| 121 let moduleDecl = <ts.ModuleDeclaration>statement; |
| 122 let name = moduleDecl.name.text; |
| 123 if (modules.hasOwnProperty(name)) { |
| 124 let srcBody = modules[name].body; |
| 125 let srcBodyBlock: ts.ModuleBlock; |
| 126 |
| 127 if (srcBody.kind !== ts.SyntaxKind.ModuleBlock) { |
| 128 throw 'Module body must be a module block.'; |
| 129 } |
| 130 srcBodyBlock = <ts.ModuleBlock>srcBody; |
| 131 |
| 132 let body = moduleDecl.body; |
| 133 if (body.kind === ts.SyntaxKind.ModuleBlock) { |
| 134 let bodyBlock = <ts.ModuleBlock>body; |
| 135 Array.prototype.push.apply(srcBodyBlock.statements, bodyBlock.statements
); |
| 136 } else { |
| 137 // moduleDecl.body is a ModuleDeclaration. |
| 138 srcBodyBlock.statements.push(moduleDecl.body); |
| 139 } |
| 140 |
| 141 f.statements.splice(i, 1); |
| 142 i--; |
| 143 } else { |
| 144 modules[name] = moduleDecl; |
| 145 } |
| 146 } |
| 147 |
| 148 function addModifier(n: ts.Node, modifier: ts.Node) { |
| 149 if (!n.modifiers) { |
| 150 n.modifiers = <ts.ModifiersArray>[]; |
| 151 n.modifiers.flags = 0; |
| 152 } |
| 153 modifier.parent = n; |
| 154 n.modifiers.push(modifier); |
| 155 } |
| 156 |
| 157 function mergeVariablesIntoClasses(n: ts.Node, classes: {[name: string]: base.
ClassLike}) { |
| 158 switch (n.kind) { |
| 159 case ts.SyntaxKind.VariableStatement: |
| 160 let statement = <ts.VariableStatement>n; |
| 161 statement.declarationList.declarations.forEach(function( |
| 162 declaration: ts.VariableDeclaration) { |
| 163 if (declaration.name.kind === ts.SyntaxKind.Identifier) { |
| 164 let name: string = (<ts.Identifier>(declaration.name)).text; |
| 165 if (classes.hasOwnProperty(name)) { |
| 166 let existing = classes[name]; |
| 167 let members = existing.members as Array<ts.ClassElement>; |
| 168 if (declaration.type) { |
| 169 let type: ts.TypeNode = declaration.type; |
| 170 if (type.kind === ts.SyntaxKind.TypeLiteral) { |
| 171 removeNode(n); |
| 172 let literal = <ts.TypeLiteralNode>type; |
| 173 literal.members.forEach((member: ts.Node) => { |
| 174 switch (member.kind) { |
| 175 case ts.SyntaxKind.ConstructSignature: |
| 176 let signature: any = member; |
| 177 let constructor = |
| 178 <ts.ConstructorDeclaration>ts.createNode(ts.SyntaxKi
nd.Constructor); |
| 179 constructor.parameters = signature.parameters; |
| 180 constructor.type = signature.type; |
| 181 base.copyLocation(signature, constructor); |
| 182 constructor.typeParameters = signature.typeParameters; |
| 183 constructor.parent = existing; |
| 184 members.push(<ts.ClassElement>constructor); |
| 185 break; |
| 186 case ts.SyntaxKind.Constructor: |
| 187 member.parent = existing.parent; |
| 188 members.push(<ts.ClassElement>member); |
| 189 break; |
| 190 case ts.SyntaxKind.MethodSignature: |
| 191 member.parent = existing.parent; |
| 192 members.push(<ts.ClassElement>member); |
| 193 break; |
| 194 case ts.SyntaxKind.PropertySignature: |
| 195 addModifier(member, ts.createNode(ts.SyntaxKind.StaticKe
yword)); |
| 196 member.parent = existing; |
| 197 members.push(<ts.ClassElement>member); |
| 198 break; |
| 199 case ts.SyntaxKind.IndexSignature: |
| 200 member.parent = existing.parent; |
| 201 members.push(<ts.ClassElement>member); |
| 202 break; |
| 203 case ts.SyntaxKind.CallSignature: |
| 204 member.parent = existing.parent; |
| 205 members.push(<ts.ClassElement>member); |
| 206 break; |
| 207 default: |
| 208 throw 'Unhandled TypeLiteral member type:' + member.kind
; |
| 209 } |
| 210 }); |
| 211 } |
| 212 } |
| 213 } |
| 214 } else { |
| 215 throw 'Unexpected VariableStatement identifier kind'; |
| 216 } |
| 217 }); |
| 218 break; |
| 219 case ts.SyntaxKind.ModuleBlock: |
| 220 ts.forEachChild(n, (child) => mergeVariablesIntoClasses(child, classes))
; |
| 221 break; |
| 222 default: |
| 223 break; |
| 224 } |
| 225 } |
| 226 |
| 227 function removeFromArray(nodes: ts.NodeArray<ts.Node>, v: ts.Node) { |
| 228 for (let i = 0, len = nodes.length; i < len; ++i) { |
| 229 if (nodes[i] === v) { |
| 230 nodes.splice(i, 1); |
| 231 break; |
| 232 } |
| 233 } |
| 234 } |
| 235 |
| 236 function removeNode(n: ts.Node) { |
| 237 let parent = n.parent; |
| 238 switch (parent.kind) { |
| 239 case ts.SyntaxKind.ModuleBlock: |
| 240 let block = <ts.ModuleBlock>parent; |
| 241 removeFromArray(block.statements, n); |
| 242 break; |
| 243 case ts.SyntaxKind.SourceFile: |
| 244 let sourceFile = <ts.SourceFile>parent; |
| 245 removeFromArray(sourceFile.statements, n); |
| 246 break; |
| 247 default: |
| 248 throw 'removeNode not implemented for kind:' + parent.kind; |
| 249 } |
| 250 } |
| 251 |
| 252 function makeCallableClassesImplementFunction(decl: base.ClassLike) { |
| 253 if (base.isCallable(decl)) { |
| 254 // Modify the AST to explicitly state that the class implements Function |
| 255 if (!decl.heritageClauses) { |
| 256 decl.heritageClauses = <ts.NodeArray<ts.HeritageClause>>[]; |
| 257 base.copyLocation(decl, decl.heritageClauses); |
| 258 } |
| 259 let clauses = decl.heritageClauses; |
| 260 let clause = base.arrayFindPolyfill( |
| 261 clauses, (c) => c.token !== ts.SyntaxKind.ExtendsKeyword || |
| 262 decl.kind === ts.SyntaxKind.InterfaceDeclaration); |
| 263 if (clause == null) { |
| 264 clause = <ts.HeritageClause>ts.createNode(ts.SyntaxKind.HeritageClause); |
| 265 clause.token = decl.kind === ts.SyntaxKind.InterfaceDeclaration ? |
| 266 ts.SyntaxKind.ExtendsKeyword : |
| 267 ts.SyntaxKind.ImplementsKeyword; |
| 268 clause.types = <ts.NodeArray<ts.ExpressionWithTypeArguments>>[]; |
| 269 clause.parent = decl; |
| 270 base.copyLocation(decl, clause); |
| 271 clauses.push(clause); |
| 272 } |
| 273 let functionType = |
| 274 <ts.ExpressionWithTypeArguments>ts.createNode(ts.SyntaxKind.Expression
WithTypeArguments); |
| 275 functionType.parent = clause; |
| 276 base.copyLocation(clause, functionType); |
| 277 let fn = <ts.Identifier>ts.createNode(ts.SyntaxKind.Identifier); |
| 278 fn.text = 'Function'; |
| 279 fn.parent = functionType; |
| 280 base.copyLocation(functionType, fn); |
| 281 functionType.expression = fn; |
| 282 clause.types.push(functionType); |
| 283 } |
| 284 } |
| 285 |
| 286 function gatherClasses(n: ts.Node, classes: {[name: string]: base.ClassLike})
{ |
| 287 switch (n.kind) { |
| 288 case ts.SyntaxKind.ClassDeclaration: |
| 289 case ts.SyntaxKind.InterfaceDeclaration: |
| 290 let classDecl = <base.ClassLike>n; |
| 291 let name = classDecl.name.text; |
| 292 // TODO(jacobr): validate that the classes have consistent |
| 293 // modifiers, etc. |
| 294 if (classes.hasOwnProperty(name)) { |
| 295 let existing = classes[name]; |
| 296 (classDecl.members as Array<ts.ClassElement>).forEach((e: ts.ClassElem
ent) => { |
| 297 (existing.members as Array<ts.ClassElement>).push(e); |
| 298 e.parent = existing; |
| 299 }); |
| 300 removeNode(classDecl); |
| 301 } else { |
| 302 classes[name] = classDecl; |
| 303 // Perform other class level post processing here. |
| 304 makeCallableClassesImplementFunction(classDecl); |
| 305 } |
| 306 break; |
| 307 case ts.SyntaxKind.ModuleDeclaration: |
| 308 case ts.SyntaxKind.SourceFile: |
| 309 let moduleClasses: {[name: string]: base.ClassLike} = {}; |
| 310 ts.forEachChild(n, (child) => gatherClasses(child, moduleClasses)); |
| 311 ts.forEachChild(n, (child) => mergeVariablesIntoClasses(child, moduleCla
sses)); |
| 312 |
| 313 break; |
| 314 case ts.SyntaxKind.ModuleBlock: |
| 315 ts.forEachChild(n, (child) => gatherClasses(child, classes)); |
| 316 break; |
| 317 default: |
| 318 break; |
| 319 } |
| 320 } |
| 321 gatherClasses(f, {}); |
| 322 } |
OLD | NEW |