dts-gen
Version:
TypeScript Definition File Generator
382 lines • 15.3 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateIdentifierDeclarationFile = exports.generateModuleDeclarationFile = void 0;
const dom = __importStar(require("dts-dom"));
const dts_dom_1 = require("dts-dom");
const ts = __importStar(require("typescript"));
const names_1 = require("./names");
const builtins = {
Date,
RegExp,
Map: typeof Map !== "undefined" ? Map : undefined,
HTMLElement: typeof HTMLElement !== "undefined" ? HTMLElement : undefined,
};
function forceAsIdentifier(s) {
// TODO: Make this more comprehensive
return (0, names_1.getDTName)(s.replace(/-/g, "_"));
}
function getValueTypes(value) {
if (typeof value === "object") {
// Objects can't be callable, so no need to check for class / function
return 4 /* ValueTypes.Object */;
}
else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
return 8 /* ValueTypes.Primitive */;
}
else if (value === null || value === undefined) {
return 16 /* ValueTypes.NullOrUndefined */;
}
else if (typeof value === "function") {
if (isClasslike(value)) {
return 1 /* ValueTypes.Class */ | (hasCloduleProperties(value) ? 4 /* ValueTypes.Object */ : 0 /* ValueTypes.None */);
}
else {
return 2 /* ValueTypes.Function */ | (hasFunduleProperties(value) ? 4 /* ValueTypes.Object */ : 0 /* ValueTypes.None */);
}
}
else {
return 32 /* ValueTypes.Unknown */;
}
}
// A class has clodule properties if it has any classes. Anything else can be written with 'static'
function hasCloduleProperties(c) {
return getKeysOfObject(c).some((k) => isClasslike(c[k]));
}
// A function has fundule properties if it has any own properties not belonging to Function.prototype
function hasFunduleProperties(fn) {
return getKeysOfObject(fn).some((k) => Function[k] === undefined);
}
function generateModuleDeclarationFile(nameHint, root) {
const localName = forceAsIdentifier(nameHint);
const decls = getTopLevelDeclarations(localName, root);
// If we get back just a namespace, we can avoid writing an export=
if (decls.length === 1 && decls[0].kind === "namespace") {
// Hoist out all the declarations and export them
const members = decls[0].members;
for (const m of members)
m.flags = m.flags | dom.DeclarationFlags.Export;
return members.map((m) => dom.emit(m)).join("");
}
else {
// Going to have to write an export=
const result = decls.map((d) => dom.emit(d));
result.unshift(dom.emit(dom.create.exportEquals(localName)));
return result.join("");
}
}
exports.generateModuleDeclarationFile = generateModuleDeclarationFile;
function generateIdentifierDeclarationFile(name, value) {
const result = getTopLevelDeclarations(name, value);
return result.map((d) => dom.emit(d)).join("\r\n");
}
exports.generateIdentifierDeclarationFile = generateIdentifierDeclarationFile;
const walkStack = new Set();
const reservedFunctionProperties = Object.getOwnPropertyNames(() => { });
function getKeysOfObject(obj) {
let keys = [];
let chain = obj;
do {
// eslint-disable-next-line eqeqeq
if (chain == null)
break;
keys = keys.concat(Object.getOwnPropertyNames(chain));
chain = Object.getPrototypeOf(chain);
} while (chain !== Object.prototype && chain !== Function.prototype);
keys = Array.from(new Set(keys));
keys = keys.filter((s) => isVisitableName(s));
if (typeof obj === "function") {
keys = keys.filter((k) => reservedFunctionProperties.indexOf(k) < 0);
}
keys.sort();
return keys;
}
function isVisitableName(s) {
return s[0] !== "_" && ["caller", "arguments", "constructor", "super_", "prototype"].indexOf(s) < 0;
}
function isLegalIdentifier(s) {
if (s.length === 0) {
return false;
}
if (!ts.isIdentifierStart(s.charCodeAt(0), ts.ScriptTarget.Latest)) {
return false;
}
for (let i = 1; i < s.length; i++) {
if (!ts.isIdentifierPart(s.charCodeAt(i), ts.ScriptTarget.Latest)) {
return false;
}
}
return dts_dom_1.reservedWords.indexOf(s) < 0;
}
function isClasslike(obj) {
return !!(obj.prototype && Object.getOwnPropertyNames(obj.prototype).length > 1);
}
const keyStack = [];
function getTopLevelDeclarations(name, obj) {
if (walkStack.has(obj) || keyStack.length > 4) {
// Circular or too-deep reference
const result = dts_dom_1.create.const(name, dom.type.any);
result.comment =
(walkStack.has(obj) ? "Circular reference" : "Too-deep object hierarchy") + ` from ${keyStack.join(".")}`;
return [result];
}
if (!isLegalIdentifier(name))
return [];
walkStack.add(obj);
keyStack.push(name);
const res = getResult();
keyStack.pop();
walkStack.delete(obj);
return res;
function getResult() {
if (typeof obj === "function") {
const funcType = getParameterListAndReturnType(obj, parseFunctionBody(obj));
const ns = dom.create.namespace(name);
let primaryDecl;
if (isClasslike(obj)) {
const cls = dom.create.class(name);
getClassPrototypeMembers(obj).forEach((m) => cls.members.push(m));
cls.members.push(dom.create.constructor(funcType[0]));
cls.members.sort(declarationComparer);
primaryDecl = cls;
}
else {
const parsedFunction = parseFunctionBody(obj);
const info = getParameterListAndReturnType(obj, parsedFunction);
primaryDecl = dom.create.function(name, info[0], info[1]);
}
// Get clodule/fundule members
const keys = getKeysOfObject(obj);
for (const k of keys) {
getTopLevelDeclarations(k, obj[k]).forEach((p) => {
if (primaryDecl.kind === "class") {
// Transform certain declarations into static members
switch (p.kind) {
case "const":
primaryDecl.members.push(dts_dom_1.create.property(p.name, p.type, dom.DeclarationFlags.Static));
break;
case "function":
primaryDecl.members.push(dts_dom_1.create.method(p.name, p.parameters, p.returnType, dom.DeclarationFlags.Static));
break;
default:
ns.members.push(p);
break;
}
}
else {
ns.members.push(p);
}
});
ns.members.sort(declarationComparer);
}
return ns.members.length > 0 ? [primaryDecl, ns] : [primaryDecl];
}
else if (typeof obj === "object") {
// If we can immediately resolve this to a simple declaration, just do so
const simpleType = getTypeOfValue(obj);
if (typeof simpleType === "string" || simpleType.kind === "name" || simpleType.kind === "array") {
const result = dom.create.const(name, simpleType);
if (simpleType === "string") {
const preview = `"${simpleType.substr(0, 100)}${simpleType.length > 100 ? "..." : ""}"`;
result.comment = "Value of string: " + preview;
}
return [result];
}
// If anything in here is classlike or functionlike, write it as a namespace.
// Otherwise, write as a 'const'
const keys = getKeysOfObject(obj);
let constituentTypes = 0 /* ValueTypes.None */;
for (const k of keys) {
constituentTypes = constituentTypes | getValueTypes(obj[k]);
}
if (constituentTypes & (1 /* ValueTypes.Class */ | 2 /* ValueTypes.Function */)) {
const ns = dom.create.namespace(name);
for (const k of keys) {
const decls = getTopLevelDeclarations(k, obj[k]);
decls.forEach((d) => ns.members.push(d));
}
ns.members.sort(declarationComparer);
return [ns];
}
else {
return [dom.create.const(name, simpleType)];
}
}
else if (typeof obj === "string" || typeof obj === "number" || typeof obj === "boolean") {
return [dts_dom_1.create.const(name, typeof obj)];
}
else {
return [dts_dom_1.create.const(name, dom.type.any)];
}
}
}
function getTypeOfValue(value) {
for (const k in builtins) {
if (builtins[k] && value instanceof builtins[k]) {
return dts_dom_1.create.namedTypeReference(k);
}
}
if (Array.isArray(value)) {
if (value.length > 0) {
return dts_dom_1.create.array(getTypeOfValue(value[0]));
}
else {
return dts_dom_1.create.array(dom.type.any);
}
}
const type = typeof value;
switch (type) {
case "string":
case "number":
case "boolean":
return type;
case "undefined":
return dom.type.any;
case "object":
if (value === null) {
return dom.type.any;
}
else {
walkStack.add(value);
const members = getPropertyDeclarationsOfObject(value);
walkStack.delete(value);
members.sort(declarationComparer);
const objType = dom.create.objectType(members);
return objType;
}
default:
return dom.type.any;
}
}
function getPropertyDeclarationsOfObject(obj) {
walkStack.add(obj);
const keys = getKeysOfObject(obj);
const result = keys.map(getProperty);
walkStack.delete(obj);
return result;
function getProperty(k) {
if (walkStack.has(obj[k])) {
return dts_dom_1.create.property(k, dom.type.any);
}
return dts_dom_1.create.property(k, getTypeOfValue(obj[k]));
}
}
function getClassPrototypeMembers(ctor) {
const names = Object.getOwnPropertyNames(ctor.prototype);
const members = names
.filter((n) => !isNameToSkip(n))
.map((name) => getPrototypeMember(name, Object.getOwnPropertyDescriptor(ctor.prototype, name).value))
.filter((m) => m !== undefined);
members.sort();
return members;
function getPrototypeMember(name, obj) {
// Skip non-function objects on the prototype (not sure what to do with these?)
if (typeof obj !== "function") {
return undefined;
}
const funcType = getParameterListAndReturnType(obj, parseFunctionBody(obj));
const result = dts_dom_1.create.method(name, funcType[0], funcType[1]);
if (isNativeFunction(obj)) {
result.comment = "Native method; no parameter or return type inference available";
}
return result;
}
function isNameToSkip(s) {
return s === "constructor" || s[0] === "_";
}
}
function declarationComparer(left, right) {
if (left.kind === right.kind) {
return left.name > right.name ? 1 : left.name < right.name ? -1 : 0;
}
else {
return left.kind > right.kind ? 1 : left.kind < right.kind ? -1 : 0;
}
}
function getParameterListAndReturnType(obj, fn) {
let usedArguments = false;
let hasReturn = false;
const funcStack = [];
if (isNativeFunction(obj)) {
const args = [];
for (let i = 0; i < obj.length; i++) {
args.push(dts_dom_1.create.parameter(`p${i}`, dom.type.any));
}
return [args, dom.type.any];
}
else {
ts.forEachChild(fn, visit);
let params = [dts_dom_1.create.parameter("args", dom.type.array(dom.type.any), dom.ParameterFlags.Rest)];
if (fn.parameters) {
params = fn.parameters.map((p) => dts_dom_1.create.parameter(`${p.name.getText()}`, inferParameterType(fn, p)));
if (usedArguments) {
params.push(dts_dom_1.create.parameter("args", dom.type.array(dom.type.any), dom.ParameterFlags.Rest));
}
}
return [params, hasReturn ? dom.type.any : dom.type.void];
}
function visit(node) {
switch (node.kind) {
case ts.SyntaxKind.Identifier:
if (node.getText() === "arguments") {
usedArguments = true;
}
break;
case ts.SyntaxKind.ReturnStatement:
const ret = node;
if (funcStack.length === 0 && ret.expression && ret.expression.kind !== ts.SyntaxKind.VoidExpression) {
hasReturn = true;
}
}
switch (node.kind) {
case ts.SyntaxKind.FunctionExpression:
case ts.SyntaxKind.FunctionDeclaration:
funcStack.push(true);
ts.forEachChild(node, visit);
funcStack.pop();
break;
default:
ts.forEachChild(node, visit);
break;
}
}
}
function inferParameterType(_fn, _param) {
// TODO: Inspect function body for clues
return dom.type.any;
}
const stringifyFunction = Function.prototype.call.bind(Function.prototype.toString);
function parseFunctionBody(fn) {
const setup = `const myFn = ${stringifyFunction(fn)};`;
const srcFile = ts.createSourceFile("test.ts", setup, ts.ScriptTarget.Latest, true);
const statement = srcFile.statements[0];
const decl = statement.declarationList.declarations[0];
const init = decl.initializer;
return init;
}
function isNativeFunction(fn) {
return stringifyFunction(fn).indexOf("{ [native code] }") > 0;
}
//# sourceMappingURL=index.js.map
;