tsd-jsdoc
Version:
Compiles JSDoc annotated javascript into a Typescript Declaration File (.d.ts).
482 lines • 20.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const ts = require("typescript");
const logger_1 = require("./logger");
const PropTree_1 = require("./PropTree");
const rgxObjectTokenize = /(<|>|,|\(|\)|\||\{|\}|:)/;
const rgxCommaAll = /,/g;
const rgxParensAll = /\(|\)/g;
const anyTypeNode = ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword);
const voidTypeNode = ts.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
const strTypeNode = ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
var ENodeType;
(function (ENodeType) {
ENodeType[ENodeType["GENERIC"] = 0] = "GENERIC";
ENodeType[ENodeType["UNION"] = 1] = "UNION";
ENodeType[ENodeType["FUNCTION"] = 2] = "FUNCTION";
ENodeType[ENodeType["TUPLE"] = 3] = "TUPLE";
ENodeType[ENodeType["TYPE"] = 4] = "TYPE";
ENodeType[ENodeType["OBJECT"] = 5] = "OBJECT";
})(ENodeType || (ENodeType = {}));
class StringTreeNode {
constructor(name, type, parent) {
this.name = name;
this.type = type;
this.parent = parent;
this.children = [];
}
dump(indent = 0) {
console.log(`${' '.repeat(indent)}name: ${this.name}, type:${this.typeToString()}`);
this.children.forEach((child) => {
child.dump(indent + 1);
});
}
typeToString() {
switch (this.type) {
case ENodeType.GENERIC:
return 'GENERIC';
case ENodeType.UNION:
return 'UNION';
case ENodeType.FUNCTION:
return 'FUNCTION';
case ENodeType.TUPLE:
return 'TUPLE';
case ENodeType.TYPE:
return 'TYPE';
case ENodeType.OBJECT:
return 'OBJECT';
default:
return 'UNKNOWN';
}
}
}
function resolveComplexTypeName(name, doclet) {
const root = generateTree(name);
if (!root) {
logger_1.warn(`failed to generate tree for ${name}, defaulting to any`);
return anyTypeNode;
}
return resolveTree(root);
}
exports.resolveComplexTypeName = resolveComplexTypeName;
function generateTree(name, parent = null) {
const anyNode = new StringTreeNode('any', ENodeType.TYPE, parent);
const parts = name.split(rgxObjectTokenize).filter(function (e) {
return e.trim() !== '';
});
for (let i = 0; i < parts.length; ++i) {
const part = parts[i].trim();
const partUpper = part.toUpperCase();
if (part.endsWith('.')) {
const matchingIndex = findMatchingBracket(parts, i + 1, '<', '>');
if (matchingIndex === -1) {
logger_1.warn(`Unable to find matching '<', '>' brackets in '${part}', defaulting to \`any\``, name);
return anyNode;
}
const node = new StringTreeNode(part.substring(0, part.length - 1), ENodeType.GENERIC, parent);
generateTree(parts.slice(i + 2, matchingIndex).join(''), node);
if (!parent)
return node;
parent.children.push(node);
i = matchingIndex + 1;
continue;
}
if (part === '(') {
const matchingIndex = findMatchingBracket(parts, i, '(', ')');
if (matchingIndex === -1) {
logger_1.warn(`Unable to find matching '(', ')' brackets in '${part}', defaulting to \`any\``, name);
return anyNode;
}
const node = new StringTreeNode('Union', ENodeType.UNION, parent);
generateTree(parts.slice(i + 1, matchingIndex).join(''), node);
if (!parent)
return node;
parent.children.push(node);
i = matchingIndex + 1;
continue;
}
if (part === '{') {
const matchingIndex = findMatchingBracket(parts, i, '{', '}');
if (matchingIndex === -1) {
logger_1.warn(`Unable to find matching '{', '}' brackets in '${part}', defaulting to \`any\``, name);
return anyNode;
}
const node = new StringTreeNode('Object', ENodeType.OBJECT, parent);
generateTree(parts.slice(i + 1, matchingIndex).join(''), node);
if (!parent)
return node;
parent.children.push(node);
i = matchingIndex + 1;
continue;
}
if (partUpper === 'FUNCTION') {
const node = new StringTreeNode(part, ENodeType.FUNCTION, parent);
let matchingIndex = findMatchingBracket(parts, i + 1, '(', ')');
if (matchingIndex === -1) {
logger_1.warn(`Unable to find matching '(', ')' brackets in '${part}', defaulting to \`any\``, name);
return anyNode;
}
if (matchingIndex > i + 2)
generateTree(parts.slice(i + 2, matchingIndex).join(''), node);
if (parts.length > matchingIndex + 2 && parts[matchingIndex + 1] === ':') {
generateTree(parts[matchingIndex + 2], node);
matchingIndex += 2;
}
else {
node.children.push(new StringTreeNode('void', ENodeType.TYPE, node));
}
if (!parent)
return node;
parent.children.push(node);
i = matchingIndex + 1;
continue;
}
if (part === '|' || part === ',' || part === ':') {
continue;
}
const node = new StringTreeNode(part, ENodeType.TYPE, parent);
if (part === '*')
node.name = 'any';
else if (partUpper === 'OBJECT')
node.name = 'object';
else if (partUpper === 'ARRAY')
node.name = 'any[]';
if (!parent)
return node;
parent.children.push(node);
}
return anyNode;
}
function findMatchingBracket(parts, startIndex, openBracket, closeBracket) {
let count = 0;
for (let i = startIndex; i < parts.length; ++i) {
if (parts[i] === openBracket) {
++count;
}
else if (parts[i] === closeBracket) {
if (--count === 0) {
return i;
}
}
}
return -1;
}
function resolveTree(node, parentTypes = null) {
const childTypes = [];
node.children.forEach((child) => resolveTree(child, childTypes));
const upperName = node.name.toUpperCase();
switch (node.type) {
case ENodeType.OBJECT:
const objectProperties = [];
for (var i = 0; i < node.children.length; i = i + 2) {
let valType = childTypes[i + 1];
if (!valType) {
logger_1.warn('Unable to resolve object value type, this is likely due to invalid JSDoc. Defaulting to \`any\`.', node);
valType = anyTypeNode;
}
const property = ts.createPropertySignature(undefined, ts.createIdentifier(node.children[i].name), undefined, valType, undefined);
objectProperties.push(property);
}
const objectNode = ts.createTypeLiteralNode(objectProperties);
ts.setEmitFlags(objectNode, ts.EmitFlags.SingleLine);
if (!parentTypes)
return objectNode;
parentTypes.push(objectNode);
break;
case ENodeType.GENERIC:
let genericNode;
if (upperName === 'OBJECT') {
let keyType = childTypes[0];
if (!keyType) {
logger_1.warn(`Unable to resolve object key type, this is likely due to invalid JSDoc. Defaulting to \`string\`.`);
keyType = strTypeNode;
}
else if (node.children[0].type !== ENodeType.TYPE || (node.children[0].name !== 'string' && node.children[0].name !== 'number')) {
logger_1.warn(`Invalid object key type. It must be \`string\` or \`number\`, but got: ${node.children[0].name}. Defaulting to \`string\`.`);
keyType = strTypeNode;
}
let valType = childTypes[1];
if (!valType) {
logger_1.warn('Unable to resolve object value type, this is likely due to invalid JSDoc. Defaulting to \`any\`.', node);
valType = anyTypeNode;
}
const indexParam = ts.createParameter(undefined, undefined, undefined, 'key', undefined, keyType, undefined);
const indexSignature = ts.createIndexSignature(undefined, undefined, [indexParam], valType);
genericNode = ts.createTypeLiteralNode([indexSignature]);
}
else if (upperName === 'ARRAY') {
let valType = childTypes[0];
if (!valType) {
logger_1.warn('Unable to resolve array value type, defaulting to \`any\`.', node);
valType = anyTypeNode;
}
genericNode = ts.createArrayTypeNode(valType);
}
else if (upperName === 'CLASS') {
let valType = childTypes[0];
if (!valType) {
logger_1.warn('Unable to resolve class value type, defaulting to \`any\`.', node);
valType = anyTypeNode;
}
genericNode = ts.createTypeQueryNode(ts.createIdentifier(node.children[0].name));
}
else {
if (childTypes.length === 0) {
logger_1.warn('Unable to resolve generic type, defaulting to \`any\`.', node);
childTypes.push(anyTypeNode);
}
if (upperName === 'PROMISE') {
while (childTypes.length > 1)
childTypes.pop();
}
genericNode = ts.createTypeReferenceNode(node.name, childTypes);
}
if (!parentTypes)
return genericNode;
parentTypes.push(genericNode);
break;
case ENodeType.UNION:
if (childTypes.length === 0) {
logger_1.warn('Unable to resolve any types for union, defaulting to \`any\`.', node);
childTypes.push(anyTypeNode);
}
const unionNode = ts.createUnionTypeNode(childTypes);
if (!parentTypes)
return unionNode;
parentTypes.push(unionNode);
break;
case ENodeType.FUNCTION:
const funcParameters = [];
if (childTypes.length === 0 || childTypes.length === 1) {
const anyArray = ts.createArrayTypeNode(anyTypeNode);
const dotDotDot = ts.createToken(ts.SyntaxKind.DotDotDotToken);
funcParameters.push(ts.createParameter(undefined, undefined, dotDotDot, 'params', undefined, anyArray, undefined));
if (childTypes.length === 0)
childTypes.push(voidTypeNode);
}
for (var i = 0; i < node.children.length - 1; ++i) {
const param = ts.createParameter(undefined, undefined, undefined, 'arg' + i, undefined, childTypes[i], undefined);
funcParameters.push(param);
}
const functionNode = ts.createFunctionTypeNode(undefined, funcParameters, childTypes[childTypes.length - 1]);
if (!parentTypes)
return functionNode;
parentTypes.push(functionNode);
break;
case ENodeType.TYPE:
const typeNode = ts.createTypeReferenceNode(node.name, undefined);
if (!parentTypes)
return typeNode;
parentTypes.push(typeNode);
break;
}
return anyTypeNode;
}
function toKeywordTypeKind(k) {
if (!k || k.length === 0)
return null;
k = k.toUpperCase();
switch (k) {
case 'ANY': return ts.SyntaxKind.AnyKeyword;
case 'UNKNOWN': return ts.SyntaxKind.UnknownKeyword;
case 'NUMBER': return ts.SyntaxKind.NumberKeyword;
case 'BIGINT': return ts.SyntaxKind.BigIntKeyword;
case 'OBJECT': return ts.SyntaxKind.ObjectKeyword;
case 'BOOLEAN': return ts.SyntaxKind.BooleanKeyword;
case 'BOOL': return ts.SyntaxKind.BooleanKeyword;
case 'STRING': return ts.SyntaxKind.StringKeyword;
case 'SYMBOL': return ts.SyntaxKind.SymbolKeyword;
case 'THIS': return ts.SyntaxKind.ThisKeyword;
case 'VOID': return ts.SyntaxKind.VoidKeyword;
case 'UNDEFINED': return ts.SyntaxKind.UndefinedKeyword;
case 'NULL': return ts.SyntaxKind.NullKeyword;
case 'NEVER': return ts.SyntaxKind.NeverKeyword;
default:
return null;
}
}
exports.toKeywordTypeKind = toKeywordTypeKind;
function resolveOptionalParameter(doclet) {
if (doclet.defaultvalue || doclet.optional)
return ts.createToken(ts.SyntaxKind.QuestionToken);
return undefined;
}
exports.resolveOptionalParameter = resolveOptionalParameter;
function resolveVariableParameter(doclet) {
if (doclet.variable)
return ts.createToken(ts.SyntaxKind.DotDotDotToken);
return undefined;
}
exports.resolveVariableParameter = resolveVariableParameter;
function resolveOptionalFromName(doclet) {
let name = doclet.name;
if (name.startsWith('[') && name.endsWith(']')) {
name = name.substring(1, name.length - 1);
return [name, ts.createToken(ts.SyntaxKind.QuestionToken)];
}
if (doclet.optional) {
return [name, ts.createToken(ts.SyntaxKind.QuestionToken)];
}
return [name, undefined];
}
exports.resolveOptionalFromName = resolveOptionalFromName;
function getExprWithTypeArgs(identifier) {
const expr = ts.createIdentifier(identifier);
return ts.createExpressionWithTypeArguments(undefined, expr);
}
function resolveHeritageClauses(doclet, onlyExtend) {
const clauses = [];
let extensions = doclet.augments || [];
if (onlyExtend) {
extensions = extensions.concat(doclet.implements || []);
extensions = extensions.concat(doclet.mixes || []);
}
if (extensions.length) {
clauses.push(ts.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, extensions.map(getExprWithTypeArgs)));
}
if (onlyExtend)
return clauses;
let implementations = (doclet.implements || []).concat(doclet.mixes || []);
if (implementations.length) {
clauses.push(ts.createHeritageClause(ts.SyntaxKind.ImplementsKeyword, implementations.map(getExprWithTypeArgs)));
}
return clauses;
}
exports.resolveHeritageClauses = resolveHeritageClauses;
function resolveTypeParameters(doclet) {
const typeParams = [];
if (doclet.tags) {
for (let i = 0; i < doclet.tags.length; ++i) {
const tag = doclet.tags[i];
if (tag.title === 'template') {
const types = (tag.text || 'T').split(',');
for (let x = 0; x < types.length; ++x) {
const name = types[x].trim();
if (!name)
continue;
typeParams.push(ts.createTypeParameterDeclaration(name, undefined, undefined));
}
}
}
}
return typeParams;
}
exports.resolveTypeParameters = resolveTypeParameters;
function resolveType(t, doclet) {
if (!t || !t.names || t.names.length === 0) {
if (doclet && doclet.properties)
return resolveTypeName('object', doclet);
if (doclet) {
logger_1.warn(`Unable to resolve type for ${doclet.longname || doclet.name}, none specified in JSDoc. Defaulting to \`any\`.`, doclet);
}
else {
logger_1.warn(`Unable to resolve type for an unnamed item, this is likely due to invalid JSDoc.` +
` Often this is caused by invalid JSDoc on a parameter. Defaulting to \`any\`.`, doclet);
}
return anyTypeNode;
}
if (t.names.length === 1) {
return resolveTypeName(t.names[0], doclet);
}
else {
const types = [];
for (let i = 0; i < t.names.length; ++i) {
types.push(resolveTypeName(t.names[i], doclet));
}
return ts.createUnionTypeNode(types);
}
}
exports.resolveType = resolveType;
function resolveTypeName(name, doclet) {
if (!name) {
logger_1.warn('Unable to resolve type name, it is null, undefined, or empty. Defaulting to \`any\`.', doclet);
return anyTypeNode;
}
if (name === '*')
return anyTypeNode;
const keyword = toKeywordTypeKind(name);
if (keyword !== null) {
if (keyword === ts.SyntaxKind.ThisKeyword)
return ts.createThisTypeNode();
if (keyword === ts.SyntaxKind.ObjectKeyword) {
if (!doclet || !doclet.properties)
return anyTypeNode;
else
return resolveTypeLiteral(doclet.properties);
}
return ts.createKeywordTypeNode(keyword);
}
const upperName = name.toUpperCase();
if (upperName === 'FUNCTION' || upperName === 'FUNCTION()') {
if (doclet && doclet.kind === 'typedef') {
const params = createFunctionParams(doclet);
const type = createFunctionReturnType(doclet);
return ts.createFunctionTypeNode(undefined, params, type);
}
else {
const anyArray = ts.createArrayTypeNode(anyTypeNode);
const dotDotDot = ts.createToken(ts.SyntaxKind.DotDotDotToken);
const param = ts.createParameter(undefined, undefined, dotDotDot, 'params', undefined, anyArray, undefined);
return ts.createFunctionTypeNode(undefined, [param], anyTypeNode);
}
}
return resolveComplexTypeName(name);
}
exports.resolveTypeName = resolveTypeName;
function resolveTypeLiteral(props) {
if (!props)
return ts.createTypeLiteralNode([]);
const tree = new PropTree_1.PropTree(props);
return createTypeLiteral(tree.roots);
}
exports.resolveTypeLiteral = resolveTypeLiteral;
function createTypeLiteral(children, parent) {
const members = [];
for (let i = 0; i < children.length; ++i) {
const node = children[i];
const opt = node.prop.optional ? ts.createToken(ts.SyntaxKind.QuestionToken) : undefined;
const t = node.children.length ? createTypeLiteral(node.children, node) : resolveType(node.prop.type);
members.push(ts.createPropertySignature(undefined, node.name, opt, t, undefined));
}
let node = ts.createTypeLiteralNode(members);
if (parent) {
const names = parent.prop.type.names;
if (names.length === 1 && names[0].toLowerCase() === 'array.<object>') {
node = ts.createArrayTypeNode(node);
}
}
return node;
}
exports.createTypeLiteral = createTypeLiteral;
function createFunctionParams(doclet) {
const params = [];
if ((doclet.kind === 'function' || doclet.kind === 'typedef') && doclet.this) {
const type = resolveType({ names: [doclet.this] }, doclet);
params.push(ts.createParameter(undefined, undefined, undefined, 'this', undefined, type, undefined));
}
if (!doclet.params || !doclet.params.length)
return params;
const tree = new PropTree_1.PropTree(doclet.params);
for (let i = 0; i < tree.roots.length; ++i) {
const node = tree.roots[i];
const opt = resolveOptionalParameter(node.prop);
const dots = resolveVariableParameter(node.prop);
let type = node.children.length ? createTypeLiteral(node.children, node) : resolveType(node.prop.type);
if (dots) {
type = ts.createArrayTypeNode(type);
}
params.push(ts.createParameter(undefined, undefined, dots, node.name, opt, type, undefined));
}
return params;
}
exports.createFunctionParams = createFunctionParams;
function createFunctionReturnType(doclet) {
if (doclet.returns && doclet.returns.length === 1) {
return resolveType(doclet.returns[0].type, doclet);
}
else {
return ts.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
}
}
exports.createFunctionReturnType = createFunctionReturnType;
//# sourceMappingURL=type_resolve_helpers.js.map