| Index: lib/facade_converter.ts
|
| diff --git a/lib/facade_converter.ts b/lib/facade_converter.ts
|
| index 76346c4e9c4df4e5ee823d0c03bee7a18fc9791f..c43dda92e5216def334104a373d7d09e45d13ab0 100644
|
| --- a/lib/facade_converter.ts
|
| +++ b/lib/facade_converter.ts
|
| @@ -1,40 +1,197 @@
|
| -import * as base from './base';
|
| import * as ts from 'typescript';
|
| +
|
| +import * as base from './base';
|
| +import {Set} 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';
|
|
|
| type CallHandler = (c: ts.CallExpression, context: ts.Expression) => void;
|
| type PropertyHandler = (c: ts.PropertyAccessExpression) => void;
|
| -type Set = {
|
| - [s: string]: boolean
|
| -};
|
|
|
| const FACADE_DEBUG = false;
|
| -
|
| const FACADE_NODE_MODULES_PREFIX = /^(\.\.\/)*node_modules\//;
|
|
|
| -function merge(...args: {[key: string]: any}[]): {[key: string]: any} {
|
| - let returnObject: {[key: string]: any} = {};
|
| - for (let arg of args) {
|
| - for (let key of Object.getOwnPropertyNames(arg)) {
|
| - returnObject[key] = arg[key];
|
| +// These constants must be kept in sync with package:func/func.dart which
|
| +// provides a cannonical set of typedefs defining commonly used function types
|
| +// to simplify specifying function types in Dart.
|
| +const MAX_DART_FUNC_ACTION_PARAMETERS = 4;
|
| +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 function fixupIdentifierName(text: string): string {
|
| + return (FacadeConverter.DART_RESERVED_WORDS.indexOf(text) !== -1 ||
|
| + FacadeConverter.DART_OTHER_KEYWORDS.indexOf(text) !== -1 || text[0] === '_') ?
|
| + DART_RESERVED_NAME_PREFIX + text :
|
| + text;
|
| +}
|
| +
|
| +function numOptionalParameters(parameters: ts.NodeArray<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 {
|
| + for (let i = 0; i < parameters.length; ++i) {
|
| + if (parameters[i].dotDotDotToken) return true;
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +/**
|
| + * Generates the JavaScript expression required to reference the node
|
| + * from the global context. InterfaceDeclarations do not technically correspond
|
| + * to actual JavaScript objects but we still generate a reference path for them
|
| + * so that we have a guaranteed unique name.
|
| + *
|
| + * Example JS path:
|
| + * module m1 {
|
| + * module m2 {
|
| + * class foo { }
|
| + * }
|
| + * }
|
| + * Path: m1.m2.foo
|
| + */
|
| +function fullJsPath(node: base.NamedDeclaration): string {
|
| + let parts: Array<string> = [base.ident(node.name)];
|
| + let p: ts.Node = node.parent;
|
| + while (p != null) {
|
| + let kind = p.kind;
|
| + if (kind === ts.SyntaxKind.ModuleDeclaration || kind === ts.SyntaxKind.InterfaceDeclaration ||
|
| + kind === ts.SyntaxKind.ClassDeclaration) {
|
| + parts.unshift(base.ident((<base.NamedDeclaration>p).name));
|
| }
|
| + p = p.parent;
|
| + }
|
| + return parts.join('.');
|
| +}
|
| +
|
| +class DartNameRecord {
|
| + name: string;
|
| + constructor(private node: ts.Node, name: string, private library: DartLibrary) {
|
| + this.name = name;
|
| }
|
| - return returnObject;
|
| }
|
|
|
| +export class DartLibrary {
|
| + constructor(private fileName: string) { this.usedNames = {}; }
|
| +
|
| + /**
|
| + * @returns {boolean} whether the name was added.
|
| + */
|
| + addName(name: string): boolean {
|
| + if (Object.prototype.hasOwnProperty.call(this.usedNames, name)) {
|
| + return false;
|
| + }
|
| + this.usedNames[name] = true;
|
| + return true;
|
| + }
|
| +
|
| + private usedNames: Set;
|
| +}
|
| +
|
| +export class NameRewriter {
|
| + private dartTypes: ts.Map<DartNameRecord> = {};
|
| + // TODO(jacobr): use libraries to track what library imports need to be
|
| + // emitted. Complex cases such as ts.SyntaxKind.TypeReference
|
| + // where emitting the type could require emitting imports to libraries not
|
| + // specified in the imports listed in TypeScript. Additionally, d.ts files
|
| + // can contain multiple modules and for readability we may want an option
|
| + // to emit each of those modules as a separate Dart library. That would also
|
| + // require tracking what external libraries are referenced.
|
| + private libraries: ts.Map<DartLibrary> = {};
|
| +
|
| + private computeName(node: base.NamedDeclaration): DartNameRecord {
|
| + let fullPath = fullJsPath(node);
|
| + if (Object.prototype.hasOwnProperty.call(this.dartTypes, fullPath)) {
|
| + return this.dartTypes[fullPath];
|
| + }
|
| + let sourceFile = <ts.SourceFile>base.getAncestor(node, ts.SyntaxKind.SourceFile);
|
| + let fileName = sourceFile.fileName;
|
| + let library: DartLibrary;
|
| + if (Object.prototype.hasOwnProperty.call(this.libraries, fileName)) {
|
| + library = this.libraries[fileName];
|
| + } else {
|
| + library = new DartLibrary(fileName);
|
| + this.libraries[fileName] = library;
|
| + }
|
| + let parts = fullPath.split('.');
|
| + for (let i = parts.length - 1; i >= 0; i--) {
|
| + // Find a unique name by including more of the module hierarchy in the
|
| + // name. This is an arbitrary but hopefully unsurprising scheme to
|
| + // generate unique names. There may be classes or members with conflicting
|
| + // names due to a single d.ts file containing multiple modules.
|
| + // TODO(jacobr): we should suppress this behavior outside of JS Interop
|
| + // mode and instead generate a compile error if there are conflicting
|
| + // names.
|
| + let candidateName = fixupIdentifierName(parts.slice(i).join('_'));
|
| + if (library.addName(candidateName)) {
|
| + // Able to add name to library.
|
| + let ret = new DartNameRecord(node, candidateName, library);
|
| + this.dartTypes[fullPath] = ret;
|
| + return ret;
|
| + }
|
| + }
|
| +
|
| + // Usually the module name prefixes should be sufficient to disambiguate
|
| + // names but sometimes we need to add a numeric prefix as well to
|
| + // disambiguate. We could alternately append the full module prefix as well
|
| + // to make the name choice completely unsurprising albeit even uglier.
|
| + // This case should be very rarely hit.
|
| + let i = 2;
|
| + while (true) {
|
| + let candidateName = parts[parts.length - 1] + i;
|
| + if (library.addName(candidateName)) {
|
| + // Able to add name to library.
|
| + let ret = new DartNameRecord(node, candidateName, library);
|
| + this.dartTypes[fullPath] = ret;
|
| + return ret;
|
| + }
|
| + i++;
|
| + }
|
| + }
|
| +
|
| + lookupName(node: base.NamedDeclaration, context: ts.Node) { return this.computeName(node).name; }
|
| +}
|
|
|
| export class FacadeConverter extends base.TranspilerBase {
|
| private tc: ts.TypeChecker;
|
| - private candidateProperties: {[propertyName: string]: boolean} = {};
|
| + // For the Dart keyword list see
|
| + // https://www.dartlang.org/docs/dart-up-and-running/ch02.html#keywords
|
| + static DART_RESERVED_WORDS =
|
| + ('assert break case catch class const continue default do else enum extends false final ' +
|
| + 'finally for if in is new null rethrow return super switch this throw true try let void ' +
|
| + 'while with')
|
| + .split(/ /);
|
| +
|
| + // These are the built-in and limited keywords.
|
| + static DART_OTHER_KEYWORDS =
|
| + ('abstract as async await deferred dynamic export external factory get implements import ' +
|
| + 'library operator part set static sync typedef yield call')
|
| + .split(/ /);
|
| +
|
| private candidateTypes: {[typeName: string]: boolean} = {};
|
| private typingsRootRegex: RegExp;
|
| private genericMethodDeclDepth = 0;
|
|
|
| - constructor(transpiler: Transpiler, typingsRoot = '') {
|
| + constructor(
|
| + transpiler: Transpiler, typingsRoot = '', private nameRewriter: NameRewriter,
|
| + private useHtml: boolean) {
|
| super(transpiler);
|
| - this.extractPropertyNames(this.callHandlers, this.candidateProperties);
|
| - this.extractPropertyNames(this.propertyHandlers, this.candidateProperties);
|
| - this.extractPropertyNames(this.TS_TO_DART_TYPENAMES, this.candidateTypes);
|
| + 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.typingsRootRegex = new RegExp('^' + typingsRoot.replace('.', '\\.'));
|
| }
|
| @@ -42,6 +199,9 @@ export class FacadeConverter extends base.TranspilerBase {
|
| private extractPropertyNames(m: ts.Map<ts.Map<any>>, candidates: {[k: string]: boolean}) {
|
| for (let fileName of Object.keys(m)) {
|
| const file = m[fileName];
|
| + if (file === undefined) {
|
| + return;
|
| + }
|
| Object.keys(file)
|
| .map((propName) => propName.substring(propName.lastIndexOf('.') + 1))
|
| .forEach((propName) => candidates[propName] = true);
|
| @@ -50,63 +210,6 @@ export class FacadeConverter extends base.TranspilerBase {
|
|
|
| setTypeChecker(tc: ts.TypeChecker) { this.tc = tc; }
|
|
|
| - maybeHandleCall(c: ts.CallExpression): boolean {
|
| - if (!this.tc) return false;
|
| - let {context, symbol} = this.getCallInformation(c);
|
| - if (!symbol) {
|
| - // getCallInformation returns a symbol if we understand this call.
|
| - return false;
|
| - }
|
| - let handler = this.getHandler(c, symbol, this.callHandlers);
|
| - return handler && !handler(c, context);
|
| - }
|
| -
|
| - handlePropertyAccess(pa: ts.PropertyAccessExpression): boolean {
|
| - if (!this.tc) return;
|
| - let ident = pa.name.text;
|
| - if (!this.candidateProperties.hasOwnProperty(ident)) return false;
|
| - let symbol = this.tc.getSymbolAtLocation(pa.name);
|
| - if (!symbol) {
|
| - this.reportMissingType(pa, ident);
|
| - return false;
|
| - }
|
| -
|
| - let handler = this.getHandler(pa, symbol, this.propertyHandlers);
|
| - return handler && !handler(pa);
|
| - }
|
| -
|
| - /**
|
| - * Searches for type references that require extra imports and emits the imports as necessary.
|
| - */
|
| - emitExtraImports(sourceFile: ts.SourceFile) {
|
| - let libraries = <ts.Map<string>>{
|
| - 'XMLHttpRequest': 'dart:html',
|
| - 'KeyboardEvent': 'dart:html',
|
| - 'Uint8Array': 'dart:typed_arrays',
|
| - 'ArrayBuffer': 'dart:typed_arrays',
|
| - 'Promise': 'dart:async',
|
| - };
|
| - let emitted: Set = {};
|
| - this.emitImports(sourceFile, libraries, emitted, sourceFile);
|
| - }
|
| -
|
| - private emitImports(
|
| - n: ts.Node, libraries: ts.Map<string>, emitted: Set, sourceFile: ts.SourceFile): void {
|
| - if (n.kind === ts.SyntaxKind.TypeReference) {
|
| - let type = base.ident((<ts.TypeReferenceNode>n).typeName);
|
| - if (libraries.hasOwnProperty(type)) {
|
| - let toEmit = libraries[type];
|
| - if (!emitted[toEmit]) {
|
| - this.emit(`import "${toEmit}";`);
|
| - emitted[toEmit] = true;
|
| - }
|
| - }
|
| - }
|
| -
|
| - n.getChildren(sourceFile)
|
| - .forEach((child: ts.Node) => this.emitImports(child, libraries, emitted, sourceFile));
|
| - }
|
| -
|
| pushTypeParameterNames(n: ts.FunctionLikeDeclaration) {
|
| if (!n.typeParameters) return;
|
| this.genericMethodDeclDepth++;
|
| @@ -137,11 +240,12 @@ export class FacadeConverter extends base.TranspilerBase {
|
| }
|
|
|
| /**
|
| - * The Dart Development Compiler (DDC) has a syntax extension that uses comments to emulate
|
| - * generic methods in Dart. ts2dart has to hack around this and keep track of which type names
|
| - * in the current scope are actually DDC type parameters and need to be emitted in comments.
|
| + * The Dart analyzer has a syntax extension that uses comments to emulate
|
| + * generic methods in Dart. We work around this and keep track of which type
|
| + * names in the current scope need to be emitted in comments.
|
| *
|
| - * TODO(martinprobst): Remove this once the DDC hack has made it into Dart proper.
|
| + * TODO(jacobr): Remove this once all Dart implementations support generic
|
| + * methods.
|
| */
|
| private isGenericMethodTypeParameterName(name: ts.EntityName): boolean {
|
| // Avoid checking this unless needed.
|
| @@ -158,6 +262,169 @@ 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 {
|
| + let that = this;
|
| + return types.map((type) => { return that.generateDartTypeName(type, insideComment); })
|
| + .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, 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) + ' */';
|
| + 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);
|
| + 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 += '>';
|
| + comment = 'Tuple<' + this.generateTypeList(tuple.elementTypes, insideComment) + '>';
|
| + break;
|
| + 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
|
| + // union when in JS Interop mode while otherwise we expect that union
|
| + // types will not be used extensively.
|
| + let simpleType = this.toSimpleDartType(union.types);
|
| + if (simpleType) {
|
| + name = this.generateDartTypeName(simpleType, insideComment);
|
| + } else {
|
| + name = 'dynamic';
|
| + }
|
| + let types = union.types;
|
| + comment = this.generateTypeList(types, true, '|');
|
| + break;
|
| + case ts.SyntaxKind.TypePredicate:
|
| + return this.generateDartTypeName((node as ts.TypePredicateNode).type, insideComment);
|
| + case ts.SyntaxKind.TypeReference:
|
| + let typeRef = <ts.TypeReferenceNode>node;
|
| + name = this.generateDartName(typeRef.typeName, insideComment) +
|
| + this.maybeGenerateTypeArguments(typeRef, insideComment);
|
| + break;
|
| + case ts.SyntaxKind.TypeLiteral:
|
| + let members = (<ts.TypeLiteralNode>node).members;
|
| + if (members.length === 1 && members[0].kind === ts.SyntaxKind.IndexSignature) {
|
| + let indexSig = <ts.IndexSignatureDeclaration>(members[0]);
|
| + if (indexSig.parameters.length > 1) {
|
| + this.reportError(indexSig, 'Expected an index signature to have a single parameter');
|
| + }
|
| + // Unfortunately for JS interop, we cannot treat JS Objects as Dart
|
| + // Map objects. We could treat them as JSMap<indexSig.type>
|
| + // 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) + '>';
|
| + } else {
|
| + name = 'dynamic';
|
| + comment = node.getText();
|
| + }
|
| + break;
|
| + case ts.SyntaxKind.FunctionType:
|
| + let callSignature = <ts.FunctionOrConstructorTypeNode>node;
|
| + let parameters = 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;
|
| + if (parameters.length <= MAX_DART_FUNC_ACTION_PARAMETERS &&
|
| + numOptional <= MAX_DART_FUNC_ACTION_PARAMETERS_OPTIONAL && !hasVarArgs(parameters)) {
|
| + this.emitImport('package:func/func.dart');
|
| + let typeDefName = (isVoid) ? 'VoidFunc' : 'Func';
|
| + typeDefName += parameters.length;
|
| + if (numOptional > 0) {
|
| + typeDefName += 'Opt' + numOptional;
|
| + }
|
| + name = typeDefName;
|
| + let numArgs = parameters.length + (isVoid ? 0 : 1);
|
| + if (numArgs > 0) {
|
| + name += '<';
|
| + }
|
| + let isFirst = true;
|
| + for (let i = 0; i < parameters.length; ++i) {
|
| + if (isFirst) {
|
| + isFirst = false;
|
| + } else {
|
| + name += ', ';
|
| + }
|
| + name += this.generateDartTypeName(parameters[i].type, insideComment);
|
| + }
|
| + if (!isVoid) {
|
| + if (!isFirst) {
|
| + name += ', ';
|
| + }
|
| + name += this.generateDartTypeName(callSignature.type, insideComment);
|
| + }
|
| + if (numArgs > 0) {
|
| + name += '>';
|
| + }
|
| + } else {
|
| + name = 'Function';
|
| + if (node.getSourceFile()) {
|
| + comment = node.getText();
|
| + }
|
| + }
|
| + break;
|
| + case ts.SyntaxKind.ArrayType:
|
| + name = 'List' +
|
| + '<' + this.generateDartTypeName((<ts.ArrayTypeNode>node).elementType, insideComment) +
|
| + '>';
|
| + break;
|
| + case ts.SyntaxKind.NumberKeyword:
|
| + name = 'num';
|
| + break;
|
| + case ts.SyntaxKind.StringLiteral:
|
| + case ts.SyntaxKind.StringKeyword:
|
| + name = 'String';
|
| + break;
|
| + case ts.SyntaxKind.VoidKeyword:
|
| + name = 'void';
|
| + break;
|
| + case ts.SyntaxKind.BooleanKeyword:
|
| + name = 'bool';
|
| + break;
|
| + case ts.SyntaxKind.AnyKeyword:
|
| + name = 'dynamic';
|
| + break;
|
| + default:
|
| + this.reportError(node, 'Unexpected TypeNode kind');
|
| + }
|
| + if (name == null) {
|
| + name = 'XXX NULLNAME';
|
| + }
|
| +
|
| + name = name.trim();
|
| + return base.formatType(name, comment, insideComment);
|
| + }
|
| +
|
| visitTypeName(typeName: ts.EntityName) {
|
| if (typeName.kind !== ts.SyntaxKind.Identifier) {
|
| this.visit(typeName);
|
| @@ -167,104 +434,292 @@ export class FacadeConverter extends base.TranspilerBase {
|
| if (this.isGenericMethodTypeParameterName(typeName)) {
|
| // DDC generic methods hack - all names that are type parameters to generic methods have to be
|
| // emitted in comments.
|
| - this.emit('dynamic/*=');
|
| - this.emit(ident);
|
| - this.emit('*/');
|
| + this.emitType('dynamic', ident);
|
| return;
|
| }
|
|
|
| - if (this.candidateTypes.hasOwnProperty(ident) && this.tc) {
|
| - let symbol = this.tc.getSymbolAtLocation(typeName);
|
| - if (!symbol) {
|
| - this.reportMissingType(typeName, ident);
|
| - return;
|
| - }
|
| - let fileAndName = this.getFileAndName(typeName, symbol);
|
| - if (fileAndName) {
|
| - let fileSubs = this.TS_TO_DART_TYPENAMES[fileAndName.fileName];
|
| - if (fileSubs && fileSubs.hasOwnProperty(fileAndName.qname)) {
|
| - this.emit(fileSubs[fileAndName.qname]);
|
| - return;
|
| - }
|
| + let custom = this.lookupCustomDartTypeName(<ts.Identifier>typeName, this.insideCodeComment);
|
| + if (custom) {
|
| + if (custom.comment) {
|
| + this.emitType(custom.name, custom.comment);
|
| + } else {
|
| + this.emit(custom.name);
|
| }
|
| + } else {
|
| + this.visit(typeName);
|
| }
|
| - this.emit(ident);
|
| }
|
|
|
| - shouldEmitNew(c: ts.CallExpression): boolean {
|
| - if (!this.tc) return true;
|
| -
|
| - let ci = this.getCallInformation(c);
|
| - let symbol = ci.symbol;
|
| - // getCallInformation returns a symbol if we understand this call.
|
| - if (!symbol) return true;
|
| + getSymbolDeclaration(symbol: ts.Symbol, n?: ts.Node): ts.Declaration {
|
| + if (!symbol) return null;
|
| + let decl = symbol.valueDeclaration;
|
| + if (!decl) {
|
| + // In the case of a pure declaration with no assignment, there is no value declared.
|
| + // Just grab the first declaration, hoping it is declared once.
|
| + if (!symbol.declarations || symbol.declarations.length === 0) {
|
| + this.reportError(n, 'no declarations for symbol ' + symbol.name);
|
| + return;
|
| + }
|
| + decl = symbol.declarations[0];
|
| + }
|
| + return decl;
|
| + }
|
|
|
| - let loc = this.getFileAndName(c, symbol);
|
| - if (!loc) return true;
|
| - let {fileName, qname} = loc;
|
| - let fileSubs = this.callHandlerReplaceNew[fileName];
|
| - if (!fileSubs) return true;
|
| - return !fileSubs[qname];
|
| + 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);
|
| }
|
|
|
| - private getCallInformation(c: ts.CallExpression): {context?: ts.Expression, symbol?: ts.Symbol} {
|
| + /**
|
| + * Returns null if declaration cannot be found or is not valid in Dart.
|
| + */
|
| + getDeclaration(identifier: ts.EntityName): ts.Declaration {
|
| let symbol: ts.Symbol;
|
| - let context: ts.Expression;
|
| - let ident: string;
|
| - let expr = c.expression;
|
| + if (!this.tc) return null;
|
| +
|
| + symbol = this.tc.getSymbolAtLocation(identifier);
|
| + let declaration = this.getSymbolDeclaration(symbol, identifier);
|
| + if (symbol && symbol.flags & ts.SymbolFlags.TypeParameter) {
|
| + let kind = declaration.parent.kind;
|
| + // Only kinds of TypeParameters supported by Dart.
|
| + if (kind !== ts.SyntaxKind.ClassDeclaration && kind !== ts.SyntaxKind.InterfaceDeclaration) {
|
| + return null;
|
| + }
|
| + }
|
| + return declaration;
|
| + }
|
|
|
| - if (expr.kind === ts.SyntaxKind.Identifier) {
|
| - // Function call.
|
| - ident = base.ident(expr);
|
| - if (!this.candidateProperties.hasOwnProperty(ident)) return {};
|
| - symbol = this.tc.getSymbolAtLocation(expr);
|
| - if (FACADE_DEBUG) console.error('s:', symbol);
|
| + /**
|
| + * Returns a custom Dart type name or null if the type isn't a custom Dart
|
| + * type.
|
| + */
|
| + lookupCustomDartTypeName(identifier: ts.EntityName, insideComment: boolean):
|
| + {name?: string, comment?: string, keep?: boolean} {
|
| + let ident = base.ident(identifier);
|
| + let symbol: ts.Symbol;
|
| + if (!this.tc) return null;
|
| +
|
| + symbol = this.tc.getSymbolAtLocation(identifier);
|
| + let declaration = this.getSymbolDeclaration(symbol, identifier);
|
| + if (symbol && symbol.flags & ts.SymbolFlags.TypeParameter) {
|
| + let kind = declaration.parent.kind;
|
| + // Only kinds of TypeParameters supported by Dart.
|
| + if (kind !== ts.SyntaxKind.ClassDeclaration && kind !== ts.SyntaxKind.InterfaceDeclaration) {
|
| + return {name: 'dynamic', comment: ident};
|
| + }
|
| + }
|
|
|
| + if (this.candidateTypes.hasOwnProperty(ident)) {
|
| if (!symbol) {
|
| - this.reportMissingType(c, ident);
|
| - return {};
|
| + return null;
|
| }
|
|
|
| - context = null;
|
| - } else if (expr.kind === ts.SyntaxKind.PropertyAccessExpression) {
|
| - // Method call.
|
| - let pa = <ts.PropertyAccessExpression>expr;
|
| - ident = base.ident(pa.name);
|
| - if (!this.candidateProperties.hasOwnProperty(ident)) return {};
|
| + let fileAndName = this.getFileAndName(identifier, symbol);
|
|
|
| - symbol = this.tc.getSymbolAtLocation(pa);
|
| - if (FACADE_DEBUG) console.error('s:', symbol);
|
| + if (fileAndName) {
|
| + let fileSubs = TS_TO_DART_TYPENAMES[fileAndName.fileName];
|
| + if (fileSubs) {
|
| + let dartBrowserType = DART_LIBRARIES_FOR_BROWSER_TYPES.hasOwnProperty(fileAndName.qname);
|
| + if (dartBrowserType) {
|
| + this.emitImport(DART_LIBRARIES_FOR_BROWSER_TYPES[fileAndName.qname]);
|
| + }
|
| + if (fileSubs.hasOwnProperty(fileAndName.qname)) {
|
| + return {name: fileSubs[fileAndName.qname]};
|
| + }
|
| + if (dartBrowserType) {
|
| + // Not a rename but has a dart core libraries definition.
|
| + return {name: fileAndName.qname};
|
| + }
|
| + }
|
| + }
|
| + }
|
| + if (symbol) {
|
| + if (symbol.flags & ts.SymbolFlags.Enum) {
|
| + // We can't treat JavaScript enums as Dart enums in this case.
|
| + return {name: 'num', comment: 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');
|
| + }
|
| + return {name: this.generateDartTypeName(alias.type, insideComment)};
|
| + }
|
|
|
| - // Error will be reported by PropertyAccess handling below.
|
| - if (!symbol) return {};
|
| + let kind = declaration.kind;
|
| + if (kind === ts.SyntaxKind.ClassDeclaration || kind === ts.SyntaxKind.InterfaceDeclaration ||
|
| + kind === ts.SyntaxKind.VariableDeclaration ||
|
| + kind === ts.SyntaxKind.PropertyDeclaration ||
|
| + kind === ts.SyntaxKind.FunctionDeclaration) {
|
| + let name = this.nameRewriter.lookupName(<base.NamedDeclaration>declaration, identifier);
|
| + if (kind === ts.SyntaxKind.InterfaceDeclaration &&
|
| + base.isFunctionTypedefLikeInterface(<ts.InterfaceDeclaration>declaration) &&
|
| + base.getAncestor(identifier, ts.SyntaxKind.HeritageClause)) {
|
| + // TODO(jacobr): we need to specify a specific call method for this
|
| + // case if we want to get the most from Dart type checking.
|
| + return {name: 'Function', comment: name};
|
| + }
|
| + return {name: name, keep: true};
|
| + }
|
| + }
|
| + return null;
|
| + }
|
|
|
| - context = pa.expression;
|
| + // TODO(jacobr): performance of this method could easily be optimized.
|
| + /**
|
| + * 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>) {
|
| + // 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
|
| + // including that logic here as well.
|
| + let mergedType = new MergedType(this);
|
| + types.forEach((type) => { mergedType.merge(type); });
|
| + let merged = mergedType.toTypeNode();
|
| + if (merged.kind === ts.SyntaxKind.UnionType) {
|
| + // For union types find a Dart type that satisfies all the types.
|
| + types = (<ts.UnionTypeNode>merged).types;
|
| + /**
|
| + * Generate a common base type for an array of types.
|
| + * The implemented is currently incomplete often returning null when there
|
| + * might really be a valid common base type.
|
| + */
|
| + 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;
|
| + }
|
| + }
|
| + }
|
| + }
|
| + return common;
|
| }
|
| - return {context, symbol};
|
| + return merged;
|
| + }
|
| +
|
| + toTypeNode(type: ts.Type): ts.TypeNode {
|
| + if (!type) return null;
|
| + let symbol = type.getSymbol();
|
| + if (!symbol) return null;
|
| +
|
| + let referenceType = <ts.TypeReferenceNode>ts.createNode(ts.SyntaxKind.TypeReference);
|
| + // TODO(jacobr): property need to prefix the name better.
|
| + referenceType.typeName = this.createEntityName(symbol);
|
| + referenceType.typeName.parent = referenceType;
|
| + return referenceType;
|
| }
|
|
|
| - private getHandler<T>(n: ts.Node, symbol: ts.Symbol, m: ts.Map<ts.Map<T>>): T {
|
| - let loc = this.getFileAndName(n, symbol);
|
| - if (!loc) return null;
|
| - let {fileName, qname} = loc;
|
| - let fileSubs = m[fileName];
|
| - if (!fileSubs) return null;
|
| - return fileSubs[qname];
|
| + createEntityName(symbol: ts.Symbol): ts.EntityName {
|
| + let parts = this.tc.getFullyQualifiedName(symbol).split('.');
|
| + let identifier = <ts.Identifier>ts.createNode(ts.SyntaxKind.Identifier);
|
| + identifier.text = parts[parts.length - 1];
|
| + // TODO(jacobr): do we need to include all parts in the entity name?
|
| + return identifier;
|
| + }
|
| +
|
| + safeGetBaseTypes(type: ts.InterfaceType): ts.ObjectType[] {
|
| + // For an unknown, calling TypeChecker.getBaseTypes on an interface
|
| + // that is a typedef like interface causes the typescript compiler to stack
|
| + // overflow. Not sure if this is a bug in the typescript compiler or I am
|
| + // missing something obvious.
|
| + let declaration = base.getDeclaration(type) as ts.InterfaceDeclaration;
|
| + if (base.isFunctionTypedefLikeInterface(declaration)) {
|
| + return [];
|
| + }
|
| + return this.tc.getBaseTypes(type);
|
| + }
|
| +
|
| + // TODO(jacobr): all of these subtype checks are fragile and are likely a
|
| + // mistake. We would be better off handling subtype relationships in Dart
|
| + // where we could reuse an existing Dart type system.
|
| + checkTypeSubtypeOf(source: ts.Type, target: ts.Type) {
|
| + if (source === target) return true;
|
| + if (!(source.flags & ts.TypeFlags.Interface)) return false;
|
| + let baseTypes = this.safeGetBaseTypes(source as ts.InterfaceType);
|
| + for (let i = 0; i < baseTypes.length; ++i) {
|
| + if (baseTypes[i] === target) return true;
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + commonSupertype(nodeA: ts.TypeNode, nodeB: ts.TypeNode): ts.TypeNode {
|
| + if (nodeA == null || nodeB == null) return null;
|
| + 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.
|
| + return null;
|
| + /*
|
| + if (!(a.flags & ts.TypeFlags.Interface) || !(b.flags & ts.TypeFlags.Interface)) {
|
| + return null;
|
| + }
|
| +
|
| + let bestCommonSuperType: ts.Type = null;
|
| + let candidatesA = this.safeGetBaseTypes(a as ts.InterfaceType);
|
| + candidatesA.push(a);
|
| +
|
| + for (let i = 0; i < candidatesA.length; ++i) {
|
| + let type = candidatesA[i];
|
| + if (this.checkTypeSubtypeOf(b, type)) {
|
| + if (!bestCommonSuperType || this.checkTypeSubtypeOf(bestCommonSuperType, type)) {
|
| + bestCommonSuperType = type;
|
| + }
|
| + }
|
| + }
|
| + return bestCommonSuperType;
|
| + */
|
| }
|
|
|
| private getFileAndName(n: ts.Node, originalSymbol: ts.Symbol): {fileName: string, qname: string} {
|
| let symbol = originalSymbol;
|
| while (symbol.flags & ts.SymbolFlags.Alias) symbol = this.tc.getAliasedSymbol(symbol);
|
| - let decl = symbol.valueDeclaration;
|
| - if (!decl) {
|
| - // In the case of a pure declaration with no assignment, there is no value declared.
|
| - // Just grab the first declaration, hoping it is declared once.
|
| - if (!symbol.declarations || symbol.declarations.length === 0) {
|
| - this.reportError(n, 'no declarations for symbol ' + originalSymbol.name);
|
| - return null;
|
| - }
|
| - decl = symbol.declarations[0];
|
| - }
|
| + let decl = this.getSymbolDeclaration(symbol, n);
|
|
|
| const fileName = decl.getSourceFile().fileName;
|
| const canonicalFileName = this.getRelativeFileName(fileName)
|
| @@ -281,446 +736,4 @@ export class FacadeConverter extends base.TranspilerBase {
|
| if (FACADE_DEBUG) console.error('fn:', fileName, 'cfn:', canonicalFileName, 'qn:', qname);
|
| return {fileName: canonicalFileName, qname};
|
| }
|
| -
|
| - private isNamedType(node: ts.Node, fileName: string, qname: string): boolean {
|
| - let symbol = this.tc.getTypeAtLocation(node).getSymbol();
|
| - if (!symbol) return false;
|
| - let actual = this.getFileAndName(node, symbol);
|
| - if (fileName === 'lib' && !(actual.fileName === 'lib' || actual.fileName === 'lib.es6')) {
|
| - return false;
|
| - } else {
|
| - if (fileName !== actual.fileName) return false;
|
| - }
|
| - return qname === actual.qname;
|
| - }
|
| -
|
| - private reportMissingType(n: ts.Node, ident: string) {
|
| - this.reportError(
|
| - n, `Untyped property access to "${ident}" which could be ` + `a special ts2dart builtin. ` +
|
| - `Please add type declarations to disambiguate.`);
|
| - }
|
| -
|
| - isInsideConstExpr(node: ts.Node): boolean {
|
| - return this.isConstCall(
|
| - <ts.CallExpression>this.getAncestor(node, ts.SyntaxKind.CallExpression));
|
| - }
|
| -
|
| - isConstCall(node: ts.Expression): boolean {
|
| - return node && node.kind === ts.SyntaxKind.CallExpression &&
|
| - base.ident((<ts.CallExpression>node).expression) === 'CONST_EXPR';
|
| - }
|
| -
|
| - private emitMethodCall(name: string, args?: ts.Expression[]) {
|
| - this.emit('.');
|
| - this.emitCall(name, args);
|
| - }
|
| -
|
| - private emitCall(name: string, args?: ts.Expression[]) {
|
| - this.emit(name);
|
| - this.emit('(');
|
| - if (args) this.visitList(args);
|
| - this.emit(')');
|
| - }
|
| -
|
| - private stdlibTypeReplacements: ts.Map<string> = {
|
| - 'Date': 'DateTime',
|
| - 'Array': 'List',
|
| - 'XMLHttpRequest': 'HttpRequest',
|
| - 'Uint8Array': 'Uint8List',
|
| - 'ArrayBuffer': 'ByteBuffer',
|
| - 'Promise': 'Future',
|
| -
|
| - // Dart has two different incompatible DOM APIs
|
| - // https://github.com/angular/angular/issues/2770
|
| - 'Node': 'dynamic',
|
| - 'Text': 'dynamic',
|
| - 'Element': 'dynamic',
|
| - 'Event': 'dynamic',
|
| - 'HTMLElement': 'dynamic',
|
| - 'HTMLAnchorElement': 'dynamic',
|
| - 'HTMLStyleElement': 'dynamic',
|
| - 'HTMLInputElement': 'dynamic',
|
| - 'HTMLDocument': 'dynamic',
|
| - 'History': 'dynamic',
|
| - 'Location': 'dynamic',
|
| - };
|
| -
|
| - private TS_TO_DART_TYPENAMES: ts.Map<ts.Map<string>> = {
|
| - 'lib': this.stdlibTypeReplacements,
|
| - 'lib.es6': this.stdlibTypeReplacements,
|
| - 'angular2/src/facade/lang': {'Date': 'DateTime'},
|
| -
|
| - 'rxjs/Observable': {'Observable': 'Stream'},
|
| - 'es6-promise/es6-promise': {'Promise': 'Future'},
|
| - 'es6-shim/es6-shim': {'Promise': 'Future'},
|
| - };
|
| -
|
| - private es6Promises: ts.Map<CallHandler> = {
|
| - 'Promise.catch': (c: ts.CallExpression, context: ts.Expression) => {
|
| - this.visit(context);
|
| - this.emit('.catchError(');
|
| - this.visitList(c.arguments);
|
| - this.emit(')');
|
| - },
|
| - 'Promise.then': (c: ts.CallExpression, context: ts.Expression) => {
|
| - // then() in Dart doesn't support 2 arguments.
|
| - this.visit(context);
|
| - this.emit('.then(');
|
| - this.visit(c.arguments[0]);
|
| - this.emit(')');
|
| - if (c.arguments.length > 1) {
|
| - this.emit('.catchError(');
|
| - this.visit(c.arguments[1]);
|
| - this.emit(')');
|
| - }
|
| - },
|
| - 'Promise': (c: ts.CallExpression, context: ts.Expression) => {
|
| - if (c.kind !== ts.SyntaxKind.NewExpression) return true;
|
| - this.assert(c, c.arguments.length === 1, 'Promise construction must take 2 arguments.');
|
| - this.assert(
|
| - c, c.arguments[0].kind === ts.SyntaxKind.ArrowFunction ||
|
| - c.arguments[0].kind === ts.SyntaxKind.FunctionExpression,
|
| - 'Promise argument must be a function expression (or arrow function).');
|
| - let callback: ts.FunctionLikeDeclaration;
|
| - if (c.arguments[0].kind === ts.SyntaxKind.ArrowFunction) {
|
| - callback = <ts.FunctionLikeDeclaration>(<ts.ArrowFunction>c.arguments[0]);
|
| - } else if (c.arguments[0].kind === ts.SyntaxKind.FunctionExpression) {
|
| - callback = <ts.FunctionLikeDeclaration>(<ts.FunctionExpression>c.arguments[0]);
|
| - }
|
| - this.assert(
|
| - c, callback.parameters.length > 0 && callback.parameters.length < 3,
|
| - 'Promise executor must take 1 or 2 arguments (resolve and reject).');
|
| -
|
| - const completerVarName = this.uniqueId('completer');
|
| - this.assert(
|
| - c, callback.parameters[0].name.kind === ts.SyntaxKind.Identifier,
|
| - 'First argument of the Promise executor is not a straight parameter.');
|
| - let resolveParameterIdent = <ts.Identifier>(callback.parameters[0].name);
|
| -
|
| - this.emit('(() {'); // Create a new scope.
|
| - this.emit(`Completer ${completerVarName} = new Completer();`);
|
| - this.emit('var');
|
| - this.emit(resolveParameterIdent.text);
|
| - this.emit(`= ${completerVarName}.complete;`);
|
| -
|
| - if (callback.parameters.length === 2) {
|
| - this.assert(
|
| - c, callback.parameters[1].name.kind === ts.SyntaxKind.Identifier,
|
| - 'First argument of the Promise executor is not a straight parameter.');
|
| - let rejectParameterIdent = <ts.Identifier>(callback.parameters[1].name);
|
| - this.emit('var');
|
| - this.emit(rejectParameterIdent.text);
|
| - this.emit(`= ${completerVarName}.completeError;`);
|
| - }
|
| - this.emit('(()');
|
| - this.visit(callback.body);
|
| - this.emit(')();');
|
| - this.emit(`return ${completerVarName}.future;`);
|
| - this.emit('})()');
|
| - },
|
| - };
|
| -
|
| - private stdlibHandlers: ts.Map<CallHandler> = merge(this.es6Promises, {
|
| - 'Array.push': (c: ts.CallExpression, context: ts.Expression) => {
|
| - this.visit(context);
|
| - this.emitMethodCall('add', c.arguments);
|
| - },
|
| - 'Array.pop': (c: ts.CallExpression, context: ts.Expression) => {
|
| - this.visit(context);
|
| - this.emitMethodCall('removeLast');
|
| - },
|
| - 'Array.shift': (c: ts.CallExpression, context: ts.Expression) => {
|
| - this.visit(context);
|
| - this.emit('. removeAt ( 0 )');
|
| - },
|
| - 'Array.unshift': (c: ts.CallExpression, context: ts.Expression) => {
|
| - this.emit('(');
|
| - this.visit(context);
|
| - if (c.arguments.length === 1) {
|
| - this.emit('.. insert ( 0,');
|
| - this.visit(c.arguments[0]);
|
| - this.emit(') ) . length');
|
| - } else {
|
| - this.emit('.. insertAll ( 0, [');
|
| - this.visitList(c.arguments);
|
| - this.emit(']) ) . length');
|
| - }
|
| - },
|
| - 'Array.map': (c: ts.CallExpression, context: ts.Expression) => {
|
| - this.visit(context);
|
| - this.emitMethodCall('map', c.arguments);
|
| - this.emitMethodCall('toList');
|
| - },
|
| - 'Array.filter': (c: ts.CallExpression, context: ts.Expression) => {
|
| - this.visit(context);
|
| - this.emitMethodCall('where', c.arguments);
|
| - this.emitMethodCall('toList');
|
| - },
|
| - 'Array.some': (c: ts.CallExpression, context: ts.Expression) => {
|
| - this.visit(context);
|
| - this.emitMethodCall('any', c.arguments);
|
| - },
|
| - 'Array.slice': (c: ts.CallExpression, context: ts.Expression) => {
|
| - this.emitCall('ListWrapper.slice', [context, ...c.arguments]);
|
| - },
|
| - 'Array.splice': (c: ts.CallExpression, context: ts.Expression) => {
|
| - this.emitCall('ListWrapper.splice', [context, ...c.arguments]);
|
| - },
|
| - 'Array.concat': (c: ts.CallExpression, context: ts.Expression) => {
|
| - this.emit('( new List . from (');
|
| - this.visit(context);
|
| - this.emit(')');
|
| - c.arguments.forEach(arg => {
|
| - if (!this.isNamedType(arg, 'lib', 'Array')) {
|
| - this.reportError(arg, 'Array.concat only takes Array arguments');
|
| - }
|
| - this.emit('.. addAll (');
|
| - this.visit(arg);
|
| - this.emit(')');
|
| - });
|
| - this.emit(')');
|
| - },
|
| - 'Array.join': (c: ts.CallExpression, context: ts.Expression) => {
|
| - this.visit(context);
|
| - if (c.arguments.length) {
|
| - this.emitMethodCall('join', c.arguments);
|
| - } else {
|
| - this.emit('. join ( "," )');
|
| - }
|
| - },
|
| - 'Array.reduce': (c: ts.CallExpression, context: ts.Expression) => {
|
| - this.visit(context);
|
| -
|
| - if (c.arguments.length >= 2) {
|
| - this.emitMethodCall('fold', [c.arguments[1], c.arguments[0]]);
|
| - } else {
|
| - this.emit('. fold ( null ,');
|
| - this.visit(c.arguments[0]);
|
| - this.emit(')');
|
| - }
|
| - },
|
| - 'ArrayConstructor.isArray': (c: ts.CallExpression, context: ts.Expression) => {
|
| - this.emit('( (');
|
| - this.visitList(c.arguments); // Should only be 1.
|
| - this.emit(')');
|
| - this.emit('is List');
|
| - this.emit(')');
|
| - },
|
| - 'Console.log': (c: ts.CallExpression, context: ts.Expression) => {
|
| - this.emit('print(');
|
| - if (c.arguments.length === 1) {
|
| - this.visit(c.arguments[0]);
|
| - } else {
|
| - this.emit('[');
|
| - this.visitList(c.arguments);
|
| - this.emit('].join(" ")');
|
| - }
|
| - this.emit(')');
|
| - },
|
| - 'RegExp.exec': (c: ts.CallExpression, context: ts.Expression) => {
|
| - if (context.kind !== ts.SyntaxKind.RegularExpressionLiteral) {
|
| - // Fail if the exec call isn't made directly on a regexp literal.
|
| - // Multiple exec calls on the same global regexp have side effects
|
| - // (each return the next match), which we can't reproduce with a simple
|
| - // Dart RegExp (users should switch to some facade / wrapper instead).
|
| - this.reportError(
|
| - c, 'exec is only supported on regexp literals, ' +
|
| - 'to avoid side-effect of multiple calls on global regexps.');
|
| - }
|
| - if (c.parent.kind === ts.SyntaxKind.ElementAccessExpression) {
|
| - // The result of the exec call is used for immediate indexed access:
|
| - // this use-case can be accommodated by RegExp.firstMatch, which returns
|
| - // a Match instance with operator[] which returns groups (special index
|
| - // 0 returns the full text of the match).
|
| - this.visit(context);
|
| - this.emitMethodCall('firstMatch', c.arguments);
|
| - } else {
|
| - // In the general case, we want to return a List. To transform a Match
|
| - // into a List of its groups, we alias it in a local closure that we
|
| - // call with the Match value. We are then able to use the group method
|
| - // to generate a List large enough to hold groupCount groups + the
|
| - // full text of the match at special group index 0.
|
| - this.emit('((match) => new List.generate(1 + match.groupCount, match.group))(');
|
| - this.visit(context);
|
| - this.emitMethodCall('firstMatch', c.arguments);
|
| - this.emit(')');
|
| - }
|
| - },
|
| - 'RegExp.test': (c: ts.CallExpression, context: ts.Expression) => {
|
| - this.visit(context);
|
| - this.emitMethodCall('hasMatch', c.arguments);
|
| - },
|
| - 'String.substr': (c: ts.CallExpression, context: ts.Expression) => {
|
| - this.reportError(
|
| - c, 'substr is unsupported, use substring (but beware of the different semantics!)');
|
| - this.visit(context);
|
| - this.emitMethodCall('substr', c.arguments);
|
| - },
|
| - });
|
| -
|
| - private es6Collections: ts.Map<CallHandler> = {
|
| - 'Map.set': (c: ts.CallExpression, context: ts.Expression) => {
|
| - this.visit(context);
|
| - this.emit('[');
|
| - this.visit(c.arguments[0]);
|
| - this.emit(']');
|
| - this.emit('=');
|
| - this.visit(c.arguments[1]);
|
| - },
|
| - 'Map.get': (c: ts.CallExpression, context: ts.Expression) => {
|
| - this.visit(context);
|
| - this.emit('[');
|
| - this.visit(c.arguments[0]);
|
| - this.emit(']');
|
| - },
|
| - 'Map.has': (c: ts.CallExpression, context: ts.Expression) => {
|
| - this.visit(context);
|
| - this.emitMethodCall('containsKey', c.arguments);
|
| - },
|
| - 'Map.delete': (c: ts.CallExpression, context: ts.Expression) => {
|
| - // JS Map.delete(k) returns whether k was present in the map,
|
| - // convert to:
|
| - // (Map.containsKey(k) && (Map.remove(k) !== null || true))
|
| - // (Map.remove(k) !== null || true) is required to always returns true
|
| - // when Map.containsKey(k)
|
| - this.emit('(');
|
| - this.visit(context);
|
| - this.emitMethodCall('containsKey', c.arguments);
|
| - this.emit('&& (');
|
| - this.visit(context);
|
| - this.emitMethodCall('remove', c.arguments);
|
| - this.emit('!= null || true ) )');
|
| - },
|
| - 'Map.forEach': (c: ts.CallExpression, context: ts.Expression) => {
|
| - let cb: any;
|
| - let params: any;
|
| -
|
| - switch (c.arguments[0].kind) {
|
| - case ts.SyntaxKind.FunctionExpression:
|
| - cb = <ts.FunctionExpression>(c.arguments[0]);
|
| - params = cb.parameters;
|
| - if (params.length !== 2) {
|
| - this.reportError(c, 'Map.forEach callback requires exactly two arguments');
|
| - return;
|
| - }
|
| - this.visit(context);
|
| - this.emit('. forEach ( (');
|
| - this.visit(params[1]);
|
| - this.emit(',');
|
| - this.visit(params[0]);
|
| - this.emit(')');
|
| - this.visit(cb.body);
|
| - this.emit(')');
|
| - break;
|
| -
|
| - case ts.SyntaxKind.ArrowFunction:
|
| - cb = <ts.ArrowFunction>(c.arguments[0]);
|
| - params = cb.parameters;
|
| - if (params.length !== 2) {
|
| - this.reportError(c, 'Map.forEach callback requires exactly two arguments');
|
| - return;
|
| - }
|
| - this.visit(context);
|
| - this.emit('. forEach ( (');
|
| - this.visit(params[1]);
|
| - this.emit(',');
|
| - this.visit(params[0]);
|
| - this.emit(')');
|
| - if (cb.body.kind !== ts.SyntaxKind.Block) {
|
| - this.emit('=>');
|
| - }
|
| - this.visit(cb.body);
|
| - this.emit(')');
|
| - break;
|
| -
|
| - default:
|
| - this.visit(context);
|
| - this.emit('. forEach ( ( k , v ) => (');
|
| - this.visit(c.arguments[0]);
|
| - this.emit(') ( v , k ) )');
|
| - break;
|
| - }
|
| - },
|
| - 'Array.find': (c: ts.CallExpression, context: ts.Expression) => {
|
| - this.visit(context);
|
| - this.emit('. firstWhere (');
|
| - this.visit(c.arguments[0]);
|
| - this.emit(', orElse : ( ) => null )');
|
| - },
|
| - };
|
| -
|
| - private callHandlerReplaceNew: ts.Map<ts.Map<boolean>> = {
|
| - 'es6-promise/es6-promise': {'Promise': true},
|
| - 'es6-shim/es6-shim': {'Promise': true},
|
| - };
|
| -
|
| - private callHandlers: ts.Map<ts.Map<CallHandler>> = {
|
| - 'lib': this.stdlibHandlers,
|
| - 'lib.es6': this.stdlibHandlers,
|
| - 'es6-promise/es6-promise': this.es6Promises,
|
| - 'es6-shim/es6-shim': merge(this.es6Promises, this.es6Collections),
|
| - 'es6-collections/es6-collections': this.es6Collections,
|
| - 'angular2/manual_typings/globals': this.es6Collections,
|
| - 'angular2/src/facade/collection': {
|
| - 'Map': (c: ts.CallExpression, context: ts.Expression): boolean => {
|
| - // The actual Map constructor is special cased for const calls.
|
| - if (!this.isInsideConstExpr(c)) return true;
|
| - if (c.arguments.length) {
|
| - this.reportError(c, 'Arguments on a Map constructor in a const are unsupported');
|
| - }
|
| - if (c.typeArguments) {
|
| - this.emit('<');
|
| - this.visitList(c.typeArguments);
|
| - this.emit('>');
|
| - }
|
| - this.emit('{ }');
|
| - return false;
|
| - },
|
| - },
|
| - 'angular2/src/core/di/forward_ref': {
|
| - 'forwardRef': (c: ts.CallExpression, context: ts.Expression) => {
|
| - // The special function forwardRef translates to an unwrapped value in Dart.
|
| - const callback = <ts.FunctionExpression>c.arguments[0];
|
| - if (callback.kind !== ts.SyntaxKind.ArrowFunction) {
|
| - this.reportError(c, 'forwardRef takes only arrow functions');
|
| - return;
|
| - }
|
| - this.visit(callback.body);
|
| - },
|
| - },
|
| - 'angular2/src/facade/lang': {
|
| - 'CONST_EXPR': (c: ts.CallExpression, context: ts.Expression) => {
|
| - // `const` keyword is emitted in the array literal handling, as it needs to be transitive.
|
| - this.visitList(c.arguments);
|
| - },
|
| - 'normalizeBlank': (c: ts.CallExpression, context: ts.Expression) => {
|
| - // normalizeBlank is a noop in Dart, so erase it.
|
| - this.visitList(c.arguments);
|
| - },
|
| - },
|
| - };
|
| -
|
| - private es6CollectionsProp: ts.Map<PropertyHandler> = {
|
| - 'Map.size': (p: ts.PropertyAccessExpression) => {
|
| - this.visit(p.expression);
|
| - this.emit('.');
|
| - this.emit('length');
|
| - },
|
| - };
|
| - private es6PromisesProp: ts.Map<PropertyHandler> = {
|
| - 'resolve': (p: ts.PropertyAccessExpression) => {
|
| - this.visit(p.expression);
|
| - this.emit('.value');
|
| - },
|
| - 'reject': (p: ts.PropertyAccessExpression) => {
|
| - this.visit(p.expression);
|
| - this.emit('.error');
|
| - },
|
| - };
|
| -
|
| - private propertyHandlers: ts.Map<ts.Map<PropertyHandler>> = {
|
| - 'es6-shim/es6-shim': this.es6CollectionsProp,
|
| - 'es6-collections/es6-collections': this.es6CollectionsProp,
|
| - 'es6-promise/es6-promise': this.es6PromisesProp,
|
| - };
|
| }
|
|
|