Skip to content

Commit 4a2423f

Browse files
authored
Merge pull request dart-archive#45 from derekxu16/master
Handled TypeScript utility types and mapped types
2 parents c6fc12a + ac6c7b9 commit 4a2423f

File tree

7 files changed

+109
-15
lines changed

7 files changed

+109
-15
lines changed

lib/base.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ export function isTypeNode(node: ts.Node): boolean {
239239
case ts.SyntaxKind.ArrayType:
240240
case ts.SyntaxKind.TypeOperator:
241241
case ts.SyntaxKind.IndexedAccessType:
242+
case ts.SyntaxKind.MappedType:
242243
case ts.SyntaxKind.TypePredicate:
243244
case ts.SyntaxKind.TypeQuery:
244245
case ts.SyntaxKind.TupleType:
@@ -332,7 +333,6 @@ export function formatType(s: string, comment: string, options: TypeDisplayOptio
332333
let expectedStubIndex = result.code.length - stubToMakeTypeValidStatement.length;
333334
if (result.code.lastIndexOf(stubToMakeTypeValidStatement) === expectedStubIndex) {
334335
comment = result.code.substring(0, expectedStubIndex).trim();
335-
sb += '=';
336336
}
337337
}
338338
sb += comment;

lib/declaration.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -565,9 +565,16 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
565565
base.ident(alias.name), type as ts.FunctionTypeNode, alias.typeParameters);
566566
} else {
567567
this.enterCodeComment();
568+
if (ts.isMappedTypeNode(alias.type)) {
569+
this.emitNoSpace('\n');
570+
this.emit(
571+
'Warning: Mapped types are not supported in Dart. Uses of this type will be replaced by dynamic.');
572+
this.emitNoSpace('\n');
573+
}
568574
this.emit(alias.getText());
575+
this.emitNoSpace('\n');
569576
this.exitCodeComment();
570-
this.emit('\n');
577+
this.emitNoSpace('\n');
571578
}
572579
// We ignore other type alias declarations as Dart doesn't have a corresponding feature yet.
573580
} break;

