Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(514)

Side by Side Diff: lib/merge.ts

Issue 2394683003: JS Interop Facade generation polish.
Patch Set: more cleanup Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « lib/main.ts ('k') | lib/module.ts » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 import ts = require('typescript'); 1 import ts = require('typescript');
2 import base = require('./base'); 2 import base = require('./base');
3 import {FacadeConverter} from './facade_converter'; 3 import {FacadeConverter} from './facade_converter';
4 4
5 /** 5 /**
6 * To support arbitrary d.ts files in Dart we often have to merge two TypeScript 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 7 * types into a single Dart type because Dart lacks features such as method
8 * overloads, type aliases, and union types. 8 * overloads, type aliases, and union types.
9 */ 9 */
10 export class MergedType { 10 export class MergedType {
11 constructor(private fc: FacadeConverter) {} 11 constructor(private fc: FacadeConverter) {}
12 12
13 merge(t?: ts.TypeNode) { 13 merge(t?: ts.TypeNode) {
14 if (t) { 14 if (t) {
15 // TODO(jacobr): get a better unique name for a type. 15 // TODO(jacobr): get a better unique name for a type.
16 switch (t.kind) { 16 switch (t.kind) {
17 case ts.SyntaxKind.NullKeyword:
18 // No need to include the null type as all Dart types are nullable any way.
19 return;
17 case ts.SyntaxKind.UnionType: 20 case ts.SyntaxKind.UnionType:
18 let union = <ts.UnionTypeNode>t; 21 let union = <ts.UnionTypeNode>t;
19 union.types.forEach(this.merge.bind(this)); 22 union.types.forEach(this.merge.bind(this));
20 return; 23 return;
21 case ts.SyntaxKind.LastTypeNode: 24 case ts.SyntaxKind.IntersectionType:
22 this.merge((t as ts.ParenthesizedTypeNode).type); 25 // Arbitrarily pick the first type of the intersection type as the mer ged type.
26 // TODO(jacobr): re-evaluate this logic.
27 let intersection = <ts.IntersectionTypeNode>t;
28 this.merge(intersection.types[0]);
23 return; 29 return;
24 case ts.SyntaxKind.TypePredicate: 30 case ts.SyntaxKind.TypePredicate:
25 this.merge((t as ts.TypePredicateNode).type); 31 this.merge((t as ts.TypePredicateNode).type);
26 return; 32 return;
27 case ts.SyntaxKind.TypeReference: 33 case ts.SyntaxKind.TypeReference:
28 // We need to follow Alais types as Dart does not support them for non 34 // We need to follow Alais types as Dart does not support them for non
29 // function types. TODO(jacobr): handle them for Function types? 35 // function types. TODO(jacobr): handle them for Function types?
30 let typeRef = <ts.TypeReferenceNode>t; 36 let typeRef = <ts.TypeReferenceNode>t;
31 let decl = this.fc.getDeclaration(typeRef.typeName); 37 let decl = this.fc.getDeclaration(typeRef.typeName);
32 if (decl !== null && decl.kind === ts.SyntaxKind.TypeAliasDeclaration) { 38 if (decl !== null && decl.kind === ts.SyntaxKind.TypeAliasDeclaration) {
33 let alias = <ts.TypeAliasDeclaration>decl; 39 let alias = <ts.TypeAliasDeclaration>decl;
40 if (!base.supportedAliasType(alias)) {
41 if (typeRef.typeArguments) {
42 console.log(
43 'Warning: typeReference with arguements not supported yet:' + t.getText());
44 }
34 45
35 if (typeRef.typeArguments) { 46 this.merge(alias.type);
36 throw 'TypeReference with arguements not supported yet:' + t.getTe xt();
37 } 47 }
38
39 this.merge(alias.type);
40 return; 48 return;
41 } 49 }
42 break; 50 break;
43 default: 51 default:
44 break; 52 break;
45 } 53 }
46 this.types[this.fc.generateDartTypeName(t, true)] = t; 54 this.types[this.fc.generateDartTypeName(t, {insideComment: true})] = t;
47 } 55 }
48 } 56 }
49 57
50 toTypeNode(): ts.TypeNode { 58 toTypeNode(): ts.TypeNode {
51 let names = Object.getOwnPropertyNames(this.types); 59 let names = Object.getOwnPropertyNames(this.types);
52 if (names.length === 0) { 60 if (names.length === 0) {
53 return null; 61 return null;
54 } 62 }
55 if (names.length === 1) { 63 if (names.length === 1) {
56 return this.types[names[0]]; 64 return this.types[names[0]];
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
102 110
103 setOptional() { this.optional = true; } 111 setOptional() { this.optional = true; }
104 112
105 private name: {[s: string]: boolean} = {}; 113 private name: {[s: string]: boolean} = {};
106 private type: MergedType; 114 private type: MergedType;
107 private optional: boolean = false; 115 private optional: boolean = false;
108 private textRange: ts.TextRange; 116 private textRange: ts.TextRange;
109 } 117 }
110 118
111 /** 119 /**
120 * Handle a parameter that is the result of merging parameter declarations from
121 * multiple method overloads.
122 */
123 export class MergedTypeParameter {
124 constructor(param: ts.TypeParameterDeclaration, fc: FacadeConverter) {
125 this.constraint = new MergedType(fc);
126 this.textRange = param;
127 this.merge(param);
128 this.name = base.ident(param.name);
129 }
130
131 merge(param: ts.TypeParameterDeclaration) {
132 this.constraint.merge(param.constraint);
133 // We ignore param.expression as it is not supported by Dart.
134 }
135
136 toTypeParameterDeclaration(): ts.TypeParameterDeclaration {
137 let ret = <ts.TypeParameterDeclaration>ts.createNode(ts.SyntaxKind.TypeParam eter);
138 let nameIdentifier = <ts.Identifier>ts.createNode(ts.SyntaxKind.Identifier);
139 nameIdentifier.text = this.name;
140 ret.name = nameIdentifier;
141 base.copyLocation(this.textRange, ret);
142 let constraint = this.constraint.toTypeNode();
143 // TODO(jacobr): remove this check once we have support for union types with in comments.
144 // We can't currently handle union types in merged type parameters as the co mments for type
145 // parameters in function types are not there for documentation and impact s trong mode.
146 if (constraint && constraint.kind !== ts.SyntaxKind.UnionType) {
147 ret.constraint = constraint;
148 }
149 return ret;
150 }
151
152 private name: string;
153 private constraint: MergedType;
154 private textRange: ts.TextRange;
155 }
156
157 /**
158 * Handle a parameter that is the result of merging parameter declarations from
159 * multiple method overloads.
160 */
161 export class MergedTypeParameters {
162 private mergedParameters: {[s: string]: MergedTypeParameter} = {};
163 private textRange: ts.TextRange;
164
165 constructor(private fc: FacadeConverter) {}
166
167 merge(params: ts.NodeArray<ts.TypeParameterDeclaration>) {
168 if (!params) return;
169 if (!this.textRange) {
170 this.textRange = params;
171 }
172 for (let i = 0; i < params.length; i++) {
173 let param = params[i];
174 let name = base.ident(param.name);
175 if (Object.hasOwnProperty.call(this.mergedParameters, name)) {
176 let merged = this.mergedParameters[name];
177 if (merged) {
178 merged.merge(param);
179 }
180 } else {
181 this.mergedParameters[name] = new MergedTypeParameter(param, this.fc);
182 }
183 }
184 }
185
186 toTypeParameters(): ts.NodeArray<ts.TypeParameterDeclaration> {
187 let names = Object.getOwnPropertyNames(this.mergedParameters);
188 if (names.length === 0) {
189 return undefined;
190 }
191
192 let ret = [] as ts.NodeArray<ts.TypeParameterDeclaration>;
193 base.copyLocation(this.textRange, ret);
194 for (let i = 0; i < names.length; ++i) {
195 ret.push(this.mergedParameters[names[i]].toTypeParameterDeclaration());
196 }
197 return ret;
198 }
199 }
200
201 /**
112 * Normalize a SourceFile 202 * Normalize a SourceFile
113 */ 203 */
114 export function normalizeSourceFile(f: ts.SourceFile) { 204 export function normalizeSourceFile(f: ts.SourceFile, fc: FacadeConverter) {
115 let modules: {[name: string]: ts.ModuleDeclaration} = {}; 205 let modules: {[name: string]: ts.ModuleDeclaration} = {};
116 206
117 // Merge top level modules. 207 // Merge top level modules.
118 for (let i = 0; i < f.statements.length; ++i) { 208 for (let i = 0; i < f.statements.length; ++i) {
119 let statement = f.statements[i]; 209 let statement = f.statements[i];
120 if (statement.kind !== ts.SyntaxKind.ModuleDeclaration) continue; 210 if (statement.kind !== ts.SyntaxKind.ModuleDeclaration) continue;
121 let moduleDecl = <ts.ModuleDeclaration>statement; 211 let moduleDecl = <ts.ModuleDeclaration>statement;
122 let name = moduleDecl.name.text; 212 let name = moduleDecl.name.text;
123 if (modules.hasOwnProperty(name)) { 213 if (Object.hasOwnProperty.call(modules, name)) {
124 let srcBody = modules[name].body; 214 let srcBody = modules[name].body;
125 let srcBodyBlock: ts.ModuleBlock; 215 let srcBodyBlock: ts.ModuleBlock;
126 216
127 if (srcBody.kind !== ts.SyntaxKind.ModuleBlock) { 217 if (srcBody.kind !== ts.SyntaxKind.ModuleBlock) {
128 throw 'Module body must be a module block.'; 218 throw 'Module body must be a module block.';
129 } 219 }
130 srcBodyBlock = <ts.ModuleBlock>srcBody; 220 srcBodyBlock = <ts.ModuleBlock>srcBody;
131 221
132 let body = moduleDecl.body; 222 let body = moduleDecl.body;
133 if (body.kind === ts.SyntaxKind.ModuleBlock) { 223 if (body.kind === ts.SyntaxKind.ModuleBlock) {
(...skipping 21 matching lines...) Expand all
155 } 245 }
156 246
157 function mergeVariablesIntoClasses(n: ts.Node, classes: {[name: string]: base. ClassLike}) { 247 function mergeVariablesIntoClasses(n: ts.Node, classes: {[name: string]: base. ClassLike}) {
158 switch (n.kind) { 248 switch (n.kind) {
159 case ts.SyntaxKind.VariableStatement: 249 case ts.SyntaxKind.VariableStatement:
160 let statement = <ts.VariableStatement>n; 250 let statement = <ts.VariableStatement>n;
161 statement.declarationList.declarations.forEach(function( 251 statement.declarationList.declarations.forEach(function(
162 declaration: ts.VariableDeclaration) { 252 declaration: ts.VariableDeclaration) {
163 if (declaration.name.kind === ts.SyntaxKind.Identifier) { 253 if (declaration.name.kind === ts.SyntaxKind.Identifier) {
164 let name: string = (<ts.Identifier>(declaration.name)).text; 254 let name: string = (<ts.Identifier>(declaration.name)).text;
165 if (classes.hasOwnProperty(name)) { 255 let existingClass = Object.hasOwnProperty.call(classes, name);
256 let hasConstructor = false;
257 if (declaration.type) {
258 let type: ts.TypeNode = declaration.type;
259 if (type.kind === ts.SyntaxKind.TypeLiteral) {
260 let literal = <ts.TypeLiteralNode>type;
261 hasConstructor = literal.members.some((member: ts.Node) => {
262 return member.kind === ts.SyntaxKind.ConstructSignature;
263 });
264 } else if (type.kind === ts.SyntaxKind.TypeReference) {
265 // Handle interfaces with constructors. As Dart does not support calling arbitrary
266 // functions like constructors we need to upgrade the interface to be a class
267 // so we call invoke the constructor on the interface class.
268 // Example typescript library definition matching this pattern:
269 //
270 // interface XStatic {
271 // new (a: string, b): XStatic;
272 // foo();
273 // }
274 //
275 // declare var X: XStatic;
276 //
277 // In JavaScript you could just write new X() and create an
278 // instance of XStatic. We don't
279 let typeRef = type as ts.TypeReferenceNode;
280 let typeName = typeRef.typeName;
281 let symbol = fc.tc.getSymbolAtLocation(typeName);
282 if (symbol == null) return;
283 let decl = fc.getSymbolDeclaration(symbol, typeName);
284 if (decl == null) return;
285 if (decl.kind !== ts.SyntaxKind.InterfaceDeclaration) return;
286 let interfaceDecl = decl as base.ExtendedInterfaceDeclaration;
287 if (!interfaceDecl.members.some(
288 (member) => { return member.kind === ts.SyntaxKind.Const ructSignature; }))
289 return;
290
291 if (interfaceDecl.classLikeVariableDeclaration == null) {
292 // We could add extra logic to be safer such as only infering that variable names
293 // are class like for cases where variable names are UpperCame lCase matching JS
294 // conventions that a variable is a Class definition.
295 interfaceDecl.classLikeVariableDeclaration = declaration;
296 }
297 }
298 }
299
300 if (existingClass || hasConstructor) {
301 if (!existingClass) {
302 // Create a stub existing class to upgrade the object literal to if there is not an
303 // existing class with the same name.
304 let clazz = <ts.ClassDeclaration>ts.createNode(ts.SyntaxKind.Cla ssDeclaration);
305 base.copyLocation(declaration, clazz);
306 clazz.name = declaration.name as ts.Identifier;
307 clazz.members = <ts.NodeArray<ts.ClassElement>>[];
308 base.copyLocation(declaration, clazz.members);
309 replaceNode(n, clazz);
310 classes[name] = clazz;
311 }
312
166 let existing = classes[name]; 313 let existing = classes[name];
314 if (existing.kind === ts.SyntaxKind.InterfaceDeclaration) {
315 let interfaceDecl = existing as base.ExtendedInterfaceDeclaratio n;
316 // It is completely safe to assume that we know the precise clas s like variable
317 // declaration for the interface in this case as they have the s ame exact name.
318 interfaceDecl.classLikeVariableDeclaration = declaration;
319 }
167 let members = existing.members as Array<ts.ClassElement>; 320 let members = existing.members as Array<ts.ClassElement>;
168 if (declaration.type) { 321 if (declaration.type) {
169 let type: ts.TypeNode = declaration.type; 322 let type: ts.TypeNode = declaration.type;
170 if (type.kind === ts.SyntaxKind.TypeLiteral) { 323 if (type.kind === ts.SyntaxKind.TypeLiteral) {
171 removeNode(n); 324 if (existingClass) {
325 removeNode(n);
326 }
172 let literal = <ts.TypeLiteralNode>type; 327 let literal = <ts.TypeLiteralNode>type;
173 literal.members.forEach((member: ts.Node) => { 328 literal.members.forEach((member: ts.Node) => {
174 switch (member.kind) { 329 switch (member.kind) {
175 case ts.SyntaxKind.ConstructSignature: 330 case ts.SyntaxKind.ConstructSignature:
176 let signature: any = member; 331 let signature: any = member;
177 let constructor = 332 let constructor =
178 <ts.ConstructorDeclaration>ts.createNode(ts.SyntaxKi nd.Constructor); 333 <ts.ConstructorDeclaration>ts.createNode(ts.SyntaxKi nd.Constructor);
179 constructor.parameters = signature.parameters; 334 constructor.parameters = signature.parameters;
180 constructor.type = signature.type; 335 constructor.type = signature.type;
181 base.copyLocation(signature, constructor); 336 base.copyLocation(signature, constructor);
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
242 break; 397 break;
243 case ts.SyntaxKind.SourceFile: 398 case ts.SyntaxKind.SourceFile:
244 let sourceFile = <ts.SourceFile>parent; 399 let sourceFile = <ts.SourceFile>parent;
245 removeFromArray(sourceFile.statements, n); 400 removeFromArray(sourceFile.statements, n);
246 break; 401 break;
247 default: 402 default:
248 throw 'removeNode not implemented for kind:' + parent.kind; 403 throw 'removeNode not implemented for kind:' + parent.kind;
249 } 404 }
250 } 405 }
251 406
252 function makeCallableClassesImplementFunction(decl: base.ClassLike) { 407 function replaceInArray(nodes: ts.NodeArray<ts.Node>, v: ts.Node, replacement: ts.Node) {
253 if (base.isCallable(decl)) { 408 for (let i = 0, len = nodes.length; i < len; ++i) {
254 // Modify the AST to explicitly state that the class implements Function 409 if (nodes[i] === v) {
255 if (!decl.heritageClauses) { 410 nodes[i] = replacement;
256 decl.heritageClauses = <ts.NodeArray<ts.HeritageClause>>[]; 411 break;
257 base.copyLocation(decl, decl.heritageClauses);
258 } 412 }
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 } 413 }
284 } 414 }
285 415
416 function replaceNode(n: ts.Node, replacement: ts.Node) {
417 let parent = n.parent;
418 replacement.parent = parent;
419 switch (parent.kind) {
420 case ts.SyntaxKind.ModuleBlock:
421 let block = <ts.ModuleBlock>parent;
422 replaceInArray(block.statements, n, replacement);
423 break;
424 case ts.SyntaxKind.SourceFile:
425 let sourceFile = <ts.SourceFile>parent;
426 replaceInArray(sourceFile.statements, n, replacement);
427 break;
428 default:
429 throw 'replaceNode not implemented for kind:' + parent.kind;
430 }
431 }
432
286 function gatherClasses(n: ts.Node, classes: {[name: string]: base.ClassLike}) { 433 function gatherClasses(n: ts.Node, classes: {[name: string]: base.ClassLike}) {
287 switch (n.kind) { 434 switch (n.kind) {
288 case ts.SyntaxKind.ClassDeclaration: 435 case ts.SyntaxKind.ClassDeclaration:
289 case ts.SyntaxKind.InterfaceDeclaration: 436 case ts.SyntaxKind.InterfaceDeclaration:
290 let classDecl = <base.ClassLike>n; 437 let classDecl = <base.ClassLike>n;
291 let name = classDecl.name.text; 438 let name = classDecl.name.text;
292 // TODO(jacobr): validate that the classes have consistent 439 // TODO(jacobr): validate that the classes have consistent
293 // modifiers, etc. 440 // modifiers, etc.
294 if (classes.hasOwnProperty(name)) { 441 if (Object.hasOwnProperty.call(classes, name)) {
295 let existing = classes[name]; 442 let existing = classes[name];
296 (classDecl.members as Array<ts.ClassElement>).forEach((e: ts.ClassElem ent) => { 443 (classDecl.members as Array<ts.ClassElement>).forEach((e: ts.ClassElem ent) => {
297 (existing.members as Array<ts.ClassElement>).push(e); 444 (existing.members as Array<ts.ClassElement>).push(e);
298 e.parent = existing; 445 e.parent = existing;
299 }); 446 });
300 removeNode(classDecl); 447 removeNode(classDecl);
301 } else { 448 } else {
302 classes[name] = classDecl; 449 classes[name] = classDecl;
303 // Perform other class level post processing here. 450 // Perform other class level post processing here.
304 makeCallableClassesImplementFunction(classDecl);
305 } 451 }
306 break; 452 break;
307 case ts.SyntaxKind.ModuleDeclaration: 453 case ts.SyntaxKind.ModuleDeclaration:
308 case ts.SyntaxKind.SourceFile: 454 case ts.SyntaxKind.SourceFile:
309 let moduleClasses: {[name: string]: base.ClassLike} = {}; 455 let moduleClasses: {[name: string]: base.ClassLike} = {};
310 ts.forEachChild(n, (child) => gatherClasses(child, moduleClasses)); 456 ts.forEachChild(n, (child) => gatherClasses(child, moduleClasses));
311 ts.forEachChild(n, (child) => mergeVariablesIntoClasses(child, moduleCla sses)); 457 ts.forEachChild(n, (child) => mergeVariablesIntoClasses(child, moduleCla sses));
312 458
313 break; 459 break;
314 case ts.SyntaxKind.ModuleBlock: 460 case ts.SyntaxKind.ModuleBlock:
315 ts.forEachChild(n, (child) => gatherClasses(child, classes)); 461 ts.forEachChild(n, (child) => gatherClasses(child, classes));
316 break; 462 break;
317 default: 463 default:
318 break; 464 break;
319 } 465 }
320 } 466 }
321 gatherClasses(f, {}); 467 gatherClasses(f, {});
322 } 468 }
OLDNEW
« no previous file with comments | « lib/main.ts ('k') | lib/module.ts » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698