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

Unified Diff: lib/merge.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 | « lib/main.ts ('k') | lib/module.ts » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: lib/merge.ts
diff --git a/lib/merge.ts b/lib/merge.ts
index 0b9c8d746496b1608dd7eb08d7b35a25735520ed..49180029a4467d5b680f36e0e183f9e4a1567df0 100644
--- a/lib/merge.ts
+++ b/lib/merge.ts
@@ -14,12 +14,18 @@ export class MergedType {
if (t) {
// TODO(jacobr): get a better unique name for a type.
switch (t.kind) {
+ case ts.SyntaxKind.NullKeyword:
+ // No need to include the null type as all Dart types are nullable anyway.
+ return;
case ts.SyntaxKind.UnionType:
let union = <ts.UnionTypeNode>t;
union.types.forEach(this.merge.bind(this));
return;
- case ts.SyntaxKind.LastTypeNode:
- this.merge((t as ts.ParenthesizedTypeNode).type);
+ case ts.SyntaxKind.IntersectionType:
+ // Arbitrarily pick the first type of the intersection type as the merged type.
+ // TODO(jacobr): re-evaluate this logic.
+ let intersection = <ts.IntersectionTypeNode>t;
+ this.merge(intersection.types[0]);
return;
case ts.SyntaxKind.TypePredicate:
this.merge((t as ts.TypePredicateNode).type);
@@ -31,19 +37,21 @@ export class MergedType {
let decl = this.fc.getDeclaration(typeRef.typeName);
if (decl !== null && decl.kind === ts.SyntaxKind.TypeAliasDeclaration) {
let alias = <ts.TypeAliasDeclaration>decl;
+ if (!base.supportedAliasType(alias)) {
+ if (typeRef.typeArguments) {
+ console.log(
+ 'Warning: typeReference with arguements not supported yet:' + t.getText());
+ }
- if (typeRef.typeArguments) {
- throw 'TypeReference with arguements not supported yet:' + t.getText();
+ this.merge(alias.type);
}
-
- this.merge(alias.type);
return;
}
break;
default:
break;
}
- this.types[this.fc.generateDartTypeName(t, true)] = t;
+ this.types[this.fc.generateDartTypeName(t, {insideComment: true})] = t;
}
}
@@ -109,9 +117,91 @@ export class MergedParameter {
}
/**
+ * Handle a parameter that is the result of merging parameter declarations from
+ * multiple method overloads.
+ */
+export class MergedTypeParameter {
+ constructor(param: ts.TypeParameterDeclaration, fc: FacadeConverter) {
+ this.constraint = new MergedType(fc);
+ this.textRange = param;
+ this.merge(param);
+ this.name = base.ident(param.name);
+ }
+
+ merge(param: ts.TypeParameterDeclaration) {
+ this.constraint.merge(param.constraint);
+ // We ignore param.expression as it is not supported by Dart.
+ }
+
+ toTypeParameterDeclaration(): ts.TypeParameterDeclaration {
+ let ret = <ts.TypeParameterDeclaration>ts.createNode(ts.SyntaxKind.TypeParameter);
+ let nameIdentifier = <ts.Identifier>ts.createNode(ts.SyntaxKind.Identifier);
+ nameIdentifier.text = this.name;
+ ret.name = nameIdentifier;
+ base.copyLocation(this.textRange, ret);
+ let constraint = this.constraint.toTypeNode();
+ // TODO(jacobr): remove this check once we have support for union types within comments.
+ // We can't currently handle union types in merged type parameters as the comments for type
+ // parameters in function types are not there for documentation and impact strong mode.
+ if (constraint && constraint.kind !== ts.SyntaxKind.UnionType) {
+ ret.constraint = constraint;
+ }
+ return ret;
+ }
+
+ private name: string;
+ private constraint: MergedType;
+ private textRange: ts.TextRange;
+}
+
+/**
+ * Handle a parameter that is the result of merging parameter declarations from
+ * multiple method overloads.
+ */
+export class MergedTypeParameters {
+ private mergedParameters: {[s: string]: MergedTypeParameter} = {};
+ private textRange: ts.TextRange;
+
+ constructor(private fc: FacadeConverter) {}
+
+ merge(params: ts.NodeArray<ts.TypeParameterDeclaration>) {
+ if (!params) return;
+ if (!this.textRange) {
+ this.textRange = params;
+ }
+ for (let i = 0; i < params.length; i++) {
+ let param = params[i];
+ let name = base.ident(param.name);
+ if (Object.hasOwnProperty.call(this.mergedParameters, name)) {
+ let merged = this.mergedParameters[name];
+ if (merged) {
+ merged.merge(param);
+ }
+ } else {
+ this.mergedParameters[name] = new MergedTypeParameter(param, this.fc);
+ }
+ }
+ }
+
+ toTypeParameters(): ts.NodeArray<ts.TypeParameterDeclaration> {
+ let names = Object.getOwnPropertyNames(this.mergedParameters);
+ if (names.length === 0) {
+ return undefined;
+ }
+
+ let ret = [] as ts.NodeArray<ts.TypeParameterDeclaration>;
+ base.copyLocation(this.textRange, ret);
+ for (let i = 0; i < names.length; ++i) {
+ ret.push(this.mergedParameters[names[i]].toTypeParameterDeclaration());
+ }
+ return ret;
+ }
+}
+
+/**
* Normalize a SourceFile
*/
-export function normalizeSourceFile(f: ts.SourceFile) {
+export function normalizeSourceFile(f: ts.SourceFile, fc: FacadeConverter) {
let modules: {[name: string]: ts.ModuleDeclaration} = {};
// Merge top level modules.
@@ -120,7 +210,7 @@ export function normalizeSourceFile(f: ts.SourceFile) {
if (statement.kind !== ts.SyntaxKind.ModuleDeclaration) continue;
let moduleDecl = <ts.ModuleDeclaration>statement;
let name = moduleDecl.name.text;
- if (modules.hasOwnProperty(name)) {
+ if (Object.hasOwnProperty.call(modules, name)) {
let srcBody = modules[name].body;
let srcBodyBlock: ts.ModuleBlock;
@@ -162,13 +252,78 @@ export function normalizeSourceFile(f: ts.SourceFile) {
declaration: ts.VariableDeclaration) {
if (declaration.name.kind === ts.SyntaxKind.Identifier) {
let name: string = (<ts.Identifier>(declaration.name)).text;
- if (classes.hasOwnProperty(name)) {
+ let existingClass = Object.hasOwnProperty.call(classes, name);
+ let hasConstructor = false;
+ if (declaration.type) {
+ let type: ts.TypeNode = declaration.type;
+ if (type.kind === ts.SyntaxKind.TypeLiteral) {
+ let literal = <ts.TypeLiteralNode>type;
+ hasConstructor = literal.members.some((member: ts.Node) => {
+ return member.kind === ts.SyntaxKind.ConstructSignature;
+ });
+ } else if (type.kind === ts.SyntaxKind.TypeReference) {
+ // Handle interfaces with constructors. As Dart does not support calling arbitrary
+ // functions like constructors we need to upgrade the interface to be a class
+ // so we call invoke the constructor on the interface class.
+ // Example typescript library definition matching this pattern:
+ //
+ // interface XStatic {
+ // new (a: string, b): XStatic;
+ // foo();
+ // }
+ //
+ // declare var X: XStatic;
+ //
+ // In JavaScript you could just write new X() and create an
+ // instance of XStatic. We don't
+ let typeRef = type as ts.TypeReferenceNode;
+ let typeName = typeRef.typeName;
+ let symbol = fc.tc.getSymbolAtLocation(typeName);
+ if (symbol == null) return;
+ let decl = fc.getSymbolDeclaration(symbol, typeName);
+ if (decl == null) return;
+ if (decl.kind !== ts.SyntaxKind.InterfaceDeclaration) return;
+ let interfaceDecl = decl as base.ExtendedInterfaceDeclaration;
+ if (!interfaceDecl.members.some(
+ (member) => { return member.kind === ts.SyntaxKind.ConstructSignature; }))
+ return;
+
+ if (interfaceDecl.classLikeVariableDeclaration == null) {
+ // We could add extra logic to be safer such as only infering that variable names
+ // are class like for cases where variable names are UpperCamelCase matching JS
+ // conventions that a variable is a Class definition.
+ interfaceDecl.classLikeVariableDeclaration = declaration;
+ }
+ }
+ }
+
+ if (existingClass || hasConstructor) {
+ if (!existingClass) {
+ // Create a stub existing class to upgrade the object literal to if there is not an
+ // existing class with the same name.
+ let clazz = <ts.ClassDeclaration>ts.createNode(ts.SyntaxKind.ClassDeclaration);
+ base.copyLocation(declaration, clazz);
+ clazz.name = declaration.name as ts.Identifier;
+ clazz.members = <ts.NodeArray<ts.ClassElement>>[];
+ base.copyLocation(declaration, clazz.members);
+ replaceNode(n, clazz);
+ classes[name] = clazz;
+ }
+
let existing = classes[name];
+ if (existing.kind === ts.SyntaxKind.InterfaceDeclaration) {
+ let interfaceDecl = existing as base.ExtendedInterfaceDeclaration;
+ // It is completely safe to assume that we know the precise class like variable
+ // declaration for the interface in this case as they have the same exact name.
+ interfaceDecl.classLikeVariableDeclaration = declaration;
+ }
let members = existing.members as Array<ts.ClassElement>;
if (declaration.type) {
let type: ts.TypeNode = declaration.type;
if (type.kind === ts.SyntaxKind.TypeLiteral) {
- removeNode(n);
+ if (existingClass) {
+ removeNode(n);
+ }
let literal = <ts.TypeLiteralNode>type;
literal.members.forEach((member: ts.Node) => {
switch (member.kind) {
@@ -249,37 +404,29 @@ export function normalizeSourceFile(f: ts.SourceFile) {
}
}
- function makeCallableClassesImplementFunction(decl: base.ClassLike) {
- if (base.isCallable(decl)) {
- // Modify the AST to explicitly state that the class implements Function
- if (!decl.heritageClauses) {
- decl.heritageClauses = <ts.NodeArray<ts.HeritageClause>>[];
- base.copyLocation(decl, decl.heritageClauses);
- }
- let clauses = decl.heritageClauses;
- let clause = base.arrayFindPolyfill(
- clauses, (c) => c.token !== ts.SyntaxKind.ExtendsKeyword ||
- decl.kind === ts.SyntaxKind.InterfaceDeclaration);
- if (clause == null) {
- clause = <ts.HeritageClause>ts.createNode(ts.SyntaxKind.HeritageClause);
- clause.token = decl.kind === ts.SyntaxKind.InterfaceDeclaration ?
- ts.SyntaxKind.ExtendsKeyword :
- ts.SyntaxKind.ImplementsKeyword;
- clause.types = <ts.NodeArray<ts.ExpressionWithTypeArguments>>[];
- clause.parent = decl;
- base.copyLocation(decl, clause);
- clauses.push(clause);
+ function replaceInArray(nodes: ts.NodeArray<ts.Node>, v: ts.Node, replacement: ts.Node) {
+ for (let i = 0, len = nodes.length; i < len; ++i) {
+ if (nodes[i] === v) {
+ nodes[i] = replacement;
+ break;
}
- let functionType =
- <ts.ExpressionWithTypeArguments>ts.createNode(ts.SyntaxKind.ExpressionWithTypeArguments);
- functionType.parent = clause;
- base.copyLocation(clause, functionType);
- let fn = <ts.Identifier>ts.createNode(ts.SyntaxKind.Identifier);
- fn.text = 'Function';
- fn.parent = functionType;
- base.copyLocation(functionType, fn);
- functionType.expression = fn;
- clause.types.push(functionType);
+ }
+ }
+
+ function replaceNode(n: ts.Node, replacement: ts.Node) {
+ let parent = n.parent;
+ replacement.parent = parent;
+ switch (parent.kind) {
+ case ts.SyntaxKind.ModuleBlock:
+ let block = <ts.ModuleBlock>parent;
+ replaceInArray(block.statements, n, replacement);
+ break;
+ case ts.SyntaxKind.SourceFile:
+ let sourceFile = <ts.SourceFile>parent;
+ replaceInArray(sourceFile.statements, n, replacement);
+ break;
+ default:
+ throw 'replaceNode not implemented for kind:' + parent.kind;
}
}
@@ -291,7 +438,7 @@ export function normalizeSourceFile(f: ts.SourceFile) {
let name = classDecl.name.text;
// TODO(jacobr): validate that the classes have consistent
// modifiers, etc.
- if (classes.hasOwnProperty(name)) {
+ if (Object.hasOwnProperty.call(classes, name)) {
let existing = classes[name];
(classDecl.members as Array<ts.ClassElement>).forEach((e: ts.ClassElement) => {
(existing.members as Array<ts.ClassElement>).push(e);
@@ -301,7 +448,6 @@ export function normalizeSourceFile(f: ts.SourceFile) {
} else {
classes[name] = classDecl;
// Perform other class level post processing here.
- makeCallableClassesImplementFunction(classDecl);
}
break;
case ts.SyntaxKind.ModuleDeclaration:
« no previous file with comments | « lib/main.ts ('k') | lib/module.ts » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698