| Index: lib/facade_converter.ts
|
| diff --git a/lib/facade_converter.ts b/lib/facade_converter.ts
|
| index c43dda92e5216def334104a373d7d09e45d13ab0..2ffaf9941a0e00f805d9ec2576fb84eb2b5ae4d9 100644
|
| --- a/lib/facade_converter.ts
|
| +++ b/lib/facade_converter.ts
|
| @@ -1,7 +1,7 @@
|
| import * as ts from 'typescript';
|
|
|
| import * as base from './base';
|
| -import {Set} from './base';
|
| +import {Set, TypeDisplayOptions} from './base';
|
| import {DART_LIBRARIES_FOR_BROWSER_TYPES, TS_TO_DART_TYPENAMES} from './dart_libraries_for_browser_types';
|
| import {Transpiler} from './main';
|
| import {MergedType} from './merge';
|
| @@ -22,23 +22,23 @@ const MAX_DART_FUNC_ACTION_PARAMETERS_OPTIONAL = 1;
|
| * Prefix to add to a variable name that leaves the JS name referenced
|
| * unchanged.
|
| */
|
| -const DART_RESERVED_NAME_PREFIX = 'JS$';
|
| +export const DART_RESERVED_NAME_PREFIX = 'JS$';
|
|
|
| export function fixupIdentifierName(text: string): string {
|
| return (FacadeConverter.DART_RESERVED_WORDS.indexOf(text) !== -1 ||
|
| - FacadeConverter.DART_OTHER_KEYWORDS.indexOf(text) !== -1 || text[0] === '_') ?
|
| + FacadeConverter.DART_OTHER_KEYWORDS.indexOf(text) !== -1 || text.match(/^(\d|_)/)) ?
|
| DART_RESERVED_NAME_PREFIX + text :
|
| text;
|
| }
|
|
|
| -function numOptionalParameters(parameters: ts.NodeArray<ts.ParameterDeclaration>): number {
|
| +function numOptionalParameters(parameters: ts.ParameterDeclaration[]): number {
|
| for (let i = 0; i < parameters.length; ++i) {
|
| if (parameters[i].questionToken) return parameters.length - i;
|
| }
|
| return 0;
|
| }
|
|
|
| -function hasVarArgs(parameters: ts.NodeArray<ts.ParameterDeclaration>): boolean {
|
| +function hasVarArgs(parameters: ts.ParameterDeclaration[]): boolean {
|
| for (let i = 0; i < parameters.length; ++i) {
|
| if (parameters[i].dotDotDotToken) return true;
|
| }
|
| @@ -161,8 +161,65 @@ export class NameRewriter {
|
| lookupName(node: base.NamedDeclaration, context: ts.Node) { return this.computeName(node).name; }
|
| }
|
|
|
| +function cloneOptions(options: TypeDisplayOptions): TypeDisplayOptions {
|
| + return {
|
| + insideComment: options.insideComment,
|
| + insideTypeArgument: options.insideTypeArgument,
|
| + hideComment: options.hideComment,
|
| + typeArguments: options.typeArguments,
|
| + resolvedTypeArguments: options.resolvedTypeArguments
|
| + };
|
| +}
|
| +
|
| +function addInsideTypeArgument(options: TypeDisplayOptions): TypeDisplayOptions {
|
| + let ret = cloneOptions(options);
|
| + ret.insideTypeArgument = true;
|
| + return ret;
|
| +}
|
| +
|
| +function addInsideComment(options: TypeDisplayOptions): TypeDisplayOptions {
|
| + let ret = cloneOptions(options);
|
| + ret.insideComment = true;
|
| + return ret;
|
| +}
|
| +
|
| +function addHideComment(options: TypeDisplayOptions): TypeDisplayOptions {
|
| + let ret = cloneOptions(options);
|
| + ret.hideComment = true;
|
| + return ret;
|
| +}
|
| +
|
| +function setTypeArguments(
|
| + options: TypeDisplayOptions, typeArguments: ts.TypeNode[]): TypeDisplayOptions {
|
| + let ret = cloneOptions(options);
|
| + ret.typeArguments = typeArguments;
|
| + return ret;
|
| +}
|
| +
|
| +function resolveTypeArguments(
|
| + options: TypeDisplayOptions, parameters: ts.TypeParameterDeclaration[]) {
|
| + let ret = cloneOptions(options);
|
| + let typeArguments = options.typeArguments ? options.typeArguments : [];
|
| + ret.resolvedTypeArguments = {};
|
| + if (parameters) {
|
| + for (let i = 0; i < parameters.length; ++i) {
|
| + let param = parameters[i];
|
| + ret.resolvedTypeArguments[base.ident(param.name)] = typeArguments[i];
|
| + }
|
| + }
|
| + // Type arguments have been resolved forward so we don't need to emit them directly.
|
| + ret.typeArguments = null;
|
| + return ret;
|
| +}
|
| +
|
| +function removeResolvedTypeArguments(options: TypeDisplayOptions): TypeDisplayOptions {
|
| + let ret = cloneOptions(options);
|
| + ret.resolvedTypeArguments = null;
|
| + return ret;
|
| +}
|
| +
|
| export class FacadeConverter extends base.TranspilerBase {
|
| - private tc: ts.TypeChecker;
|
| + tc: ts.TypeChecker;
|
| // For the Dart keyword list see
|
| // https://www.dartlang.org/docs/dart-up-and-running/ch02.html#keywords
|
| static DART_RESERVED_WORDS =
|
| @@ -177,21 +234,31 @@ export class FacadeConverter extends base.TranspilerBase {
|
| 'library operator part set static sync typedef yield call')
|
| .split(/ /);
|
|
|
| + // TODO(jacobr): add separate list of members of the core Dart object class and core Dart List
|
| + // class that need to be escaped with JS$ when being entered as properties.
|
| + // As methods on Object and List will take priorty over JS subclasses.
|
| + // Note that toString is fine as the default implementation is compatible with JavaScript.
|
| + static DART_OBJECT_MEMBERS = ('hashCode, runtimeType noSuchMethod').split(/ /);
|
| +
|
| + static DART_LIST_MEMBERS =
|
| + ('first hashCode isEmpty isNotEmpty iterator last length reversed runtimeType single add ' +
|
| + 'addAll any asMap clear contains elementAt every expand fillRange firstWhere fold forEach ' +
|
| + 'getRange indexOf insert insertAll join lastIndexOf lastWhere map noSuchMethod reduce ' +
|
| + 'remove removeAt removeLast removeRange removeWhere replaceRange retainWhere setAll ' +
|
| + 'setRange shuffle singleWhere skip skipWhile sort sublist take takeWhile toList toSet ' +
|
| + 'toString where')
|
| + .split(/ /);
|
| +
|
| private candidateTypes: {[typeName: string]: boolean} = {};
|
| private typingsRootRegex: RegExp;
|
| private genericMethodDeclDepth = 0;
|
|
|
| - constructor(
|
| - transpiler: Transpiler, typingsRoot = '', private nameRewriter: NameRewriter,
|
| - private useHtml: boolean) {
|
| + constructor(transpiler: Transpiler, typingsRoot = '', private nameRewriter: NameRewriter) {
|
| super(transpiler);
|
| - if (useHtml) {
|
| - this.extractPropertyNames(TS_TO_DART_TYPENAMES, this.candidateTypes);
|
| - Object.keys(DART_LIBRARIES_FOR_BROWSER_TYPES)
|
| - .forEach((propName) => this.candidateTypes[propName] = true);
|
| - } else {
|
| - this.extractPropertyNames(TS_TO_DART_TYPENAMES, this.candidateTypes);
|
| - }
|
| + this.extractPropertyNames(TS_TO_DART_TYPENAMES, this.candidateTypes);
|
| + // Remove this line if decide to support generating code that avoids dart:html.
|
| + Object.keys(DART_LIBRARIES_FOR_BROWSER_TYPES)
|
| + .forEach((propName) => this.candidateTypes[propName] = true);
|
|
|
| this.typingsRootRegex = new RegExp('^' + typingsRoot.replace('.', '\\.'));
|
| }
|
| @@ -262,52 +329,49 @@ export class FacadeConverter extends base.TranspilerBase {
|
| return symbol.declarations.some(d => d.parent.kind === ts.SyntaxKind.FunctionDeclaration);
|
| }
|
|
|
| - generateTypeList(types: ts.TypeNode[], insideComment: boolean, seperator = ','): string {
|
| + generateTypeList(types: ts.TypeNode[], options: TypeDisplayOptions, seperator = ','): string {
|
| let that = this;
|
| - return types.map((type) => { return that.generateDartTypeName(type, insideComment); })
|
| + return types
|
| + .map((type) => { return that.generateDartTypeName(type, addInsideTypeArgument(options)); })
|
| .join(seperator);
|
| }
|
|
|
| - maybeGenerateTypeArguments(
|
| - n: {typeArguments?: ts.NodeArray<ts.TypeNode>}, insideComment: boolean): string {
|
| - if (!n.typeArguments) return '';
|
| - return '<' + this.generateTypeList(n.typeArguments, insideComment) + '>';
|
| - }
|
| + generateDartTypeName(node: ts.TypeNode, options?: TypeDisplayOptions): string {
|
| + if (!options) {
|
| + options = {
|
| + insideComment: this.insideCodeComment,
|
| + insideTypeArgument: this.insideTypeArgument
|
| + };
|
| + }
|
|
|
| - generateDartTypeName(node: ts.TypeNode, insideComment: boolean): string {
|
| let name: string;
|
| let comment: string;
|
| if (!node) {
|
| return 'dynamic';
|
| }
|
| +
|
| switch (node.kind) {
|
| case ts.SyntaxKind.TypeQuery:
|
| - let query = <ts.TypeQueryNode>node;
|
| name = 'dynamic';
|
| - name += '/* Dart does not support TypeQuery: typeof ' + base.ident(query.exprName) + ' */';
|
| + // TODO(jacobr): evaluate supporting this case.
|
| + // let query = <ts.TypeQueryNode>node;
|
| + // name += '/* TypeQuery: typeof ' + base.ident(query.exprName) + ' */';
|
| break;
|
| - case ts.SyntaxKind.LastTypeNode:
|
| - let type = (node as ts.ParenthesizedTypeNode).type;
|
| - if (!type) {
|
| - // This case occurs for String literal types
|
| - comment = node.getText();
|
| - // TODO(jacobr): find a better way to detect string literal types.
|
| - name = comment[0] === '"' ? 'String' : 'dynamic';
|
| - break;
|
| - }
|
| - return this.generateDartTypeName(type, insideComment);
|
| case ts.SyntaxKind.TypePredicate:
|
| - return this.generateDartTypeName((node as ts.TypePredicateNode).type, insideComment);
|
| + return this.generateDartTypeName((node as ts.TypePredicateNode).type, options);
|
| case ts.SyntaxKind.TupleType:
|
| let tuple = <ts.TupleTypeNode>node;
|
| name = 'List<';
|
| let mergedType = new MergedType(this);
|
| tuple.elementTypes.forEach((t) => mergedType.merge(t));
|
| - name += this.generateDartTypeName(mergedType.toTypeNode(), insideComment);
|
| + name += this.generateDartTypeName(mergedType.toTypeNode(), addInsideTypeArgument(options));
|
| name += '>';
|
| - comment = 'Tuple<' + this.generateTypeList(tuple.elementTypes, insideComment) + '>';
|
| + // Intentionally ensure this comment is not valid Dart code so we do not use the /*=
|
| + // syntax and so that it is clear this isn't a true Dart code comment.
|
| + comment = 'Tuple of <' +
|
| + this.generateTypeList(tuple.elementTypes, addInsideComment(options)) + '>';
|
| break;
|
| - case ts.SyntaxKind.UnionType:
|
| + case ts.SyntaxKind.UnionType: {
|
| let union = <ts.UnionTypeNode>node;
|
| // TODO(jacobr): this isn't fundamentally JS Interop specific but we
|
| // choose to be more aggressive at finding a useful value for the
|
| @@ -315,19 +379,34 @@ export class FacadeConverter extends base.TranspilerBase {
|
| // types will not be used extensively.
|
| let simpleType = this.toSimpleDartType(union.types);
|
| if (simpleType) {
|
| - name = this.generateDartTypeName(simpleType, insideComment);
|
| + name = this.generateDartTypeName(simpleType, addHideComment(options));
|
| } else {
|
| name = 'dynamic';
|
| }
|
| let types = union.types;
|
| - comment = this.generateTypeList(types, true, '|');
|
| - break;
|
| + comment = this.generateTypeList(types, addInsideComment(options), '|');
|
| + } break;
|
| + case ts.SyntaxKind.IntersectionType: {
|
| + let intersection = <ts.IntersectionTypeNode>node;
|
| + // TODO(jacobr): this isn't fundamentally JS Interop specific but we
|
| + // choose to be more aggressive at finding a useful value for the
|
| + // union when in JS Interop mode while otherwise we expect that union
|
| + // types will not be used extensively.
|
| + let simpleType = this.toSimpleDartType(intersection.types);
|
| + if (simpleType) {
|
| + name = this.generateDartTypeName(simpleType, addHideComment(options));
|
| + } else {
|
| + name = 'dynamic';
|
| + }
|
| + let types = intersection.types;
|
| + comment = this.generateTypeList(types, addInsideComment(options), '&');
|
| + } break;
|
| case ts.SyntaxKind.TypePredicate:
|
| - return this.generateDartTypeName((node as ts.TypePredicateNode).type, insideComment);
|
| + return this.generateDartTypeName((node as ts.TypePredicateNode).type, options);
|
| case ts.SyntaxKind.TypeReference:
|
| let typeRef = <ts.TypeReferenceNode>node;
|
| - name = this.generateDartName(typeRef.typeName, insideComment) +
|
| - this.maybeGenerateTypeArguments(typeRef, insideComment);
|
| + name = this.generateDartName(
|
| + typeRef.typeName, setTypeArguments(options, typeRef.typeArguments));
|
| break;
|
| case ts.SyntaxKind.TypeLiteral:
|
| let members = (<ts.TypeLiteralNode>node).members;
|
| @@ -341,8 +420,9 @@ export class FacadeConverter extends base.TranspilerBase {
|
| // if we define a base JSMap type that is Map like but not actually
|
| // a map.
|
| name = 'dynamic';
|
| - comment = 'JSMap of <' + this.generateDartTypeName(indexSig.parameters[0].type, true) +
|
| - ',' + this.generateDartTypeName(indexSig.type, true) + '>';
|
| + comment = 'JSMap of <' +
|
| + this.generateDartTypeName(indexSig.parameters[0].type, addInsideComment(options)) +
|
| + ',' + this.generateDartTypeName(indexSig.type, addInsideComment(options)) + '>';
|
| } else {
|
| name = 'dynamic';
|
| comment = node.getText();
|
| @@ -350,8 +430,11 @@ export class FacadeConverter extends base.TranspilerBase {
|
| break;
|
| case ts.SyntaxKind.FunctionType:
|
| let callSignature = <ts.FunctionOrConstructorTypeNode>node;
|
| - let parameters = callSignature.parameters;
|
| -
|
| + // TODO(jacobr): instead of removing the expected type of the this parameter, we could add
|
| + // seperate VoidFuncBindThis and FuncBindThis typedefs to package:func/func.dart if we
|
| + // decide indicating the parameter type of the bound this is useful enough. As JavaScript
|
| + // is moving away from binding this
|
| + let parameters = base.filterThisParameter(callSignature.parameters);
|
| // Use a function signature from package:func where possible.
|
| let numOptional = numOptionalParameters(parameters);
|
| let isVoid = callSignature.type && callSignature.type.kind === ts.SyntaxKind.VoidKeyword;
|
| @@ -359,7 +442,7 @@ export class FacadeConverter extends base.TranspilerBase {
|
| numOptional <= MAX_DART_FUNC_ACTION_PARAMETERS_OPTIONAL && !hasVarArgs(parameters)) {
|
| this.emitImport('package:func/func.dart');
|
| let typeDefName = (isVoid) ? 'VoidFunc' : 'Func';
|
| - typeDefName += parameters.length;
|
| + typeDefName += parameters.length.toString();
|
| if (numOptional > 0) {
|
| typeDefName += 'Opt' + numOptional;
|
| }
|
| @@ -375,13 +458,13 @@ export class FacadeConverter extends base.TranspilerBase {
|
| } else {
|
| name += ', ';
|
| }
|
| - name += this.generateDartTypeName(parameters[i].type, insideComment);
|
| + name += this.generateDartTypeName(parameters[i].type, addInsideTypeArgument(options));
|
| }
|
| if (!isVoid) {
|
| if (!isFirst) {
|
| name += ', ';
|
| }
|
| - name += this.generateDartTypeName(callSignature.type, insideComment);
|
| + name += this.generateDartTypeName(callSignature.type, addInsideTypeArgument(options));
|
| }
|
| if (numArgs > 0) {
|
| name += '>';
|
| @@ -395,18 +478,33 @@ export class FacadeConverter extends base.TranspilerBase {
|
| break;
|
| case ts.SyntaxKind.ArrayType:
|
| name = 'List' +
|
| - '<' + this.generateDartTypeName((<ts.ArrayTypeNode>node).elementType, insideComment) +
|
| + '<' +
|
| + this.generateDartTypeName(
|
| + (<ts.ArrayTypeNode>node).elementType, addInsideTypeArgument(options)) +
|
| '>';
|
| break;
|
| case ts.SyntaxKind.NumberKeyword:
|
| name = 'num';
|
| break;
|
| + case ts.SyntaxKind.StringLiteralType:
|
| + comment = '\'' + (node as ts.StringLiteralTypeNode).text + '\'';
|
| + name = 'String';
|
| + break;
|
| case ts.SyntaxKind.StringLiteral:
|
| case ts.SyntaxKind.StringKeyword:
|
| name = 'String';
|
| break;
|
| + case ts.SyntaxKind.NullKeyword:
|
| + name = 'Null';
|
| + break;
|
| + case ts.SyntaxKind.UndefinedKeyword:
|
| + // TODO(jacobr): unclear if this should be Null or dynamic.
|
| + name = 'dynamic';
|
| + break;
|
| case ts.SyntaxKind.VoidKeyword:
|
| - name = 'void';
|
| + // void cannot be used as a type argument in Dart so we fall back to Object if void is
|
| + // unfortunately specified as a type argument.
|
| + name = options.insideTypeArgument ? 'Null' : 'void';
|
| break;
|
| case ts.SyntaxKind.BooleanKeyword:
|
| name = 'bool';
|
| @@ -414,15 +512,19 @@ export class FacadeConverter extends base.TranspilerBase {
|
| case ts.SyntaxKind.AnyKeyword:
|
| name = 'dynamic';
|
| break;
|
| + case ts.SyntaxKind.ParenthesizedType:
|
| + return this.generateDartTypeName((node as ts.ParenthesizedTypeNode).type, options);
|
| + case ts.SyntaxKind.ThisType:
|
| + return this.generateDartName(base.getEnclosingClass(node).name, options);
|
| default:
|
| - this.reportError(node, 'Unexpected TypeNode kind');
|
| + this.reportError(node, 'Unexpected TypeNode kind: ' + node.kind);
|
| }
|
| if (name == null) {
|
| - name = 'XXX NULLNAME';
|
| + name = 'ERROR_NULL_NAME';
|
| }
|
|
|
| name = name.trim();
|
| - return base.formatType(name, comment, insideComment);
|
| + return base.formatType(name, comment, options);
|
| }
|
|
|
| visitTypeName(typeName: ts.EntityName) {
|
| @@ -438,7 +540,9 @@ export class FacadeConverter extends base.TranspilerBase {
|
| return;
|
| }
|
|
|
| - let custom = this.lookupCustomDartTypeName(<ts.Identifier>typeName, this.insideCodeComment);
|
| + let custom = this.lookupCustomDartTypeName(
|
| + <ts.Identifier>typeName,
|
| + {insideComment: this.insideCodeComment, insideTypeArgument: this.insideTypeArgument});
|
| if (custom) {
|
| if (custom.comment) {
|
| this.emitType(custom.name, custom.comment);
|
| @@ -465,12 +569,15 @@ export class FacadeConverter extends base.TranspilerBase {
|
| return decl;
|
| }
|
|
|
| - generateDartName(identifier: ts.EntityName, insideComment: boolean): string {
|
| - let ret = this.lookupCustomDartTypeName(identifier, insideComment);
|
| - if (ret) return base.formatType(ret.name, ret.comment, insideComment);
|
| - // TODO(jacobr): handle library import prefixes better. This generally works
|
| - // but is somewhat fragile.
|
| - return base.ident(identifier);
|
| + generateDartName(identifier: ts.EntityName, options: TypeDisplayOptions): string {
|
| + let ret = this.lookupCustomDartTypeName(identifier, options);
|
| +
|
| + if (ret) {
|
| + return base.formatType(ret.name, ret.comment, options);
|
| + }
|
| + // TODO(jacobr): handle library import prefixes more robustly. This generally works
|
| + // but is fragile.
|
| + return this.maybeAddTypeArguments(base.ident(identifier), options);
|
| }
|
|
|
| /**
|
| @@ -492,22 +599,41 @@ export class FacadeConverter extends base.TranspilerBase {
|
| return declaration;
|
| }
|
|
|
| + maybeAddTypeArguments(name: string, options: TypeDisplayOptions): string {
|
| + if (options.typeArguments) {
|
| + name +=
|
| + '<' + this.generateTypeList(options.typeArguments, setTypeArguments(options, null)) + '>';
|
| + }
|
| + return name;
|
| + }
|
| +
|
| /**
|
| * Returns a custom Dart type name or null if the type isn't a custom Dart
|
| * type.
|
| */
|
| - lookupCustomDartTypeName(identifier: ts.EntityName, insideComment: boolean):
|
| + lookupCustomDartTypeName(identifier: ts.EntityName, options?: TypeDisplayOptions):
|
| {name?: string, comment?: string, keep?: boolean} {
|
| + if (!options) {
|
| + options = {
|
| + insideComment: this.insideCodeComment,
|
| + insideTypeArgument: this.insideTypeArgument
|
| + };
|
| + }
|
| let ident = base.ident(identifier);
|
| - let symbol: ts.Symbol;
|
| - if (!this.tc) return null;
|
| -
|
| - symbol = this.tc.getSymbolAtLocation(identifier);
|
| + let symbol: ts.Symbol = this.tc.getSymbolAtLocation(identifier);
|
| let declaration = this.getSymbolDeclaration(symbol, identifier);
|
| if (symbol && symbol.flags & ts.SymbolFlags.TypeParameter) {
|
| let kind = declaration.parent.kind;
|
| + if (options.resolvedTypeArguments &&
|
| + Object.hasOwnProperty.call(options.resolvedTypeArguments, ident)) {
|
| + return {
|
| + name: this.generateDartTypeName(
|
| + options.resolvedTypeArguments[ident], removeResolvedTypeArguments(options))
|
| + };
|
| + }
|
| // Only kinds of TypeParameters supported by Dart.
|
| - if (kind !== ts.SyntaxKind.ClassDeclaration && kind !== ts.SyntaxKind.InterfaceDeclaration) {
|
| + if (kind !== ts.SyntaxKind.ClassDeclaration && kind !== ts.SyntaxKind.InterfaceDeclaration &&
|
| + kind !== ts.SyntaxKind.TypeAliasDeclaration) {
|
| return {name: 'dynamic', comment: ident};
|
| }
|
| }
|
| @@ -527,30 +653,40 @@ export class FacadeConverter extends base.TranspilerBase {
|
| this.emitImport(DART_LIBRARIES_FOR_BROWSER_TYPES[fileAndName.qname]);
|
| }
|
| if (fileSubs.hasOwnProperty(fileAndName.qname)) {
|
| - return {name: fileSubs[fileAndName.qname]};
|
| + return {name: this.maybeAddTypeArguments(fileSubs[fileAndName.qname], options)};
|
| }
|
| if (dartBrowserType) {
|
| // Not a rename but has a dart core libraries definition.
|
| - return {name: fileAndName.qname};
|
| + return {name: this.maybeAddTypeArguments(fileAndName.qname, options)};
|
| }
|
| }
|
| }
|
| }
|
| if (symbol) {
|
| + this.emitImportForSourceFile(declaration.getSourceFile(), identifier.getSourceFile());
|
| if (symbol.flags & ts.SymbolFlags.Enum) {
|
| // We can't treat JavaScript enums as Dart enums in this case.
|
| - return {name: 'num', comment: ident};
|
| + return {name: 'num', comment: 'enum ' + ident};
|
| }
|
| // TODO(jacobr): we could choose to only support type alais declarations
|
| // for JS interop but it seems handling type alaises is generally helpful
|
| // without much risk of generating confusing Dart code.
|
| if (declaration.kind === ts.SyntaxKind.TypeAliasDeclaration) {
|
| let alias = <ts.TypeAliasDeclaration>declaration;
|
| - if (alias.typeParameters) {
|
| - // We can handle this case but currently do not.
|
| - this.reportError(declaration, 'Type parameters for type alaises are not supported');
|
| + if (base.supportedAliasType(alias)) {
|
| + return {
|
| + name: this.maybeAddTypeArguments(
|
| + this.nameRewriter.lookupName(<base.NamedDeclaration>declaration, identifier),
|
| + options),
|
| + keep: true
|
| + };
|
| }
|
| - return {name: this.generateDartTypeName(alias.type, insideComment)};
|
| + // Type alias we cannot support in Dart.
|
| + // Substitute the alias type and parameters directly in the destination.
|
| + return {
|
| + name: this.generateDartTypeName(
|
| + alias.type, resolveTypeArguments(options, alias.typeParameters))
|
| + };
|
| }
|
|
|
| let kind = declaration.kind;
|
| @@ -566,7 +702,7 @@ export class FacadeConverter extends base.TranspilerBase {
|
| // case if we want to get the most from Dart type checking.
|
| return {name: 'Function', comment: name};
|
| }
|
| - return {name: name, keep: true};
|
| + return {name: this.maybeAddTypeArguments(name, options), keep: true};
|
| }
|
| }
|
| return null;
|
| @@ -577,7 +713,7 @@ export class FacadeConverter extends base.TranspilerBase {
|
| * This method works around the lack of Dart support for union types
|
| * generating a valid Dart type that satisfies all the types passed in.
|
| */
|
| - toSimpleDartType(types: Array<ts.TypeNode>) {
|
| + toSimpleDartType(types: Array<ts.TypeNode>): ts.TypeNode {
|
| // We use MergeType to ensure that we have already deduped types that are
|
| // equivalent even if they aren't obviously identical.
|
| // MergedType will also follow typed aliases, etc which allows us to avoid
|
| @@ -585,6 +721,9 @@ export class FacadeConverter extends base.TranspilerBase {
|
| let mergedType = new MergedType(this);
|
| types.forEach((type) => { mergedType.merge(type); });
|
| let merged = mergedType.toTypeNode();
|
| +
|
| + if (merged == null) return null;
|
| +
|
| if (merged.kind === ts.SyntaxKind.UnionType) {
|
| // For union types find a Dart type that satisfies all the types.
|
| types = (<ts.UnionTypeNode>merged).types;
|
| @@ -596,48 +735,56 @@ export class FacadeConverter extends base.TranspilerBase {
|
| let common: ts.TypeNode = types[0];
|
| for (let i = 1; i < types.length && common != null; ++i) {
|
| let type = types[i];
|
| - if (common !== type) {
|
| - if (base.isCallableType(common, this.tc) && base.isCallableType(type, this.tc)) {
|
| - // Fall back to a generic Function type if both types are Function.
|
| - let fn = <ts.FunctionOrConstructorTypeNode>ts.createNode(ts.SyntaxKind.FunctionType);
|
| - fn.parameters = <ts.NodeArray<ts.ParameterDeclaration>>[];
|
| - let parameter = <ts.ParameterDeclaration>ts.createNode(ts.SyntaxKind.Parameter);
|
| - parameter.dotDotDotToken = ts.createNode(ts.SyntaxKind.DotDotDotToken);
|
| - let name = <ts.Identifier>ts.createNode(ts.SyntaxKind.Identifier);
|
| - name.text = 'args';
|
| - fn.parameters.push(parameter);
|
| - common = fn;
|
| - } else {
|
| - switch (type.kind) {
|
| - case ts.SyntaxKind.ArrayType:
|
| - if (common.kind !== ts.SyntaxKind.ArrayType) {
|
| - return null;
|
| - }
|
| - let array = <ts.ArrayTypeNode>ts.createNode(ts.SyntaxKind.ArrayType);
|
| - array.elementType = this.toSimpleDartType([
|
| - (common as ts.ArrayTypeNode).elementType, (type as ts.ArrayTypeNode).elementType
|
| - ]);
|
| - common = array;
|
| - break;
|
| - // case ts.SyntaxKind
|
| - case ts.SyntaxKind.TypeReference:
|
| - if (common.kind !== ts.SyntaxKind.TypeReference) {
|
| - return null;
|
| - }
|
| - common = this.commonSupertype(common, type);
|
| - break;
|
| -
|
| - default:
|
| - return null;
|
| - }
|
| - }
|
| - }
|
| + common = this.findCommonType(type, common);
|
| }
|
| return common;
|
| }
|
| return merged;
|
| }
|
|
|
| + findCommonType(type: ts.TypeNode, common: ts.TypeNode): ts.TypeNode {
|
| + if (common === type) return common;
|
| +
|
| + // If both types generate the exact same Dart type name without comments then
|
| + // there is no need to do anything. The types
|
| + if (this.generateDartTypeName(common, {hideComment: true}) ===
|
| + this.generateDartTypeName(type, {hideComment: true})) {
|
| + return common;
|
| + }
|
| +
|
| +
|
| + if (type.kind === ts.SyntaxKind.ArrayType) {
|
| + if (common.kind !== ts.SyntaxKind.ArrayType) {
|
| + return null;
|
| + }
|
| + let array = <ts.ArrayTypeNode>ts.createNode(ts.SyntaxKind.ArrayType);
|
| + array.elementType = this.toSimpleDartType(
|
| + [(common as ts.ArrayTypeNode).elementType, (type as ts.ArrayTypeNode).elementType]);
|
| + return array;
|
| + }
|
| + if (type.kind === ts.SyntaxKind.TypeReference && common.kind === ts.SyntaxKind.TypeReference) {
|
| + let candidate = this.commonSupertype(common, type);
|
| + if (candidate !== null) {
|
| + return candidate;
|
| + }
|
| + }
|
| +
|
| + if (base.isCallableType(common, this.tc) && base.isCallableType(type, this.tc)) {
|
| + // Fall back to a generic Function type if both types are Function.
|
| + // TODO(jacobr): this is a problematic fallback.
|
| + let fn = <ts.FunctionOrConstructorTypeNode>ts.createNode(ts.SyntaxKind.FunctionType);
|
| + fn.parameters = <ts.NodeArray<ts.ParameterDeclaration>>[];
|
| + let parameter = <ts.ParameterDeclaration>ts.createNode(ts.SyntaxKind.Parameter);
|
| + parameter.dotDotDotToken = ts.createNode(ts.SyntaxKind.DotDotDotToken);
|
| + let name = <ts.Identifier>ts.createNode(ts.SyntaxKind.Identifier);
|
| + name.text = 'args';
|
| + fn.parameters.push(parameter);
|
| + return fn;
|
| + }
|
| + // No common type found.
|
| + return null;
|
| + }
|
| +
|
| toTypeNode(type: ts.Type): ts.TypeNode {
|
| if (!type) return null;
|
| let symbol = type.getSymbol();
|
| @@ -685,12 +832,22 @@ export class FacadeConverter extends base.TranspilerBase {
|
|
|
| commonSupertype(nodeA: ts.TypeNode, nodeB: ts.TypeNode): ts.TypeNode {
|
| if (nodeA == null || nodeB == null) return null;
|
| + if (nodeA.kind === ts.SyntaxKind.TypeReference && nodeB.kind === ts.SyntaxKind.TypeReference) {
|
| + // Handle the trivial case where the types are identical except for type arguments.
|
| + // We could do a better job and actually attempt to merge type arguments.
|
| + let refA = nodeA as ts.TypeReferenceNode;
|
| + let refB = nodeB as ts.TypeReferenceNode;
|
| + if (base.ident(refA.typeName) === base.ident(refB.typeName)) {
|
| + let merge = <ts.TypeReferenceNode>ts.createNode(ts.SyntaxKind.TypeReference);
|
| + merge.typeName = refA.typeName;
|
| + return merge;
|
| + }
|
| + }
|
| return this.toTypeNode(this.getCommonSupertype(
|
| this.tc.getTypeAtLocation(nodeA), this.tc.getTypeAtLocation(nodeB)));
|
| }
|
|
|
| getCommonSupertype(a: ts.Type, b: ts.Type): ts.Type {
|
| - if (a === b) return a;
|
| // This logic was probably a mistake. It adds a lot of complexity and we can
|
| // do better performing these calculations in the Dart analyzer based
|
| // directly on the union types specified in comments.
|
|
|