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

Unified Diff: lib/base.ts

Issue 2394683003: JS Interop Facade generation polish.
Patch Set: more cleanup Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « README.md ('k') | lib/declaration.ts » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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(']');
}
« no previous file with comments | « README.md ('k') | lib/declaration.ts » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698