| Index: lib/base.ts | 
| diff --git a/lib/base.ts b/lib/base.ts | 
| index dcc5d5f047617f6ba11a1d8025a6341a881aa1fe..9267d1d90b113fa9bdddbfe3b829720c08c113b2 100644 | 
| --- a/lib/base.ts | 
| +++ b/lib/base.ts | 
| @@ -1,18 +1,63 @@ | 
| import * as dartStyle from 'dart-style'; | 
| +import * as path from 'path'; | 
| import * as ts from 'typescript'; | 
|  | 
| import {OutputContext, Transpiler} from './main'; | 
|  | 
| +export type ResolvedTypeMap = { | 
| +  [name: string]: ts.TypeNode | 
| +}; | 
| + | 
| +/*** | 
| + * Options for how we display a TypeScript type as a Dart type. | 
| + */ | 
| +export interface TypeDisplayOptions { | 
| +  /** | 
| +   * We are displaying the type inside a comment so we don't have to restrict to valid Dart syntax. | 
| +   * For example, we can display string literal type using the regular TypeScript syntax. | 
| +   */ | 
| +  insideComment?: boolean; | 
| +  /** | 
| +   * Dart has additional restrictions for what types are valid to emit inside a type argument. For | 
| +   * example, "void" is not valid inside a type argument so Null has to be used instead. | 
| +   */ | 
| +  insideTypeArgument?: boolean; | 
| +  /** | 
| +   * Indicates that we should not append an additional comment indicating what the true TypeScript | 
| +   * type was for cases where Dart cannot express the type precisely. | 
| +   */ | 
| +  hideComment?: boolean; | 
| + | 
| +  /** | 
| +   * Parameter declarations to substitute | 
| +   */ | 
| +  typeArguments?: ts.TypeNode[]; | 
| + | 
| +  resolvedTypeArguments?: ResolvedTypeMap; | 
| +} | 
| + | 
| export type ClassLike = ts.ClassDeclaration | ts.InterfaceDeclaration; | 
| export type NamedDeclaration = ClassLike | ts.PropertyDeclaration | ts.VariableDeclaration | | 
| ts.MethodDeclaration | ts.ModuleDeclaration | ts.FunctionDeclaration; | 
|  | 
| +export interface ExtendedInterfaceDeclaration extends ts.InterfaceDeclaration { | 
| +  /** | 
| +   * VariableDeclaration associated with this interface that we want to treat as the concrete | 
| +   * location of this interface to enable interfaces that act like constructors. | 
| +   * Because Dart does not permit calling objects like constructors we have to add this workaround. | 
| +   */ | 
| +  classLikeVariableDeclaration?: ts.VariableDeclaration; | 
| +} | 
| + | 
| export type Set = { | 
| [s: string]: boolean | 
| }; | 
|  | 
| export function ident(n: ts.Node): string { | 
| if (n.kind === ts.SyntaxKind.Identifier) return (<ts.Identifier>n).text; | 
| +  if (n.kind === ts.SyntaxKind.FirstLiteralToken) { | 
| +    return (n as ts.LiteralLikeNode).text; | 
| +  } | 
| if (n.kind === ts.SyntaxKind.QualifiedName) { | 
| let qname = (<ts.QualifiedName>n); | 
| let leftName = ident(qname.left); | 
| @@ -60,6 +105,11 @@ export function isCallableType(type: ts.TypeNode, tc: ts.TypeChecker): boolean { | 
| return false; | 
| } | 
|  | 
| +export function supportedAliasType(alias: ts.TypeAliasDeclaration): boolean { | 
| +  let kind = alias.type.kind; | 
| +  return kind === ts.SyntaxKind.TypeLiteral || kind === ts.SyntaxKind.FunctionType; | 
| +} | 
| + | 
| export function isFunctionType(type: ts.TypeNode, tc: ts.TypeChecker): boolean { | 
| let kind = type.kind; | 
| if (kind === ts.SyntaxKind.FunctionType) return true; | 
| @@ -68,6 +118,16 @@ export function isFunctionType(type: ts.TypeNode, tc: ts.TypeChecker): boolean { | 
| if (t.symbol && t.symbol.flags & ts.SymbolFlags.Function) return true; | 
| } | 
|  | 
| +  if (kind === ts.SyntaxKind.IntersectionType) { | 
| +    let types = (<ts.IntersectionTypeNode>type).types; | 
| +    for (let i = 0; i < types.length; ++i) { | 
| +      if (isFunctionType(types[i], tc)) { | 
| +        return true; | 
| +      } | 
| +    } | 
| +    return false; | 
| +  } | 
| + | 
| if (kind === ts.SyntaxKind.UnionType) { | 
| let types = (<ts.UnionTypeNode>type).types; | 
| for (let i = 0; i < types.length; ++i) { | 
| @@ -92,9 +152,30 @@ export function isFunctionType(type: ts.TypeNode, tc: ts.TypeChecker): boolean { | 
| return false; | 
| } | 
|  | 
| +export function isThisParameter(param: ts.ParameterDeclaration): boolean { | 
| +  return param.name && param.name.kind === ts.SyntaxKind.Identifier && | 
| +      (param.name as ts.Identifier).text === 'this'; | 
| +} | 
| + | 
| +/** | 
| + * Dart does not have a concept of binding the type of the "this" parameter to a method. | 
| + */ | 
| +export function filterThisParameter(params: ts.ParameterDeclaration[]): ts.ParameterDeclaration[] { | 
| +  let ret: ts.ParameterDeclaration[] = []; | 
| +  for (let i = 0; i < params.length; i++) { | 
| +    let param = params[i]; | 
| +    if (!isThisParameter(param)) { | 
| +      ret.push(param); | 
| +    } | 
| +  } | 
| +  return ret; | 
| +} | 
| + | 
| export function isTypeNode(node: ts.Node): boolean { | 
| switch (node.kind) { | 
| +    case ts.SyntaxKind.IntersectionType: | 
| case ts.SyntaxKind.UnionType: | 
| +    case ts.SyntaxKind.ParenthesizedType: | 
| case ts.SyntaxKind.TypeReference: | 
| case ts.SyntaxKind.TypeLiteral: | 
| case ts.SyntaxKind.LastTypeNode: | 
| @@ -105,9 +186,12 @@ export function isTypeNode(node: ts.Node): boolean { | 
| case ts.SyntaxKind.NumberKeyword: | 
| case ts.SyntaxKind.StringKeyword: | 
| case ts.SyntaxKind.VoidKeyword: | 
| +    case ts.SyntaxKind.NullKeyword: | 
| +    case ts.SyntaxKind.UndefinedKeyword: | 
| case ts.SyntaxKind.BooleanKeyword: | 
| case ts.SyntaxKind.AnyKeyword: | 
| case ts.SyntaxKind.FunctionType: | 
| +    case ts.SyntaxKind.ThisType: | 
| return true; | 
| default: | 
| return false; | 
| @@ -141,11 +225,12 @@ export function getAncestor(n: ts.Node, kind: ts.SyntaxKind): ts.Node { | 
| } | 
|  | 
| export function getEnclosingClass(n: ts.Node): ClassLike { | 
| -  for (let parent = n.parent; parent; parent = parent.parent) { | 
| -    if (parent.kind === ts.SyntaxKind.ClassDeclaration || | 
| -        parent.kind === ts.SyntaxKind.InterfaceDeclaration) { | 
| -      return <ClassLike>parent; | 
| +  while (n) { | 
| +    if (n.kind === ts.SyntaxKind.ClassDeclaration || | 
| +        n.kind === ts.SyntaxKind.InterfaceDeclaration) { | 
| +      return <ClassLike>n; | 
| } | 
| +    n = n.parent; | 
| } | 
| return null; | 
| } | 
| @@ -158,10 +243,10 @@ export function isInsideConstExpr(node: ts.Node): boolean { | 
| return isConstCall(<ts.CallExpression>getAncestor(node, ts.SyntaxKind.CallExpression)); | 
| } | 
|  | 
| -export function formatType(s: string, comment: string, insideCodeComment: boolean): string { | 
| -  if (!comment) { | 
| +export function formatType(s: string, comment: string, options: TypeDisplayOptions): string { | 
| +  if (!comment || options.hideComment) { | 
| return s; | 
| -  } else if (insideCodeComment) { | 
| +  } else if (options.insideComment) { | 
| // When inside a comment we only need to emit the comment version which | 
| // is the syntax we would like to use if Dart supported all language | 
| // features we would like to use for interop. | 
| @@ -202,17 +287,34 @@ export class TranspilerBase { | 
| maybeLineBreak() { return this.transpiler.maybeLineBreak(); } | 
| enterCodeComment() { return this.transpiler.enterCodeComment(); } | 
| exitCodeComment() { return this.transpiler.exitCodeComment(); } | 
| + | 
| +  enterTypeArguments() { this.transpiler.enterTypeArgument(); } | 
| +  exitTypeArguments() { this.transpiler.exitTypeArgument(); } | 
| +  get insideTypeArgument() { return this.transpiler.insideTypeArgument; } | 
| + | 
| get insideCodeComment() { return this.transpiler.insideCodeComment; } | 
|  | 
| emitImport(toEmit: string) { | 
| if (!this.transpiler.importsEmitted[toEmit]) { | 
| this.pushContext(OutputContext.Import); | 
| -      this.emit(`import "${toEmit}";`); | 
| +      this.emit(`import "${toEmit}";\n`); | 
| this.transpiler.importsEmitted[toEmit] = true; | 
| this.popContext(); | 
| } | 
| } | 
|  | 
| +  emitImportForSourceFile(sourceFile: ts.SourceFile, context: ts.SourceFile) { | 
| +    if (sourceFile === context) return; | 
| +    if (sourceFile.hasNoDefaultLib) { | 
| +      // We don't want to emit imports to default lib libraries as we replace with Dart equivalents | 
| +      // such as dart:html, etc. | 
| +      return; | 
| +    } | 
| +    let relativePath = path.relative(path.dirname(context.fileName), sourceFile.fileName); | 
| +    this.emitImport(this.transpiler.getDartFileName(relativePath)); | 
| +  } | 
| + | 
| + | 
| reportError(n: ts.Node, message: string) { this.transpiler.reportError(n, message); } | 
|  | 
| visitNode(n: ts.Node): boolean { throw new Error('not implemented'); } | 
| @@ -230,6 +332,33 @@ export class TranspilerBase { | 
| } | 
| } | 
|  | 
| +  /** | 
| +   * Returns whether any parameters were actually emitted. | 
| +   * @param nodes | 
| +   * @param separator | 
| +   */ | 
| +  visitParameterList(nodes: ts.ParameterDeclaration[]): boolean { | 
| +    let emittedParameters = false; | 
| +    for (let i = 0; i < nodes.length; ++i) { | 
| +      let param = nodes[i]; | 
| +      if (!this.insideCodeComment && isThisParameter(param)) { | 
| +        // Emit the this type in a comment as it could be of interest to Dart users who are | 
| +        // calling allowInteropCaptureThis to bind a Dart method. | 
| +        this.enterCodeComment(); | 
| +        this.visit(param.type); | 
| +        this.emit('this'); | 
| +        this.exitCodeComment(); | 
| +        continue; | 
| +      } | 
| +      if (emittedParameters) { | 
| +        this.emitNoSpace(','); | 
| +      } | 
| +      this.visit(param); | 
| +      emittedParameters = true; | 
| +    } | 
| +    return emittedParameters; | 
| +  } | 
| + | 
| uniqueId(name: string): string { | 
| const id = this.idCounter++; | 
| return `_${name}\$\$js_facade_gen\$${id}`; | 
| @@ -271,16 +400,14 @@ export class TranspilerBase { | 
| return this.transpiler.getRelativeFileName(fileName); | 
| } | 
|  | 
| +  getDartFileName(fileName?: string): string { return this.transpiler.getDartFileName(fileName); } | 
| + | 
| maybeVisitTypeArguments(n: {typeArguments?: ts.NodeArray<ts.TypeNode>}) { | 
| if (n.typeArguments) { | 
| -      // If it's a single type argument `<void>`, ignore it and emit nothing. | 
| -      // This is particularly useful for `Promise<void>`, see | 
| -      // https://github.com/dart-lang/sdk/issues/2231#issuecomment-108313639 | 
| -      if (n.typeArguments.length === 1 && n.typeArguments[0].kind === ts.SyntaxKind.VoidKeyword) { | 
| -        return; | 
| -      } | 
| this.emitNoSpace('<'); | 
| +      this.enterTypeArguments(); | 
| this.visitList(n.typeArguments); | 
| +      this.exitTypeArguments(); | 
| this.emitNoSpace('>'); | 
| } | 
| } | 
| @@ -298,16 +425,17 @@ export class TranspilerBase { | 
| } | 
| } | 
|  | 
| +    let hasValidParameters = false; | 
| if (firstInitParamIdx !== 0) { | 
| let requiredParams = parameters.slice(0, firstInitParamIdx); | 
| -      this.visitList(requiredParams); | 
| +      hasValidParameters = this.visitParameterList(requiredParams); | 
| } | 
|  | 
| if (firstInitParamIdx !== parameters.length) { | 
| -      if (firstInitParamIdx !== 0) this.emitNoSpace(','); | 
| +      if (hasValidParameters) this.emitNoSpace(','); | 
| let positionalOptional = parameters.slice(firstInitParamIdx, parameters.length); | 
| this.emit('['); | 
| -      this.visitList(positionalOptional); | 
| +      this.visitParameterList(positionalOptional); | 
| this.emitNoSpace(']'); | 
| } | 
|  | 
|  |