in src/externs.ts [406:516]
function writeType(
decl: ts.InterfaceDeclaration|ts.ClassDeclaration, namespace: ReadonlyArray<string>) {
const name = decl.name;
if (!name) {
reportDiagnostic(diagnostics, decl, 'anonymous type in externs');
return;
}
const typeName = namespace.concat([name.getText()]).join('.');
if (PREDECLARED_CLOSURE_EXTERNS_LIST.indexOf(typeName) >= 0) return;
if (isFirstValueDeclaration(decl)) {
// Emit the 'function' that is actually the declaration of the interface
// itself. If it's a class, this function also must include the type
// annotations of the constructor.
let paramNames: string[] = [];
const jsdocTags: jsdoc.Tag[] = [];
let wroteJsDoc = false;
maybeAddHeritageClauses(jsdocTags, mtt, decl);
maybeAddTemplateClause(jsdocTags, decl);
if (decl.kind === ts.SyntaxKind.ClassDeclaration) {
// TODO: it appears you can just write 'class Foo { ...' in externs.
// This code instead tries to translate it to a function.
jsdocTags.push({tagName: 'constructor'}, {tagName: 'struct'});
const ctors = (decl as ts.ClassDeclaration)
.members.filter((m) => m.kind === ts.SyntaxKind.Constructor);
if (ctors.length) {
const firstCtor: ts.ConstructorDeclaration = ctors[0] as ts.ConstructorDeclaration;
if (ctors.length > 1) {
paramNames = emitFunctionType(ctors as ts.ConstructorDeclaration[], jsdocTags);
} else {
paramNames = emitFunctionType([firstCtor], jsdocTags);
}
wroteJsDoc = true;
}
} else {
// Otherwise it's an interface; tag it as structurally typed.
jsdocTags.push({tagName: 'record'}, {tagName: 'struct'});
}
if (!wroteJsDoc) emit(jsdoc.toString(jsdocTags));
writeFunction(name, paramNames, namespace);
}
// Process everything except (MethodSignature|MethodDeclaration|Constructor)
const methods = new Map<string, ts.MethodDeclaration[]>();
for (const member of decl.members) {
switch (member.kind) {
case ts.SyntaxKind.PropertySignature:
case ts.SyntaxKind.PropertyDeclaration:
const prop = member as ts.PropertySignature;
if (prop.name.kind === ts.SyntaxKind.Identifier) {
let type = mtt.typeToClosure(prop);
if (prop.questionToken && type === '?') {
// An optional 'any' type translates to '?|undefined' in Closure.
type = '?|undefined';
}
emit(jsdoc.toString([{tagName: 'type', type}]));
if (hasModifierFlag(prop, ts.ModifierFlags.Static)) {
emit(`\n${typeName}.${prop.name.getText()};\n`);
} else {
emit(`\n${typeName}.prototype.${prop.name.getText()};\n`);
}
continue;
}
// TODO: For now property names other than Identifiers are not handled; e.g.
// interface Foo { "123bar": number }
break;
case ts.SyntaxKind.MethodSignature:
case ts.SyntaxKind.MethodDeclaration:
const method = member as ts.MethodDeclaration;
const isStatic = hasModifierFlag(method, ts.ModifierFlags.Static);
const methodSignature = `${method.name.getText()}$$$${isStatic ? 'static' : 'instance'}`;
if (methods.has(methodSignature)) {
methods.get(methodSignature)!.push(method);
} else {
methods.set(methodSignature, [method]);
}
continue;
case ts.SyntaxKind.Constructor:
continue; // Handled above.
default:
// Members can include things like index signatures, for e.g.
// interface Foo { [key: string]: number; }
// For now, just skip it.
break;
}
// If we get here, the member wasn't handled in the switch statement.
let memberName = namespace;
if (member.name) {
memberName = memberName.concat([member.name.getText()]);
}
emit(`\n/* TODO: ${ts.SyntaxKind[member.kind]}: ${memberName.join('.')} */\n`);
}
// Handle method declarations/signatures separately, since we need to deal with overloads.
for (const methodVariants of Array.from(methods.values())) {
const firstMethodVariant = methodVariants[0];
let parameterNames: string[];
if (methodVariants.length > 1) {
parameterNames = emitFunctionType(methodVariants);
} else {
parameterNames = emitFunctionType([firstMethodVariant]);
}
const methodNamespace = namespace.concat([name.getText()]);
// If the method is static, don't add the prototype.
if (!hasModifierFlag(firstMethodVariant, ts.ModifierFlags.Static)) {
methodNamespace.push('prototype');
}
writeFunction(firstMethodVariant.name, parameterNames, methodNamespace);
}
}