lib/facade_converter.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,24 @@ export class FacadeConverter extends base.TranspilerBase {
358358
const indexTypeName = this.generateDartTypeName(node.indexType, addInsideComment(options));
359359

360360
comment = `${objectTypeName}[${indexTypeName}]`;
361+
} else if (ts.isMappedTypeNode(node)) {
362+
name = 'dynamic';
363+
if (ts.isTypeAliasDeclaration(node.parent)) {
364+
// MappedTypeNodes don't contain information about the name or type arguments that were
365+
// passed to the alias, so we have to get that information from the parent
366+
const parent = node.parent;
367+
comment = parent.name.getText();
368+
if (parent.typeParameters && options.resolvedTypeArguments) {
369+
comment += '<';
370+
const resolvedParameters = parent.typeParameters.map(
371+
param => this.generateDartTypeName(
372+
options.resolvedTypeArguments.get(base.ident(param.name)), options));
373+
for (const resolvedParam of resolvedParameters) {
374+
comment += resolvedParam;
375+
}
376+
comment += '>';
377+
}
378+
}
361379
} else if (ts.isTypePredicateNode(node)) {
362380
name = 'bool';
363381
comment = base.ident(node.parameterName) + ' is ' +
@@ -387,7 +405,26 @@ export class FacadeConverter extends base.TranspilerBase {
387405
} else if (ts.isTypePredicateNode(node)) {
388406
return this.generateDartTypeName(node.type, options);
389407
} else if (ts.isTypeReferenceNode(node)) {
390-
name = this.generateDartName(node.typeName, setTypeArguments(options, node.typeArguments));
408+
// First, check for certain TypeScript utility types and handle them manually as continuing to
409+
// call generateDartType will try to resolve a type alias that uses the mapped type feature,
410+
// which doesn't have a Dart equivalent and will become dynamic
411+
// https://www.typescriptlang.org/docs/handbook/utility-types.html
412+
const type = node.typeName.getText();
413+
switch (type) {
414+
case 'Partial':
415+
// Partial<X> is currently the same as X since all types are nullable in Dart
416+
name = this.generateDartTypeName(node.typeArguments[0]);
417+
comment = node.typeName.getText() + '<' +
418+
this.generateTypeList(node.typeArguments, addInsideComment(options)) + '>';
419+
break;
420+
case 'Record':
421+
// TODO(derekx): It should be possible to generate a Readonly version of a class by
422+
// handling it in the same way as other readonly types. That is, emitting another class
423+
// like JS$ReadonlyX whose members don't have setters.
424+
default:
425+
name =
426+
this.generateDartName(node.typeName, setTypeArguments(options, node.typeArguments));
427+
}
391428
} else if (ts.isTypeLiteralNode(node)) {
392429
let members = node.members;
393430
if (members.length === 1 && ts.isIndexSignatureDeclaration(members[0])) {

lib/main.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -580,8 +580,13 @@ class Output {
580580
if (!result.error) {
581581
code = result.code;
582582
}
583-
code = code.trim();
584-
this.emitNoSpace(code);
583+
const trimmed = code.trim();
584+
const isMultilineComment = trimmed.indexOf('\n') !== -1;
585+
if (isMultilineComment) {
586+
this.emitNoSpace(code);
587+
} else {
588+
this.emitNoSpace(trimmed);
589+
}
585590
this.emitNoSpace('*/');
586591

587592
// Don't really need an exact column, just need to track

test/function_test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ external f(D fn(C a(B b)));`);
4444
});
4545
it('supports generic-typed function parameters', () => {
4646
expectTranslate('function f<T, U>(fn: (a: T, b: U) => T) {}').to.equal(`@JS()
47-
external f/*<T, U>*/(dynamic/*=T*/ fn(dynamic/*=T*/ a, dynamic/*=U*/ b));`);
47+
external f/*<T, U>*/(dynamic /*T*/ fn(dynamic /*T*/ a, dynamic /*U*/ b));`);
4848
});
4949
it('translates functions taking rest parameters to untyped Function', () => {
5050
expectTranslate('function f(fn: (...a: string[]) => number) {}').to.equal(`@JS()
@@ -98,14 +98,14 @@ x({String a, num b, c}) {
9898
describe('generic functions', () => {
9999
it('supports generic types', () => {
100100
expectTranslate('function sort<T, U>(xs: T[]): T[] { return xs; }').to.equal(`@JS()
101-
external List<dynamic/*=T*/ > sort/*<T, U>*/(List<dynamic/*=T*/ > xs);`);
101+
external List<dynamic /*T*/ > sort/*<T, U>*/(List<dynamic /*T*/ > xs);`);
102102
});
103103
it('replaces type usage sites, but not idents', () => {
104104
expectTranslate(`function wobble<T, U>(u: U): T { }`).to.equal(`@JS()
105-
external dynamic/*=T*/ wobble/*<T, U>*/(dynamic/*=U*/ u);`);
105+
external dynamic /*T*/ wobble/*<T, U>*/(dynamic /*U*/ u);`);
106106
});
107107
it('translates generic calls', () => {
108108
expectTranslate(`function wobble<T>(foo: T): T { }`).to.equal(`@JS()
109-
external dynamic/*=T*/ wobble/*<T>*/(dynamic/*=T*/ foo);`);
109+
external dynamic /*T*/ wobble/*<T>*/(dynamic /*T*/ foo);`);
110110
});
111111
});

