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

Side by Side Diff: lib/base.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 | « README.md ('k') | lib/declaration.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 * as dartStyle from 'dart-style'; 1 import * as dartStyle from 'dart-style';
2 import * as path from 'path';
2 import * as ts from 'typescript'; 3 import * as ts from 'typescript';
3 4
4 import {OutputContext, Transpiler} from './main'; 5 import {OutputContext, Transpiler} from './main';
5 6
7 export type ResolvedTypeMap = {
8 [name: string]: ts.TypeNode
9 };
10
11 /***
12 * Options for how we display a TypeScript type as a Dart type.
13 */
14 export interface TypeDisplayOptions {
15 /**
16 * We are displaying the type inside a comment so we don't have to restrict to valid Dart syntax.
17 * For example, we can display string literal type using the regular TypeScrip t syntax.
18 */
19 insideComment?: boolean;
20 /**
21 * Dart has additional restrictions for what types are valid to emit inside a type argument. For
22 * example, "void" is not valid inside a type argument so Null has to be used instead.
23 */
24 insideTypeArgument?: boolean;
25 /**
26 * Indicates that we should not append an additional comment indicating what t he true TypeScript
27 * type was for cases where Dart cannot express the type precisely.
28 */
29 hideComment?: boolean;
30
31 /**
32 * Parameter declarations to substitute
33 */
34 typeArguments?: ts.TypeNode[];
35
36 resolvedTypeArguments?: ResolvedTypeMap;
37 }
38
6 export type ClassLike = ts.ClassDeclaration | ts.InterfaceDeclaration; 39 export type ClassLike = ts.ClassDeclaration | ts.InterfaceDeclaration;
7 export type NamedDeclaration = ClassLike | ts.PropertyDeclaration | ts.VariableD eclaration | 40 export type NamedDeclaration = ClassLike | ts.PropertyDeclaration | ts.VariableD eclaration |
8 ts.MethodDeclaration | ts.ModuleDeclaration | ts.FunctionDeclaration; 41 ts.MethodDeclaration | ts.ModuleDeclaration | ts.FunctionDeclaration;
9 42
43 export interface ExtendedInterfaceDeclaration extends ts.InterfaceDeclaration {
44 /**
45 * VariableDeclaration associated with this interface that we want to treat as the concrete
46 * location of this interface to enable interfaces that act like constructors.
47 * Because Dart does not permit calling objects like constructors we have to a dd this workaround.
48 */
49 classLikeVariableDeclaration?: ts.VariableDeclaration;
50 }
51
10 export type Set = { 52 export type Set = {
11 [s: string]: boolean 53 [s: string]: boolean
12 }; 54 };
13 55
14 export function ident(n: ts.Node): string { 56 export function ident(n: ts.Node): string {
15 if (n.kind === ts.SyntaxKind.Identifier) return (<ts.Identifier>n).text; 57 if (n.kind === ts.SyntaxKind.Identifier) return (<ts.Identifier>n).text;
58 if (n.kind === ts.SyntaxKind.FirstLiteralToken) {
59 return (n as ts.LiteralLikeNode).text;
60 }
16 if (n.kind === ts.SyntaxKind.QualifiedName) { 61 if (n.kind === ts.SyntaxKind.QualifiedName) {
17 let qname = (<ts.QualifiedName>n); 62 let qname = (<ts.QualifiedName>n);
18 let leftName = ident(qname.left); 63 let leftName = ident(qname.left);
19 if (leftName) return leftName + '.' + ident(qname.right); 64 if (leftName) return leftName + '.' + ident(qname.right);
20 } 65 }
21 return null; 66 return null;
22 } 67 }
23 68
24 export function isFunctionTypedefLikeInterface(ifDecl: ts.InterfaceDeclaration): boolean { 69 export function isFunctionTypedefLikeInterface(ifDecl: ts.InterfaceDeclaration): boolean {
25 return ifDecl.members && ifDecl.members.length === 1 && 70 return ifDecl.members && ifDecl.members.length === 1 &&
(...skipping 27 matching lines...) Expand all
53 98
54 export function isCallableType(type: ts.TypeNode, tc: ts.TypeChecker): boolean { 99 export function isCallableType(type: ts.TypeNode, tc: ts.TypeChecker): boolean {
55 if (isFunctionType(type, tc)) return true; 100 if (isFunctionType(type, tc)) return true;
56 if (type.kind === ts.SyntaxKind.TypeReference) { 101 if (type.kind === ts.SyntaxKind.TypeReference) {
57 if (tc.getSignaturesOfType(tc.getTypeAtLocation(type), ts.SignatureKind.Call ).length > 0) 102 if (tc.getSignaturesOfType(tc.getTypeAtLocation(type), ts.SignatureKind.Call ).length > 0)
58 return true; 103 return true;
59 } 104 }
60 return false; 105 return false;
61 } 106 }
62 107
108 export function supportedAliasType(alias: ts.TypeAliasDeclaration): boolean {
109 let kind = alias.type.kind;
110 return kind === ts.SyntaxKind.TypeLiteral || kind === ts.SyntaxKind.FunctionTy pe;
111 }
112
63 export function isFunctionType(type: ts.TypeNode, tc: ts.TypeChecker): boolean { 113 export function isFunctionType(type: ts.TypeNode, tc: ts.TypeChecker): boolean {
64 let kind = type.kind; 114 let kind = type.kind;
65 if (kind === ts.SyntaxKind.FunctionType) return true; 115 if (kind === ts.SyntaxKind.FunctionType) return true;
66 if (kind === ts.SyntaxKind.TypeReference) { 116 if (kind === ts.SyntaxKind.TypeReference) {
67 let t = tc.getTypeAtLocation(type); 117 let t = tc.getTypeAtLocation(type);
68 if (t.symbol && t.symbol.flags & ts.SymbolFlags.Function) return true; 118 if (t.symbol && t.symbol.flags & ts.SymbolFlags.Function) return true;
69 } 119 }
70 120
121 if (kind === ts.SyntaxKind.IntersectionType) {
122 let types = (<ts.IntersectionTypeNode>type).types;
123 for (let i = 0; i < types.length; ++i) {
124 if (isFunctionType(types[i], tc)) {
125 return true;
126 }
127 }
128 return false;
129 }
130
71 if (kind === ts.SyntaxKind.UnionType) { 131 if (kind === ts.SyntaxKind.UnionType) {
72 let types = (<ts.UnionTypeNode>type).types; 132 let types = (<ts.UnionTypeNode>type).types;
73 for (let i = 0; i < types.length; ++i) { 133 for (let i = 0; i < types.length; ++i) {
74 if (!isFunctionType(types[i], tc)) { 134 if (!isFunctionType(types[i], tc)) {
75 return false; 135 return false;
76 } 136 }
77 } 137 }
78 return true; 138 return true;
79 } 139 }
80 // Warning: if the kind is a reference type and the reference is to an 140 // Warning: if the kind is a reference type and the reference is to an
81 // interface that only has a call member we will not return that it is a 141 // interface that only has a call member we will not return that it is a
82 // function type. 142 // function type.
83 if (kind === ts.SyntaxKind.TypeLiteral) { 143 if (kind === ts.SyntaxKind.TypeLiteral) {
84 let members = (<ts.TypeLiteralNode>type).members; 144 let members = (<ts.TypeLiteralNode>type).members;
85 for (let i = 0; i < members.length; ++i) { 145 for (let i = 0; i < members.length; ++i) {
86 if (members[i].kind !== ts.SyntaxKind.CallSignature) { 146 if (members[i].kind !== ts.SyntaxKind.CallSignature) {
87 return false; 147 return false;
88 } 148 }
89 } 149 }
90 return true; 150 return true;
91 } 151 }
92 return false; 152 return false;
93 } 153 }
94 154
155 export function isThisParameter(param: ts.ParameterDeclaration): boolean {
156 return param.name && param.name.kind === ts.SyntaxKind.Identifier &&
157 (param.name as ts.Identifier).text === 'this';
158 }
159
160 /**
161 * Dart does not have a concept of binding the type of the "this" parameter to a method.
162 */
163 export function filterThisParameter(params: ts.ParameterDeclaration[]): ts.Param eterDeclaration[] {
164 let ret: ts.ParameterDeclaration[] = [];
165 for (let i = 0; i < params.length; i++) {
166 let param = params[i];
167 if (!isThisParameter(param)) {
168 ret.push(param);
169 }
170 }
171 return ret;
172 }
173
95 export function isTypeNode(node: ts.Node): boolean { 174 export function isTypeNode(node: ts.Node): boolean {
96 switch (node.kind) { 175 switch (node.kind) {
176 case ts.SyntaxKind.IntersectionType:
97 case ts.SyntaxKind.UnionType: 177 case ts.SyntaxKind.UnionType:
178 case ts.SyntaxKind.ParenthesizedType:
98 case ts.SyntaxKind.TypeReference: 179 case ts.SyntaxKind.TypeReference:
99 case ts.SyntaxKind.TypeLiteral: 180 case ts.SyntaxKind.TypeLiteral:
100 case ts.SyntaxKind.LastTypeNode: 181 case ts.SyntaxKind.LastTypeNode:
101 case ts.SyntaxKind.ArrayType: 182 case ts.SyntaxKind.ArrayType:
102 case ts.SyntaxKind.TypePredicate: 183 case ts.SyntaxKind.TypePredicate:
103 case ts.SyntaxKind.TypeQuery: 184 case ts.SyntaxKind.TypeQuery:
104 case ts.SyntaxKind.TupleType: 185 case ts.SyntaxKind.TupleType:
105 case ts.SyntaxKind.NumberKeyword: 186 case ts.SyntaxKind.NumberKeyword:
106 case ts.SyntaxKind.StringKeyword: 187 case ts.SyntaxKind.StringKeyword:
107 case ts.SyntaxKind.VoidKeyword: 188 case ts.SyntaxKind.VoidKeyword:
189 case ts.SyntaxKind.NullKeyword:
190 case ts.SyntaxKind.UndefinedKeyword:
108 case ts.SyntaxKind.BooleanKeyword: 191 case ts.SyntaxKind.BooleanKeyword:
109 case ts.SyntaxKind.AnyKeyword: 192 case ts.SyntaxKind.AnyKeyword:
110 case ts.SyntaxKind.FunctionType: 193 case ts.SyntaxKind.FunctionType:
194 case ts.SyntaxKind.ThisType:
111 return true; 195 return true;
112 default: 196 default:
113 return false; 197 return false;
114 } 198 }
115 } 199 }
116 200
117 export function isCallable(decl: ClassLike): boolean { 201 export function isCallable(decl: ClassLike): boolean {
118 let members = decl.members as Array<ts.ClassElement>; 202 let members = decl.members as Array<ts.ClassElement>;
119 return members.some((member) => { return member.kind === ts.SyntaxKind.CallSig nature; }); 203 return members.some((member) => { return member.kind === ts.SyntaxKind.CallSig nature; });
120 } 204 }
(...skipping 13 matching lines...) Expand all
134 } 218 }
135 219
136 export function getAncestor(n: ts.Node, kind: ts.SyntaxKind): ts.Node { 220 export function getAncestor(n: ts.Node, kind: ts.SyntaxKind): ts.Node {
137 for (let parent = n; parent; parent = parent.parent) { 221 for (let parent = n; parent; parent = parent.parent) {
138 if (parent.kind === kind) return parent; 222 if (parent.kind === kind) return parent;
139 } 223 }
140 return null; 224 return null;
141 } 225 }
142 226
143 export function getEnclosingClass(n: ts.Node): ClassLike { 227 export function getEnclosingClass(n: ts.Node): ClassLike {
144 for (let parent = n.parent; parent; parent = parent.parent) { 228 while (n) {
145 if (parent.kind === ts.SyntaxKind.ClassDeclaration || 229 if (n.kind === ts.SyntaxKind.ClassDeclaration ||
146 parent.kind === ts.SyntaxKind.InterfaceDeclaration) { 230 n.kind === ts.SyntaxKind.InterfaceDeclaration) {
147 return <ClassLike>parent; 231 return <ClassLike>n;
148 } 232 }
233 n = n.parent;
149 } 234 }
150 return null; 235 return null;
151 } 236 }
152 237
153 export function isConstCall(node: ts.CallExpression): boolean { 238 export function isConstCall(node: ts.CallExpression): boolean {
154 return node && ident(node.expression) === 'CONST_EXPR'; 239 return node && ident(node.expression) === 'CONST_EXPR';
155 } 240 }
156 241
157 export function isInsideConstExpr(node: ts.Node): boolean { 242 export function isInsideConstExpr(node: ts.Node): boolean {
158 return isConstCall(<ts.CallExpression>getAncestor(node, ts.SyntaxKind.CallExpr ession)); 243 return isConstCall(<ts.CallExpression>getAncestor(node, ts.SyntaxKind.CallExpr ession));
159 } 244 }
160 245
161 export function formatType(s: string, comment: string, insideCodeComment: boolea n): string { 246 export function formatType(s: string, comment: string, options: TypeDisplayOptio ns): string {
162 if (!comment) { 247 if (!comment || options.hideComment) {
163 return s; 248 return s;
164 } else if (insideCodeComment) { 249 } else if (options.insideComment) {
165 // When inside a comment we only need to emit the comment version which 250 // When inside a comment we only need to emit the comment version which
166 // is the syntax we would like to use if Dart supported all language 251 // is the syntax we would like to use if Dart supported all language
167 // features we would like to use for interop. 252 // features we would like to use for interop.
168 return comment; 253 return comment;
169 } else { 254 } else {
170 let sb = s + '/*'; 255 let sb = s + '/*';
171 // Check if the comment is a valid type name in which case it is safe to use the Dart code 256 // Check if the comment is a valid type name in which case it is safe to use the Dart code
172 // written in comments syntax. 257 // written in comments syntax.
173 const stubToMakeTypeValidStatement = ' DUMMY_VARIABLE_NAME;'; 258 const stubToMakeTypeValidStatement = ' DUMMY_VARIABLE_NAME;';
174 comment = comment.trim(); 259 comment = comment.trim();
(...skipping 20 matching lines...) Expand all
195 280
196 visit(n: ts.Node) { this.transpiler.visit(n); } 281 visit(n: ts.Node) { this.transpiler.visit(n); }
197 pushContext(context: OutputContext) { this.transpiler.pushContext(context); } 282 pushContext(context: OutputContext) { this.transpiler.pushContext(context); }
198 popContext() { this.transpiler.popContext(); } 283 popContext() { this.transpiler.popContext(); }
199 emit(s: string) { this.transpiler.emit(s); } 284 emit(s: string) { this.transpiler.emit(s); }
200 emitNoSpace(s: string) { this.transpiler.emitNoSpace(s); } 285 emitNoSpace(s: string) { this.transpiler.emitNoSpace(s); }
201 emitType(s: string, comment: string) { this.transpiler.emitType(s, comment); } 286 emitType(s: string, comment: string) { this.transpiler.emitType(s, comment); }
202 maybeLineBreak() { return this.transpiler.maybeLineBreak(); } 287 maybeLineBreak() { return this.transpiler.maybeLineBreak(); }
203 enterCodeComment() { return this.transpiler.enterCodeComment(); } 288 enterCodeComment() { return this.transpiler.enterCodeComment(); }
204 exitCodeComment() { return this.transpiler.exitCodeComment(); } 289 exitCodeComment() { return this.transpiler.exitCodeComment(); }
290
291 enterTypeArguments() { this.transpiler.enterTypeArgument(); }
292 exitTypeArguments() { this.transpiler.exitTypeArgument(); }
293 get insideTypeArgument() { return this.transpiler.insideTypeArgument; }
294
205 get insideCodeComment() { return this.transpiler.insideCodeComment; } 295 get insideCodeComment() { return this.transpiler.insideCodeComment; }
206 296
207 emitImport(toEmit: string) { 297 emitImport(toEmit: string) {
208 if (!this.transpiler.importsEmitted[toEmit]) { 298 if (!this.transpiler.importsEmitted[toEmit]) {
209 this.pushContext(OutputContext.Import); 299 this.pushContext(OutputContext.Import);
210 this.emit(`import "${toEmit}";`); 300 this.emit(`import "${toEmit}";\n`);
211 this.transpiler.importsEmitted[toEmit] = true; 301 this.transpiler.importsEmitted[toEmit] = true;
212 this.popContext(); 302 this.popContext();
213 } 303 }
214 } 304 }
215 305
306 emitImportForSourceFile(sourceFile: ts.SourceFile, context: ts.SourceFile) {
307 if (sourceFile === context) return;
308 if (sourceFile.hasNoDefaultLib) {
309 // We don't want to emit imports to default lib libraries as we replace wi th Dart equivalents
310 // such as dart:html, etc.
311 return;
312 }
313 let relativePath = path.relative(path.dirname(context.fileName), sourceFile. fileName);
314 this.emitImport(this.transpiler.getDartFileName(relativePath));
315 }
316
317
216 reportError(n: ts.Node, message: string) { this.transpiler.reportError(n, mess age); } 318 reportError(n: ts.Node, message: string) { this.transpiler.reportError(n, mess age); }
217 319
218 visitNode(n: ts.Node): boolean { throw new Error('not implemented'); } 320 visitNode(n: ts.Node): boolean { throw new Error('not implemented'); }
219 321
220 visitEach(nodes: ts.Node[]) { nodes.forEach((n) => this.visit(n)); } 322 visitEach(nodes: ts.Node[]) { nodes.forEach((n) => this.visit(n)); }
221 323
222 visitEachIfPresent(nodes?: ts.Node[]) { 324 visitEachIfPresent(nodes?: ts.Node[]) {
223 if (nodes) this.visitEach(nodes); 325 if (nodes) this.visitEach(nodes);
224 } 326 }
225 327
226 visitList(nodes: ts.Node[], separator = ',') { 328 visitList(nodes: ts.Node[], separator = ',') {
227 for (let i = 0; i < nodes.length; i++) { 329 for (let i = 0; i < nodes.length; i++) {
228 this.visit(nodes[i]); 330 this.visit(nodes[i]);
229 if (i < nodes.length - 1) this.emitNoSpace(separator); 331 if (i < nodes.length - 1) this.emitNoSpace(separator);
230 } 332 }
231 } 333 }
232 334
335 /**
336 * Returns whether any parameters were actually emitted.
337 * @param nodes
338 * @param separator
339 */
340 visitParameterList(nodes: ts.ParameterDeclaration[]): boolean {
341 let emittedParameters = false;
342 for (let i = 0; i < nodes.length; ++i) {
343 let param = nodes[i];
344 if (!this.insideCodeComment && isThisParameter(param)) {
345 // Emit the this type in a comment as it could be of interest to Dart us ers who are
346 // calling allowInteropCaptureThis to bind a Dart method.
347 this.enterCodeComment();
348 this.visit(param.type);
349 this.emit('this');
350 this.exitCodeComment();
351 continue;
352 }
353 if (emittedParameters) {
354 this.emitNoSpace(',');
355 }
356 this.visit(param);
357 emittedParameters = true;
358 }
359 return emittedParameters;
360 }
361
233 uniqueId(name: string): string { 362 uniqueId(name: string): string {
234 const id = this.idCounter++; 363 const id = this.idCounter++;
235 return `_${name}\$\$js_facade_gen\$${id}`; 364 return `_${name}\$\$js_facade_gen\$${id}`;
236 } 365 }
237 366
238 assert(c: ts.Node, condition: boolean, reason: string): void { 367 assert(c: ts.Node, condition: boolean, reason: string): void {
239 if (!condition) { 368 if (!condition) {
240 this.reportError(c, reason); 369 this.reportError(c, reason);
241 throw new Error(reason); 370 throw new Error(reason);
242 } 371 }
(...skipping 21 matching lines...) Expand all
264 } 393 }
265 394
266 hasFlag(n: {flags: number}, flag: ts.NodeFlags): boolean { 395 hasFlag(n: {flags: number}, flag: ts.NodeFlags): boolean {
267 return n && (n.flags & flag) !== 0 || false; 396 return n && (n.flags & flag) !== 0 || false;
268 } 397 }
269 398
270 getRelativeFileName(fileName: string): string { 399 getRelativeFileName(fileName: string): string {
271 return this.transpiler.getRelativeFileName(fileName); 400 return this.transpiler.getRelativeFileName(fileName);
272 } 401 }
273 402
403 getDartFileName(fileName?: string): string { return this.transpiler.getDartFil eName(fileName); }
404
274 maybeVisitTypeArguments(n: {typeArguments?: ts.NodeArray<ts.TypeNode>}) { 405 maybeVisitTypeArguments(n: {typeArguments?: ts.NodeArray<ts.TypeNode>}) {
275 if (n.typeArguments) { 406 if (n.typeArguments) {
276 // If it's a single type argument `<void>`, ignore it and emit nothing.
277 // This is particularly useful for `Promise<void>`, see
278 // https://github.com/dart-lang/sdk/issues/2231#issuecomment-108313639
279 if (n.typeArguments.length === 1 && n.typeArguments[0].kind === ts.SyntaxK ind.VoidKeyword) {
280 return;
281 }
282 this.emitNoSpace('<'); 407 this.emitNoSpace('<');
408 this.enterTypeArguments();
283 this.visitList(n.typeArguments); 409 this.visitList(n.typeArguments);
410 this.exitTypeArguments();
284 this.emitNoSpace('>'); 411 this.emitNoSpace('>');
285 } 412 }
286 } 413 }
287 414
288 visitParameters(parameters: ts.ParameterDeclaration[]) { 415 visitParameters(parameters: ts.ParameterDeclaration[]) {
289 this.emitNoSpace('('); 416 this.emitNoSpace('(');
290 let firstInitParamIdx = 0; 417 let firstInitParamIdx = 0;
291 for (; firstInitParamIdx < parameters.length; firstInitParamIdx++) { 418 for (; firstInitParamIdx < parameters.length; firstInitParamIdx++) {
292 // ObjectBindingPatterns are handled within the parameter visit. 419 // ObjectBindingPatterns are handled within the parameter visit.
293 let isOpt = parameters[firstInitParamIdx].initializer || 420 let isOpt = parameters[firstInitParamIdx].initializer ||
294 parameters[firstInitParamIdx].questionToken || 421 parameters[firstInitParamIdx].questionToken ||
295 parameters[firstInitParamIdx].dotDotDotToken; 422 parameters[firstInitParamIdx].dotDotDotToken;
296 if (isOpt && parameters[firstInitParamIdx].name.kind !== ts.SyntaxKind.Obj ectBindingPattern) { 423 if (isOpt && parameters[firstInitParamIdx].name.kind !== ts.SyntaxKind.Obj ectBindingPattern) {
297 break; 424 break;
298 } 425 }
299 } 426 }
300 427
428 let hasValidParameters = false;
301 if (firstInitParamIdx !== 0) { 429 if (firstInitParamIdx !== 0) {
302 let requiredParams = parameters.slice(0, firstInitParamIdx); 430 let requiredParams = parameters.slice(0, firstInitParamIdx);
303 this.visitList(requiredParams); 431 hasValidParameters = this.visitParameterList(requiredParams);
304 } 432 }
305 433
306 if (firstInitParamIdx !== parameters.length) { 434 if (firstInitParamIdx !== parameters.length) {
307 if (firstInitParamIdx !== 0) this.emitNoSpace(','); 435 if (hasValidParameters) this.emitNoSpace(',');
308 let positionalOptional = parameters.slice(firstInitParamIdx, parameters.le ngth); 436 let positionalOptional = parameters.slice(firstInitParamIdx, parameters.le ngth);
309 this.emit('['); 437 this.emit('[');
310 this.visitList(positionalOptional); 438 this.visitParameterList(positionalOptional);
311 this.emitNoSpace(']'); 439 this.emitNoSpace(']');
312 } 440 }
313 441
314 this.emitNoSpace(')'); 442 this.emitNoSpace(')');
315 } 443 }
316 } 444 }
OLDNEW
« no previous file with comments | « README.md ('k') | lib/declaration.ts » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698