Comparing version 2.0.0-beta.6 to 2.0.0
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const dom = require("dts-dom"); | ||
const fs = require("fs"); | ||
const path = require("path"); | ||
const ts = require("typescript"); | ||
const logger_1 = require("./logger"); | ||
const rgxArrayType = /^Array(?:\.<(.*)>)?$/; | ||
const rgxObjectType = /^Object\.<(\w*),\s*\(?(.*)\)?>$/; | ||
const rgxJsDocHeader = /^\/\*\*\s?/; | ||
const rgxJsDocFooter = /\s*\*\/\s?$/; | ||
const rgxJsDocBody = /^\*\s?/; | ||
const RAW_GLOBAL_TYPES = JSON.parse(fs.readFileSync(path.join(__dirname, '../global-types.json')).toString()); | ||
const GLOBAL_TYPES = Object.keys(RAW_GLOBAL_TYPES) | ||
.reduce((acc, key) => { | ||
RAW_GLOBAL_TYPES[key].forEach((t) => { | ||
acc[t] = dom.create.namedTypeReference(t); | ||
}); | ||
return acc; | ||
}, {}); | ||
const accessFlagMap = { | ||
public: dom.DeclarationFlags.None, | ||
private: dom.DeclarationFlags.Private, | ||
protected: dom.DeclarationFlags.Protected, | ||
}; | ||
const assert_never_1 = require("./assert_never"); | ||
const create_helpers_1 = require("./create_helpers"); | ||
function isClassLike(doclet) { | ||
return doclet.kind === 'class' || doclet.kind === 'interface'; | ||
} | ||
function isModuleLike(doclet) { | ||
return doclet.kind === 'module' || doclet.kind === 'namespace'; | ||
} | ||
function isEnum(doclet) { | ||
return doclet.kind === 'member' || doclet.kind === 'constant' && doclet.isEnum; | ||
} | ||
class Emitter { | ||
constructor(docs, config, eol = '\n') { | ||
this.config = config; | ||
this.eol = eol; | ||
this.parse(docs); | ||
constructor(allowPrivate) { | ||
this.allowPrivate = allowPrivate; | ||
this.results = []; | ||
this._treeRoots = []; | ||
this._treeNodes = {}; | ||
} | ||
parse(docs) { | ||
this.results = []; | ||
this.objects = Object.assign({}, GLOBAL_TYPES); | ||
if (!docs) { | ||
this._treeRoots = []; | ||
this._treeNodes = {}; | ||
if (!docs) | ||
return; | ||
} | ||
this._parseObjects(docs); | ||
this._resolveObjects(docs); | ||
this._resolveInterfaceMembers(docs); | ||
this._resolveModules(this.results); | ||
this._createTreeNodes(docs); | ||
this._buildTree(docs); | ||
this._parseTree(); | ||
} | ||
emit() { | ||
dom.config.outputEol = this.eol; | ||
let out = ''; | ||
for (const res of this.results) { | ||
out += dom.emit(res); | ||
const resultFile = ts.createSourceFile('types.d.ts', '', ts.ScriptTarget.Latest, false, ts.ScriptKind.TS); | ||
const printer = ts.createPrinter({ | ||
removeComments: false, | ||
newLine: ts.NewLineKind.LineFeed, | ||
}); | ||
let out2 = ''; | ||
for (let i = 0; i < this.results.length; ++i) { | ||
out2 += printer.printNode(ts.EmitHint.Unspecified, this.results[i], resultFile); | ||
out2 += '\n\n'; | ||
} | ||
return out; | ||
return out2; | ||
} | ||
_parseObjects(docs) { | ||
for (const doclet of docs) { | ||
if (!this._shouldResolveDoclet(doclet)) { | ||
_createTreeNodes(docs) { | ||
for (let i = 0; i < docs.length; ++i) { | ||
const doclet = docs[i]; | ||
if (doclet.kind === 'package' || this._ignoreDoclet(doclet)) | ||
continue; | ||
if (!this._treeNodes[doclet.longname]) { | ||
this._treeNodes[doclet.longname] = { doclet, children: [] }; | ||
} | ||
if (this.objects[doclet.longname] && !doclet.override) { | ||
continue; | ||
} | ||
switch (doclet.kind) { | ||
case 'class': | ||
this._createClass(doclet); | ||
break; | ||
case 'constant': | ||
this._createMember(doclet); | ||
break; | ||
case 'function': | ||
this._createFunction(doclet); | ||
break; | ||
case 'interface': | ||
this._createInterface(doclet); | ||
break; | ||
case 'member': | ||
this._createMember(doclet); | ||
break; | ||
case 'mixin': | ||
this._createInterface(doclet); | ||
break; | ||
case 'module': | ||
this._createNamespace(doclet); | ||
break; | ||
case 'namespace': | ||
this._createNamespace(doclet); | ||
break; | ||
case 'typedef': | ||
this._createTypedef(doclet); | ||
break; | ||
} | ||
const obj = this.objects[doclet.longname]; | ||
if (!obj) { | ||
continue; | ||
} | ||
obj.jsDocComment = cleanComment(doclet.comment); | ||
handleCustomTags(doclet, obj); | ||
if (!doclet.memberof) { | ||
this.results.push(obj); | ||
} | ||
} | ||
} | ||
_resolveObjects(docs) { | ||
for (const doclet of docs) { | ||
if (!this._shouldResolveDoclet(doclet)) { | ||
_buildTree(docs) { | ||
for (let i = 0; i < docs.length; ++i) { | ||
const doclet = docs[i]; | ||
if (doclet.kind === 'package' || this._ignoreDoclet(doclet)) | ||
continue; | ||
} | ||
const obj = this.objects[doclet.longname]; | ||
const obj = this._treeNodes[doclet.longname]; | ||
if (!obj) { | ||
logger_1.warnResolve(doclet, 1); | ||
logger_1.warn('Failed to find doclet node when building tree, this is likely a bug.', doclet); | ||
continue; | ||
} | ||
if (doclet.memberof && !doclet.inherited) { | ||
const objMember = obj; | ||
const p = this.objects[doclet.memberof]; | ||
if (!p) { | ||
logger_1.warnResolve(doclet, 0, 'No such name found.'); | ||
const interfaceLongname = this._getInterfaceKey(doclet.longname); | ||
let interfaceMerge = null; | ||
if (doclet.kind === 'class') { | ||
const impls = doclet.implements || []; | ||
const mixes = doclet.mixes || []; | ||
const extras = impls.concat(mixes); | ||
if (extras.length) { | ||
interfaceMerge = this._treeNodes[interfaceLongname] = { | ||
doclet: { | ||
kind: 'interface', | ||
name: doclet.name, | ||
scope: doclet.scope, | ||
longname: interfaceLongname, | ||
augments: extras, | ||
memberof: doclet.memberof, | ||
}, | ||
children: [], | ||
}; | ||
} | ||
else if (!p.members) { | ||
logger_1.warnResolve(doclet, 0, `Found parent, but it cannot contain members. Discovered type: ${p.kind}.`); | ||
} | ||
else { | ||
if (objMember.kind === 'function' && p.kind !== 'namespace') { | ||
objMember.kind = 'method'; | ||
} | ||
let isDuplicate = false; | ||
for (const member of p.members) { | ||
if (member._doclet.longname === objMember._doclet.longname) { | ||
isDuplicate = true; | ||
break; | ||
} | ||
} | ||
if (isDuplicate) { | ||
continue; | ||
} | ||
obj._parent = p; | ||
p.members.push(objMember); | ||
} | ||
} | ||
if (doclet.kind === 'typedef' && obj.type) { | ||
obj.type._parent = obj; | ||
} | ||
if (doclet.kind === 'function' | ||
|| (doclet.kind === 'typedef' && obj.type && obj.type.kind === 'function-type')) { | ||
const d = doclet; | ||
const o = (doclet.kind === 'typedef' ? obj.type : obj); | ||
if (doclet.params) { | ||
o.parameters = this._resolveFunctionParams(doclet.params); | ||
} | ||
for (const param of o.parameters) { | ||
param._parent = o; | ||
} | ||
if (!d.returns) { | ||
o.returnType = dom.type.void; | ||
} | ||
else if (!d.returns[0].type) { | ||
logger_1.warnResolve(d, 5, `Type is not well-formed, defaulting to any.`); | ||
o.returnType = dom.type.any; | ||
} | ||
else { | ||
o.returnType = this._resolveType(d.returns[0], o); | ||
} | ||
} | ||
if (doclet.kind === 'typedef' && obj.type && obj.type.kind === 'object') { | ||
if (doclet.properties) { | ||
obj.type.members = handleNestedProperties(doclet.properties).map((pDoc) => { | ||
let propType; | ||
if ((Object.keys(pDoc.props).length)) { | ||
propType = dom.create.property(pDoc.meta.name, this._walkNestedProps(pDoc), handleFlags(pDoc.meta)); | ||
} | ||
else { | ||
propType = dom.create.property(pDoc.meta.name, null, handleFlags(pDoc.meta)); | ||
propType.type = this._resolveType(pDoc.meta, propType); | ||
} | ||
propType.jsDocComment = cleanComment(pDoc.meta.comment); | ||
propType._doclet = doclet; | ||
return propType; | ||
}); | ||
} | ||
obj.type.members.forEach((m) => { | ||
m._parent = obj.type; | ||
}); | ||
} | ||
if (doclet.kind === 'typedef' && !obj.type) { | ||
obj.type = this._resolveType(doclet, obj); | ||
} | ||
if (doclet.kind === 'member' || doclet.kind === 'constant') { | ||
if (!doclet.type && !doclet.properties) { | ||
if (doclet.memberof) { | ||
const parent = this._treeNodes[doclet.memberof]; | ||
if (!parent) { | ||
logger_1.warn(`Failed to find parent of doclet '${doclet.longname}' using memberof '${doclet.memberof}', this is likely due to invalid JSDoc.`, doclet); | ||
continue; | ||
} | ||
obj.type = this._resolveType(doclet, obj); | ||
} | ||
if (doclet.kind === 'class' || doclet.kind === 'mixin' || doclet.kind === 'interface') { | ||
const o = obj; | ||
let ctorObj = null; | ||
if (o.members && typeof o.members[Symbol.iterator] === 'function') { | ||
for (const member of o.members) { | ||
if (member.kind === 'constructor') { | ||
ctorObj = member; | ||
} | ||
} | ||
const isObjClassLike = isClassLike(doclet); | ||
const isParentClassLike = isClassLike(parent.doclet); | ||
if (isParentClassLike && (isObjClassLike || doclet.kind === 'typedef')) { | ||
const mod = this._getOrCreateClassModule(parent); | ||
if (interfaceMerge) | ||
mod.children.push(interfaceMerge); | ||
mod.children.push(obj); | ||
} | ||
else { | ||
logger_1.warn(`No members specified for ${doclet.kind} ${doclet.name} in ${doclet.meta.filename}`); | ||
} | ||
if (ctorObj) { | ||
if (doclet.params) { | ||
ctorObj.parameters = this._resolveFunctionParams(doclet.params); | ||
const isObjModuleLike = isModuleLike(doclet); | ||
const isParentModuleLike = isModuleLike(parent.doclet); | ||
if (isObjModuleLike && isParentModuleLike) | ||
obj.isNested = true; | ||
const isParentEnum = isEnum(parent.doclet); | ||
if (!isParentEnum) { | ||
if (interfaceMerge) | ||
parent.children.push(interfaceMerge); | ||
parent.children.push(obj); | ||
} | ||
for (const param of ctorObj.parameters) { | ||
param._parent = ctorObj; | ||
} | ||
} | ||
if (doclet.augments && doclet.augments.length) { | ||
const baseType = this.objects[doclet.augments[0]]; | ||
if (!baseType) { | ||
logger_1.warnResolve(doclet, 3); | ||
} | ||
else { | ||
if (o.kind === 'class') { | ||
o.baseType = baseType; | ||
} | ||
else { | ||
o.baseTypes.push(baseType); | ||
} | ||
} | ||
} | ||
if (doclet.implements && doclet.implements.length) { | ||
if (o.kind === 'class') { | ||
o.implements.push.apply(o.implements, doclet.implements.map((s) => this.objects[s])); | ||
} | ||
else { | ||
o.baseTypes.push.apply(o.baseTypes, doclet.implements.map((s) => this.objects[s])); | ||
} | ||
} | ||
if (doclet.mixes && doclet.mixes.length) { | ||
for (const mix of doclet.mixes) { | ||
const declaration = this.objects[mix]; | ||
if (o.kind === 'class') { | ||
o.implements.push(declaration); | ||
} | ||
else { | ||
o.baseTypes.push(declaration); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
_resolveInterfaceMembers(docs) { | ||
for (const doclet of docs) { | ||
if (!this._shouldResolveDoclet(doclet)) { | ||
continue; | ||
} | ||
const obj = this.objects[doclet.longname]; | ||
if (!obj) { | ||
logger_1.warnResolve(doclet, 1); | ||
continue; | ||
} | ||
if (obj.kind === 'class') { | ||
for (const impl of obj.implements) { | ||
for (const implMemb of impl.members) { | ||
const implMember = Object.assign({}, implMemb); | ||
if (implMember.kind === 'call-signature') { | ||
continue; | ||
} | ||
let clsMember = null; | ||
for (const member of obj.members) { | ||
if (member.kind === 'constructor') { | ||
continue; | ||
} | ||
if (member.name === implMember.name) { | ||
clsMember = member; | ||
break; | ||
} | ||
} | ||
if (implMember.kind === 'property' && implMember.type.kind === 'function-type') { | ||
implMember.kind = 'method'; | ||
} | ||
if (!objEqual(clsMember, implMember)) { | ||
obj.members.push(implMember); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
_resolveModules(classes) { | ||
for (let i = classes.length - 1; i >= 0; --i) { | ||
const res = classes[i]; | ||
if (res.kind === 'class' || res.kind === 'interface') { | ||
this._doResolveClassModule(res); | ||
} | ||
else if (res.kind === 'module' || res.kind === 'namespace') { | ||
this._resolveModules(res.members); | ||
} | ||
} | ||
} | ||
_doResolveClassModule(clazz) { | ||
for (const m of clazz.members) { | ||
const member = m; | ||
if (member.kind === 'class' || member.kind === 'interface') { | ||
this._moveMemberToModule(member); | ||
this._doResolveClassModule(member); | ||
} | ||
else if (member.kind === 'alias') { | ||
this._moveMemberToModule(member); | ||
} | ||
} | ||
} | ||
_moveMemberToModule(obj) { | ||
const parent = obj._parent; | ||
const idx = parent.members.indexOf(obj); | ||
const top = parent._parent; | ||
if (!parent._module) { | ||
parent._module = dom.create.module(parent.name); | ||
if (top) { | ||
(top._module || top).members.push(parent._module); | ||
} | ||
else { | ||
this.results.push(parent._module); | ||
if (interfaceMerge) | ||
this._treeRoots.push(interfaceMerge); | ||
this._treeRoots.push(obj); | ||
} | ||
} | ||
parent._module.members.push(obj); | ||
parent.members.splice(idx, 1); | ||
} | ||
_resolveType(doclet, obj) { | ||
const candidateType = doclet.type || doclet.properties && doclet.properties[0].type; | ||
if (!candidateType) { | ||
return dom.type.any; | ||
_parseTree() { | ||
for (let i = 0; i < this._treeRoots.length; ++i) { | ||
this.results.push(this._parseTreeNode(this._treeRoots[i])); | ||
} | ||
const names = candidateType.names; | ||
const types = []; | ||
for (const t of names) { | ||
types.push(this._resolveTypeString(t, doclet, obj)); | ||
} | ||
if (types.length === 0) { | ||
return dom.type.any; | ||
} | ||
if (types.length > 1) { | ||
return dom.create.union(types); | ||
} | ||
return doclet.variable ? dom.type.array(types[0]) : types[0]; | ||
} | ||
_resolveTypeString(t, doclet, obj) { | ||
if (t.startsWith('(')) { | ||
t = t.replace('(', ''); | ||
} | ||
if (t.endsWith(')')) { | ||
t = t.replace(/\)$/, ''); | ||
} | ||
if (t.startsWith('Array')) { | ||
const matches = t.match(rgxArrayType); | ||
if (matches) { | ||
if (matches[1]) { | ||
return dom.create.array(this._resolveTypeString(matches[1], doclet, obj)); | ||
} | ||
else { | ||
return dom.create.array(dom.type.any); | ||
} | ||
_parseTreeNode(node, parent) { | ||
const children = []; | ||
if (children) { | ||
for (let i = 0; i < node.children.length; ++i) { | ||
children.push(this._parseTreeNode(node.children[i], node)); | ||
} | ||
} | ||
if (t.startsWith('Object.<')) { | ||
const matches = t.match(rgxObjectType); | ||
if (matches && matches[1] && matches[2]) { | ||
const indexTypeStr = matches[1].trim(); | ||
const valueTypeStr = matches[2].trim(); | ||
if (indexTypeStr !== 'string' && indexTypeStr !== 'number') { | ||
logger_1.warn(`Invalid object index type: "${matches[1]}", must be "string" or "number". Falling back to "any".`); | ||
return dom.type.any; | ||
switch (node.doclet.kind) { | ||
case 'class': | ||
return create_helpers_1.createClass(node.doclet, children); | ||
case 'constant': | ||
case 'member': | ||
if (node.doclet.isEnum) | ||
return create_helpers_1.createEnum(node.doclet); | ||
else if (parent && parent.doclet.kind === 'class') | ||
return create_helpers_1.createClassMember(node.doclet); | ||
else if (parent && parent.doclet.kind === 'interface') | ||
return create_helpers_1.createInterfaceMember(node.doclet); | ||
else | ||
return create_helpers_1.createNamespaceMember(node.doclet); | ||
case 'function': | ||
if (node.doclet.memberof) { | ||
const parent = this._treeNodes[node.doclet.memberof]; | ||
if (parent && parent.doclet.kind === 'class') | ||
return create_helpers_1.createClassMethod(node.doclet); | ||
else if (parent && parent.doclet.kind === 'interface') | ||
return create_helpers_1.createInterfaceMethod(node.doclet); | ||
} | ||
return dom.create.objectType([ | ||
dom.create.indexSignature('key', indexTypeStr, this._resolveTypeString(valueTypeStr, doclet, obj)), | ||
]); | ||
} | ||
return create_helpers_1.createFunction(node.doclet); | ||
case 'interface': | ||
return create_helpers_1.createInterface(node.doclet, children); | ||
case 'mixin': | ||
return create_helpers_1.createInterface(node.doclet, children); | ||
case 'module': | ||
return create_helpers_1.createModule(node.doclet, !!node.isNested, children); | ||
case 'namespace': | ||
return create_helpers_1.createNamespace(node.doclet, !!node.isNested, children); | ||
case 'typedef': | ||
return create_helpers_1.createTypedef(node.doclet, children); | ||
default: | ||
return assert_never_1.assertNever(node.doclet); | ||
} | ||
else if (t.startsWith('Object')) { | ||
return dom.type.any; | ||
} | ||
let p = obj; | ||
while (p) { | ||
if (p.typeParameters) { | ||
for (const g of p.typeParameters) { | ||
if (t === g.name) { | ||
return t; | ||
} | ||
} | ||
} | ||
p = p._parent; | ||
} | ||
const possiblePrimitive = /^[A-Z]/.test(t) ? t.toLowerCase() : t; | ||
if (possiblePrimitive === dom.type.string | ||
|| possiblePrimitive === dom.type.number | ||
|| possiblePrimitive === dom.type.boolean | ||
|| possiblePrimitive === dom.type.true | ||
|| possiblePrimitive === dom.type.false | ||
|| possiblePrimitive === dom.type.object | ||
|| possiblePrimitive === dom.type.any | ||
|| possiblePrimitive === dom.type.null | ||
|| possiblePrimitive === dom.type.undefined | ||
|| possiblePrimitive === dom.type.void) { | ||
return possiblePrimitive; | ||
} | ||
if (possiblePrimitive === 'function') { | ||
return dom.create.functionType([], dom.type.any); | ||
} | ||
if (t === '*') { | ||
return dom.type.any; | ||
} | ||
if (t.includes('|')) { | ||
return dom.create.union(t.split('|').map((v) => this._resolveTypeString(v, doclet, obj))); | ||
} | ||
if (this.objects[t]) { | ||
return this.objects[t]; | ||
} | ||
else { | ||
try { | ||
const val = eval(t); | ||
const evalType = typeof val; | ||
if (evalType === 'number') { | ||
return dom.type.numberLiteral(val); | ||
} | ||
else if (evalType === 'string') { | ||
return dom.type.stringLiteral(val); | ||
} | ||
else { | ||
logger_1.warn(`Unable to handle eval type "${evalType}", defaulting to "any"`); | ||
} | ||
} | ||
catch (_a) { | ||
logger_1.warn(`Unable to resolve type name "${t}" for "${doclet.longname || doclet.description}". No type found with that name, defaulting to "any".`); | ||
} | ||
return dom.type.any; | ||
} | ||
} | ||
_createClass(doclet) { | ||
const obj = this.objects[doclet.longname] = dom.create.class(doclet.name); | ||
obj._doclet = doclet; | ||
if (doclet.params) { | ||
const ctorParams = []; | ||
for (const param of doclet.params) { | ||
const p = dom.create.parameter(param.name, null, handleParameterFlags(param)); | ||
ctorParams.push(p); | ||
} | ||
const ctor = dom.create.constructor(ctorParams, handleFlags(doclet)); | ||
ctor._doclet = doclet; | ||
obj.members.push(ctor); | ||
} | ||
_ignoreDoclet(doclet) { | ||
return doclet.kind === 'package' | ||
|| doclet.ignore | ||
|| (!this.allowPrivate && doclet.access === 'private'); | ||
} | ||
_createInterface(doclet) { | ||
const obj = this.objects[doclet.longname] = dom.create.interface(doclet.name); | ||
obj._doclet = doclet; | ||
_getInterfaceKey(longname) { | ||
return longname ? longname + '$$interface$helper' : ''; | ||
} | ||
_createMember(doclet) { | ||
let obj = null; | ||
if (doclet.isEnum) { | ||
obj = dom.create.enum(doclet.name, doclet.kind === 'constant'); | ||
} | ||
else { | ||
const o = this.objects[doclet.memberof]; | ||
if (o && o.kind === 'enum') { | ||
return; | ||
} | ||
obj = o && o.kind === 'namespace' | ||
? dom.create.const(doclet.name, null, handleFlags(doclet)) | ||
: dom.create.property(doclet.name, null, handleFlags(doclet)); | ||
} | ||
obj._doclet = doclet; | ||
this.objects[doclet.longname] = obj; | ||
if (doclet.isEnum && doclet.properties) { | ||
for (const property of doclet.properties) { | ||
const propNode = property.meta.code; | ||
const val = dom.create.enumValue(propNode.name); | ||
val.jsDocComment = cleanComment(property.comment); | ||
obj.members.push(val); | ||
} | ||
} | ||
_getModuleKey(longname) { | ||
return longname ? longname + '$$module$helper' : ''; | ||
} | ||
_createFunction(doclet) { | ||
const obj = this.objects[doclet.longname] = dom.create.function(doclet.name, [], null, handleFlags(doclet)); | ||
obj._doclet = doclet; | ||
} | ||
_createNamespace(doclet) { | ||
const obj = this.objects[doclet.longname] = dom.create.namespace(doclet.name); | ||
obj._doclet = doclet; | ||
} | ||
_createTypedef(doclet) { | ||
if (!doclet.type || !doclet.type.names || !doclet.type.names.length) { | ||
logger_1.warn(`No type specified on typedef "${doclet.longname}", assuming "object".`); | ||
doclet.type = { names: ['object'] }; | ||
} | ||
const typeName = doclet.type.names[0]; | ||
let type = null; | ||
switch (typeName.toLowerCase()) { | ||
case 'function': | ||
type = dom.create.functionType([], null); | ||
break; | ||
case 'object': | ||
type = dom.create.objectType([]); | ||
break; | ||
} | ||
const obj = this.objects[doclet.longname] = dom.create.alias(doclet.name, type, handleFlags(doclet)); | ||
obj._doclet = doclet; | ||
} | ||
_shouldResolveDoclet(doclet) { | ||
const parent = this.objects[doclet.memberof]; | ||
return (!doclet.ignore | ||
&& doclet.kind !== 'package' | ||
&& (!parent || parent.kind !== 'enum') | ||
&& (this.config.private || doclet.access !== 'private')); | ||
} | ||
_resolveFunctionParams(params) { | ||
return handleNestedProperties(params).map((p) => { | ||
if ((Object.keys(p.props).length)) { | ||
const param = dom.create.parameter(p.meta.name, this._walkNestedProps(p), handleParameterFlags(p.meta)); | ||
return param; | ||
_getOrCreateClassModule(obj) { | ||
if (obj.doclet.kind === 'namespace') | ||
return obj; | ||
const moduleKey = this._getModuleKey(obj.doclet.longname); | ||
let mod = this._treeNodes[moduleKey]; | ||
if (mod) | ||
return mod; | ||
mod = this._treeNodes[moduleKey] = { | ||
doclet: { | ||
kind: 'module', | ||
name: obj.doclet.name, | ||
scope: 'static', | ||
longname: moduleKey, | ||
}, | ||
children: [], | ||
}; | ||
if (obj.doclet.memberof) { | ||
const parent = this._treeNodes[obj.doclet.memberof]; | ||
if (!parent) { | ||
logger_1.warn(`Failed to find parent of doclet '${obj.doclet.longname}' using memberof '${obj.doclet.memberof}', this is likely due to invalid JSDoc.`, obj.doclet); | ||
return mod; | ||
} | ||
else { | ||
const param = dom.create.parameter(p.meta.name, null, handleParameterFlags(p.meta)); | ||
param.type = this._resolveType(p.meta, param); | ||
return param; | ||
} | ||
}); | ||
} | ||
_walkNestedProps(p) { | ||
const props = Object.keys(p.props).map((pKey) => { | ||
return this._walkNestedProp(p.props[pKey], pKey); | ||
}); | ||
return dom.create.objectType(props); | ||
} | ||
_walkNestedProp(p, key) { | ||
const hasNestProps = Object.keys(p.props).length; | ||
const param = dom.create.property(key, hasNestProps ? this._walkNestedProps(p) : null, handleFlags(p.meta)); | ||
param.type = this._resolveType(p.meta, param); | ||
return param; | ||
} | ||
} | ||
exports.default = Emitter; | ||
function handleFlags(doclet) { | ||
let flags = dom.DeclarationFlags.None; | ||
flags |= accessFlagMap[doclet.access]; | ||
flags |= doclet.optional || doclet.defaultvalue !== undefined ? dom.DeclarationFlags.Optional : dom.DeclarationFlags.None; | ||
flags |= doclet.virtual ? dom.DeclarationFlags.Abstract : dom.DeclarationFlags.None; | ||
flags |= doclet.readonly ? dom.DeclarationFlags.ReadOnly : dom.DeclarationFlags.None; | ||
flags |= doclet.scope === 'static' ? dom.DeclarationFlags.Static : dom.DeclarationFlags.None; | ||
return flags; | ||
} | ||
function handleParameterFlags(doclet) { | ||
let flags = dom.ParameterFlags.None; | ||
flags |= accessFlagMap[doclet.access]; | ||
flags |= doclet.optional || doclet.defaultvalue !== undefined ? dom.ParameterFlags.Optional : dom.ParameterFlags.None; | ||
flags |= doclet.variable ? dom.ParameterFlags.Rest : dom.ParameterFlags.None; | ||
return flags; | ||
} | ||
function handleCustomTags(doclet, obj) { | ||
if (!doclet.tags || !doclet.tags.length) { | ||
return; | ||
} | ||
for (const tag of doclet.tags) { | ||
switch (tag.title) { | ||
case 'template': | ||
obj.typeParameters.push(dom.create.typeParameter(tag.value)); | ||
break; | ||
let parentMod = this._getOrCreateClassModule(parent); | ||
mod.doclet.memberof = parentMod.doclet.longname; | ||
parentMod.children.push(mod); | ||
} | ||
} | ||
} | ||
function handleNestedProperties(properties) { | ||
const nestedCache = { props: {} }; | ||
const nestedProps = []; | ||
properties.forEach((prop) => { | ||
const segs = prop.name ? prop.name.split('.') : []; | ||
if (!nestedCache.props[segs[0]]) { | ||
makeNestObject(segs, nestedCache, prop); | ||
nestedProps.push(nestedCache.props[segs[0]]); | ||
} | ||
else { | ||
makeNestObject(segs, nestedCache, prop); | ||
this._treeRoots.push(mod); | ||
} | ||
}); | ||
return nestedProps; | ||
} | ||
function makeNestObject(segs, context, meta) { | ||
for (const seg of segs) { | ||
context.props[seg] = context.props[seg] || { meta: null, props: {} }; | ||
context = context.props[seg]; | ||
return mod; | ||
} | ||
context.meta = Object.assign({}, meta, { name: meta.name.split('.').pop() }); | ||
} | ||
function objEqual(o1, o2) { | ||
if (!o1 || !o2) { | ||
return (o1 === o2); | ||
} | ||
for (const k in o1) { | ||
if (o1[k] !== o2[k]) { | ||
return false; | ||
} | ||
} | ||
for (const k in o2) { | ||
if (o2[k] !== o1[k]) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
function cleanComment(s) { | ||
if (!s) { | ||
return ''; | ||
} | ||
const cleanLines = []; | ||
for (const line of s.split(/\r?\n/g)) { | ||
const cleaned = line.trim() | ||
.replace(rgxJsDocHeader, '') | ||
.replace(rgxJsDocFooter, '') | ||
.replace(rgxJsDocBody, ''); | ||
if (cleaned) { | ||
cleanLines.push(cleaned); | ||
} | ||
} | ||
return cleanLines.join('\n'); | ||
} | ||
exports.Emitter = Emitter; | ||
//# sourceMappingURL=Emitter.js.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
function warn(...args) { | ||
args.unshift('[TSD-JSDoc]'); | ||
return console && console.warn.apply(console, args); | ||
const header = '[TSD-JSDoc]'; | ||
let isVerbose = false; | ||
function setVerbose(value) { | ||
isVerbose = value; | ||
} | ||
exports.warn = warn; | ||
function warnResolve(doclet, reason, message = '') { | ||
let str = ''; | ||
switch (reason) { | ||
case 0: | ||
str = `Unable to resolve memberof for "${doclet.longname}", using memberof "${doclet.memberof}".`; | ||
break; | ||
case 1: | ||
str = `Unable to find object for longname "${doclet.longname}".`; | ||
break; | ||
case 2: | ||
str = `Type is required for doclet of type "${doclet.kind}" but none found for "${doclet.longname}".`; | ||
break; | ||
case 3: | ||
str = `Failed to resolve base type of "${doclet.longname}", no object found with name "${doclet.augments[0]}".`; | ||
break; | ||
case 4: | ||
str = `Unable to resolve function param type for longname "${doclet.longname}".`; | ||
break; | ||
case 5: | ||
str = `Unable to resolve function return type for longname "${doclet.longname}".`; | ||
break; | ||
exports.setVerbose = setVerbose; | ||
function warn(msg, data) { | ||
if (typeof (console) === 'undefined') | ||
return; | ||
console.warn(`${header} ${msg}`); | ||
if (isVerbose && arguments.length > 1) { | ||
console.warn(data); | ||
} | ||
if (message) { | ||
str += ` ${message}`; | ||
} | ||
warn(str); | ||
} | ||
exports.warnResolve = warnResolve; | ||
exports.warn = warn; | ||
//# sourceMappingURL=logger.js.map |
@@ -7,6 +7,9 @@ "use strict"; | ||
const Emitter_1 = require("./Emitter"); | ||
const logger_1 = require("./logger"); | ||
function publish(data, opts) { | ||
data({ undocumented: true }).remove(); | ||
const docs = data().get(); | ||
const emitter = new Emitter_1.default(docs, opts); | ||
logger_1.setVerbose(!!opts.verbose); | ||
const emitter = new Emitter_1.Emitter(!!opts.private); | ||
emitter.parse(docs); | ||
if (opts.destination === 'console') { | ||
@@ -24,3 +27,4 @@ console.log(emitter.emit()); | ||
} | ||
const pkg = (helper.find(data, { kind: 'package' }) || [])[0]; | ||
const pkgArray = helper.find(data, { kind: 'package' }) || []; | ||
const pkg = pkgArray[0]; | ||
const out = path.join(opts.destination, pkg && pkg.name ? `${pkg.name}.d.ts` : 'types.d.ts'); | ||
@@ -31,3 +35,2 @@ fs.writeFileSync(out, emitter.emit()); | ||
exports.publish = publish; | ||
; | ||
//# sourceMappingURL=publish.js.map |
{ | ||
"name": "tsd-jsdoc", | ||
"version": "2.0.0-beta.6", | ||
"version": "2.0.0", | ||
"description": "Compiles JSDoc annotated javascript into a Typescript Declaration File (.d.ts).", | ||
@@ -13,12 +13,9 @@ "main": "dist/publish.js", | ||
"scripts": { | ||
"clean": "rm -rf *.js *.js.map", | ||
"lint": "eslint --rulesdir node_modules/@englercj/code-style *.js", | ||
"build": "tsc -p tsconfig.json", | ||
"watch": "tsc -w -p tsconfig.json", | ||
"prepare": "npm run build", | ||
"test": "jsdoc -c test/config.json -d out" | ||
"test": "mocha --ui tdd -r ts-node/register test/specs/**.ts" | ||
}, | ||
"files": [ | ||
"dist/*", | ||
"global-types.json", | ||
"README.md", | ||
@@ -28,7 +25,10 @@ "LICENSE" | ||
"devDependencies": { | ||
"@types/node": "^10.1.3", | ||
"crawler": "^1.1.3", | ||
"@types/chai": "^4.1.7", | ||
"@types/mocha": "^5.2.5", | ||
"@types/node": "^10.12.12", | ||
"chai": "^4.2.0", | ||
"jsdoc": "^3.5.5", | ||
"tslint": "^5.10.0", | ||
"typescript": "^2.8.3" | ||
"jsdoc-api": "^4.0.3", | ||
"mocha": "^5.2.0", | ||
"ts-node": "^7.0.1" | ||
}, | ||
@@ -39,4 +39,5 @@ "peerDependencies": { | ||
"dependencies": { | ||
"dts-dom": "^3.1.0" | ||
"ts-simple-ast": "^19.1.0", | ||
"typescript": "^3.2.1" | ||
} | ||
} |
135
README.md
@@ -26,3 +26,3 @@ # jsdoc2tsd | ||
``` | ||
$> jsdoc -t node_modules/tsd-jsdoc -r . | ||
$> jsdoc -t node_modules/tsd-jsdoc/dist -r . | ||
``` | ||
@@ -35,3 +35,3 @@ | ||
"opts": { | ||
"template": "./node_modules/tsd-jsdoc" | ||
"template": "./node_modules/tsd-jsdoc/dist" | ||
} | ||
@@ -41,124 +41,12 @@ } | ||
## Gotchas | ||
## Validation | ||
### Validation | ||
This library provides very little validation beyond what JSDoc provides. Meaning if you | ||
have invalid JSDoc comments, this will likely output an invalid TypeScript Definition File. | ||
This library provides very little validation beyond what jsdoc provides. Meaning if you | ||
have invalid jsodc comments, this will likely output an invalid TypeScript Definition File. | ||
Additionally there are things that JSDoc allows, that TypeScript does not. This library | ||
tries to make these differences transparent, and translate from one to the other when | ||
necessary. It can't handle anything though, and you can generate invalid Typescript | ||
even if your JSDoc is valid. | ||
Additionally there are things that jsdoc allows, that TypeScript does not. | ||
One example would be a member variable marked with `@constant`. While that is valid | ||
jsdoc, it is not valid TS: | ||
```ts | ||
class MyClass { | ||
const member: number; // ERROR: A class member cannot have the 'const' keyword. | ||
} | ||
``` | ||
So there a few cases like this where the jsdoc is massaged into valid TS. | ||
### `module:` | ||
This syntax is used to link to another module's docs. If you use it | ||
to describe the code, it will be ignored. | ||
For example, this JavaScript: | ||
```js | ||
const Loader = require('resource-loader'); | ||
/** | ||
* @class | ||
* @extends module:resource-loader/Loader | ||
*/ | ||
function MyClass() { | ||
Loader.call(this); | ||
} | ||
MyClass.prototype = Object.create(Loader.prototype); | ||
``` | ||
Will generate this declaration: | ||
```ts | ||
class MyClass { | ||
} | ||
``` | ||
Instead you can include their jsdoc commented source or write your own jsdocs to | ||
describe `Loader` and then just use `@extends Loader`. | ||
### Method/Member Overrides | ||
Any method or member that has the same name as one in the parent of a child class | ||
will be ignored in the Child class, unless it is a method with different parameters | ||
that *is not* marked with the `@override` tag. | ||
For example, this JavaScript: | ||
```js | ||
/** | ||
* @class | ||
*/ | ||
class Parent { | ||
constructor() { | ||
/** | ||
* A property. | ||
* | ||
* @member {boolean} | ||
*/ | ||
this.someprop = true; | ||
} | ||
/** | ||
*/ | ||
amethod() {} | ||
/** | ||
*/ | ||
bmethod() {} | ||
} | ||
/** | ||
* @class | ||
* @extends Parent | ||
*/ | ||
class Child extends Parent { | ||
constructor() { | ||
/** | ||
* The property again | ||
* | ||
* @member {boolean} | ||
*/ | ||
this.someprop = false; | ||
} | ||
/** | ||
* @override | ||
* @param {object} opt - Does stuff. | ||
*/ | ||
amethod(opt) {} | ||
/** | ||
* @param {object} opt - Does stuff. | ||
*/ | ||
bmethod(opt) {} | ||
} | ||
``` | ||
Will generate this declaration: | ||
```ts | ||
class Parent { | ||
someprop: boolean; | ||
amethod(): void; | ||
} | ||
class Child extends Parent { | ||
bmethod(opt): void; | ||
} | ||
``` | ||
## Unsupported Tags | ||
@@ -171,8 +59,7 @@ | ||
- [`@event`](http://usejsdoc.org/tags-event.html) - No TS equivalent | ||
- [`@exports`](http://usejsdoc.org/tags-exports.html) - Everything is exported since it is a definition file. | ||
- [`@external`](http://usejsdoc.org/tags-external.html) - **Not Yet Implemented** | ||
- [`@exports`](http://usejsdoc.org/tags-exports.html) - Everything is exported | ||
- [`@external`](http://usejsdoc.org/tags-external.html) - Not sure what behavior would be expected | ||
- [`@fires`](http://usejsdoc.org/tags-fires.html) - No TS equivalent | ||
- [`@listens`](http://usejsdoc.org/tags-listens.html) - No TS equivalent | ||
- [`@override`](http://usejsdoc.org/tags-override.html) - No TS equivalent ([issue](https://github.com/Microsoft/TypeScript/issues/2000)) | ||
- [`@this`](http://usejsdoc.org/tags-this.html) - **Not Yet Implemented** | ||
- [`@throws`](http://usejsdoc.org/tags-throws.html) - No TS equivalent | ||
@@ -179,0 +66,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
19
1
1
67847
3
8
788
89
+ Addedts-simple-ast@^19.1.0
+ Addedtypescript@^3.2.1
+ Added@dsherret/to-absolute-glob@2.0.2(transitive)
+ Added@mrmlnc/readdir-enhanced@2.2.1(transitive)
+ Added@nodelib/fs.stat@1.1.3(transitive)
+ Addedarr-diff@4.0.0(transitive)
+ Addedarr-flatten@1.1.0(transitive)
+ Addedarr-union@3.1.0(transitive)
+ Addedarray-differ@1.0.0(transitive)
+ Addedarray-union@1.0.2(transitive)
+ Addedarray-uniq@1.0.3(transitive)
+ Addedarray-unique@0.3.2(transitive)
+ Addedarrify@1.0.1(transitive)
+ Addedassign-symbols@1.0.0(transitive)
+ Addedatob@2.1.2(transitive)
+ Addedbalanced-match@1.0.2(transitive)
+ Addedbase@0.11.2(transitive)
+ Addedbrace-expansion@1.1.11(transitive)
+ Addedbraces@2.3.2(transitive)
+ Addedcache-base@1.0.1(transitive)
+ Addedcall-me-maybe@1.0.2(transitive)
+ Addedclass-utils@0.3.6(transitive)
+ Addedcode-block-writer@7.3.1(transitive)
+ Addedcollection-visit@1.0.0(transitive)
+ Addedcomponent-emitter@1.3.1(transitive)
+ Addedconcat-map@0.0.1(transitive)
+ Addedcopy-descriptor@0.1.1(transitive)
+ Addeddebug@2.6.9(transitive)
+ Addeddecode-uri-component@0.2.2(transitive)
+ Addeddefine-property@0.2.51.0.02.0.2(transitive)
+ Addeddir-glob@2.0.0(transitive)
+ Addedexpand-brackets@2.1.4(transitive)
+ Addedextend-shallow@2.0.13.0.2(transitive)
+ Addedextglob@2.0.4(transitive)
+ Addedfast-glob@2.2.7(transitive)
+ Addedfill-range@4.0.0(transitive)
+ Addedfor-in@1.0.2(transitive)
+ Addedfragment-cache@0.2.1(transitive)
+ Addedfs-extra@7.0.1(transitive)
+ Addedfs.realpath@1.0.0(transitive)
+ Addedfunction-bind@1.1.2(transitive)
+ Addedget-value@2.0.6(transitive)
+ Addedglob@7.2.3(transitive)
+ Addedglob-parent@3.1.0(transitive)
+ Addedglob-to-regexp@0.3.0(transitive)
+ Addedglobby@8.0.2(transitive)
+ Addedhas-value@0.3.11.0.0(transitive)
+ Addedhas-values@0.1.41.0.0(transitive)
+ Addedhasown@2.0.2(transitive)
+ Addedignore@3.3.10(transitive)
+ Addedinflight@1.0.6(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedis-absolute@1.0.0(transitive)
+ Addedis-accessor-descriptor@1.0.1(transitive)
+ Addedis-buffer@1.1.6(transitive)
+ Addedis-data-descriptor@1.0.1(transitive)
+ Addedis-descriptor@0.1.71.0.3(transitive)
+ Addedis-extendable@0.1.11.0.1(transitive)
+ Addedis-extglob@2.1.1(transitive)
+ Addedis-glob@3.1.04.0.3(transitive)
+ Addedis-negated-glob@1.0.0(transitive)
+ Addedis-number@3.0.0(transitive)
+ Addedis-plain-object@2.0.4(transitive)
+ Addedis-relative@1.0.0(transitive)
+ Addedis-unc-path@1.0.0(transitive)
+ Addedis-windows@1.0.2(transitive)
+ Addedisarray@1.0.0(transitive)
+ Addedisobject@2.1.03.0.1(transitive)
+ Addedjsonfile@4.0.0(transitive)
+ Addedkind-of@3.2.24.0.06.0.3(transitive)
+ Addedmap-cache@0.2.2(transitive)
+ Addedmap-visit@1.0.0(transitive)
+ Addedmerge2@1.4.1(transitive)
+ Addedmicromatch@3.1.10(transitive)
+ Addedminimatch@3.1.2(transitive)
+ Addedmixin-deep@1.3.2(transitive)
+ Addedms@2.0.0(transitive)
+ Addedmultimatch@2.1.0(transitive)
+ Addednanomatch@1.2.13(transitive)
+ Addedobject-copy@0.1.0(transitive)
+ Addedobject-visit@1.0.1(transitive)
+ Addedobject.pick@1.3.0(transitive)
+ Addedonce@1.4.0(transitive)
+ Addedpascalcase@0.1.1(transitive)
+ Addedpath-dirname@1.0.2(transitive)
+ Addedpath-is-absolute@1.0.1(transitive)
+ Addedpath-type@3.0.0(transitive)
+ Addedpify@3.0.0(transitive)
+ Addedposix-character-classes@0.1.1(transitive)
+ Addedregex-not@1.0.2(transitive)
+ Addedrepeat-element@1.1.4(transitive)
+ Addedrepeat-string@1.6.1(transitive)
+ Addedresolve-url@0.2.1(transitive)
+ Addedret@0.1.15(transitive)
+ Addedsafe-regex@1.1.0(transitive)
+ Addedset-value@2.0.1(transitive)
+ Addedslash@1.0.0(transitive)
+ Addedsnapdragon@0.8.2(transitive)
+ Addedsnapdragon-node@2.1.1(transitive)
+ Addedsnapdragon-util@3.0.1(transitive)
+ Addedsource-map@0.5.7(transitive)
+ Addedsource-map-resolve@0.5.3(transitive)
+ Addedsource-map-url@0.4.1(transitive)
+ Addedsplit-string@3.1.0(transitive)
+ Addedstatic-extend@0.1.2(transitive)
+ Addedto-object-path@0.3.0(transitive)
+ Addedto-regex@3.0.2(transitive)
+ Addedto-regex-range@2.1.1(transitive)
+ Addedts-simple-ast@19.1.0(transitive)
+ Addedtslib@1.14.1(transitive)
+ Addedtypescript@3.9.10(transitive)
+ Addedunc-path-regex@0.1.2(transitive)
+ Addedunion-value@1.0.1(transitive)
+ Addeduniversalify@0.1.2(transitive)
+ Addedunset-value@1.0.0(transitive)
+ Addedurix@0.1.0(transitive)
+ Addeduse@3.1.1(transitive)
+ Addedwrappy@1.0.2(transitive)
- Removeddts-dom@^3.1.0
- Removeddts-dom@3.7.0(transitive)