test/js_interop_test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ class X {
266266
class X<T> {
267267
// @Ignore
268268
X.fakeConstructor$();
269-
external static X<dynamic/*=T*/ > Z/*<T>*/();
269+
external static X<dynamic /*T*/ > Z/*<T>*/();
270270
}`);
271271
expectTranslate('class X<T> { Z(): X<T> {} }').to.equal(`@JS()
272272
class X<T> {
@@ -288,7 +288,7 @@ class X {
288288
/*external T createElement<T>('img' tagName);*/
289289
/*external T createElement<T>('video' tagName);*/
290290
/*external T createElement<T>(String tagName);*/
291-
external dynamic/*=T*/ createElement/*<T>*/(
291+
external dynamic /*T*/ createElement/*<T>*/(
292292
String /*'img'|'video'|String*/ tagName);
293293
}`);
294294

@@ -322,7 +322,7 @@ class X {
322322
/*external T createElement<T extends ImageElement>('img' tagName);*/
323323
/*external T createElement<T extends VideoElement>('video' tagName);*/
324324
/*external T createElement<T extends Element>(String tagName);*/
325-
external dynamic/*=T*/ createElement/*<T>*/(
325+
external dynamic /*T*/ createElement/*<T>*/(
326326
String /*'img'|'video'|String*/ tagName);
327327
}`);
328328

@@ -936,7 +936,8 @@ function addEventListener(type: string, listener: (this: Element, event: Event)
936936
937937
/*external addEventListener('load' type, void listener(ImageElement JS$this, Event event));*/
938938
/*external addEventListener(
939-
String type, void listener(Element JS$this, Event event));*/
939+
String type, void listener(Element JS$this, Event event));
940+
*/
940941
@JS()
941942
external addEventListener(
942943
String /*'load'|String*/ type, void listener(/*Element this*/ Event event));`);

test/type_test.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,50 @@ external String Function(String) get x;
9696
@JS()
9797
external set x(String Function(String) v);`);
9898
});
99+
100+
describe('TypeScript utility types and other mapped types', () => {
101+
it('emits X in place of Partial<X> since all Dart types are currently nullable', () => {
102+
expectTranslate('interface X { a: number; } declare const x: Partial<X>;')
103+
.to.equal(`@anonymous
104+
@JS()
105+
abstract class X {
106+
external num get a;
107+
external set a(num v);
108+
external factory X({num a});
109+
}
110+
111+
@JS()
112+
external X /*Partial<X>*/ get x;`);
113+
});
114+
115+
it('treats other mapped types as dynamic', () => {
116+
expectTranslate(`interface Todo {
117+
task: string;
118+
}
119+
120+
type ReadonlyTodo = {
121+
readonly[P in keyof Todo]: Todo[P];
122+
}
123+
124+
declare const todo: ReadonlyTodo;`)
125+
.to.equal(`@anonymous
126+
@JS()
127+
abstract class Todo {
128+
external String get task;
129+
external set task(String v);
130+
external factory Todo({String task});
131+
}
132+
133+
/*
134+
Warning: Mapped types are not supported in Dart. Uses of this type will be replaced by dynamic.
135+
type ReadonlyTodo = {
136+
readonly[P in keyof Todo]: Todo[P];
137+
}
138+
*/
139+
@JS()
140+
external dynamic /*ReadonlyTodo*/ get todo;`);
141+
});
142+
});
99143
});
100144

101145
describe('type arguments', () => {
@@ -239,7 +283,7 @@ external void dispatchSimple(SimpleValueFn<String, num> callback);`);
239283
`).to.equal(`/*export type Triangle<G> = [G, G, G];*/
240284
/*export type ListOfLists<G> = [G[]];*/
241285
@JS()
242-
external List<List<dynamic/*=T*/ > /*Tuple of <T,T,T>*/ > triangles/*<T>*/();`);
286+
external List<List<dynamic /*T*/ > /*Tuple of <T,T,T>*/ > triangles/*<T>*/();`);
243287
});
244288

245289
it('supports the keyof operator and the indexed access operator', () => {
@@ -257,6 +301,6 @@ abstract class A {
257301
258302
@JS()
259303
external bool f/*<K extends keyof A>*/(
260-
dynamic/*=K*/ first, dynamic /*A[K]*/ second);`);
304+
dynamic /*K*/ first, dynamic /*A[K]*/ second);`);
261305
});
262306
});

0 commit comments

Comments
 (0)