bigint-crypto-utils/node_modules/jsdoc-parse/lib/transform.js

404 lines
11 KiB
JavaScript
Raw Normal View History

const testValue = require('test-value')
const where = testValue.where
const arrayify = require('array-back')
const extract = require('reduce-extract')
const pick = require('lodash.pick')
const omit = require('lodash.omit')
/**
* @module transform
*/
module.exports = transform
/**
* Returns a duplex stream - jsdoc explain data in, transformed jsdoc-parse data out.
* @return {Duplex}
*/
function transform (data) {
data = fixES6ConstructorMemberLongnames(data)
/* remove undocumented, package and file doclets */
let json = data.filter(i => !i.undocumented && !/package|file/.test(i.kind))
json = json.map(setIsExportedFlag)
json = json.map(setCodename)
json = insertConstructors(json)
json = json.map(function (doclet) {
doclet = setID(doclet)
doclet = removeQuotes(doclet)
doclet = cleanProperties(doclet)
doclet = buildTodoList(doclet)
doclet = extractTypicalName(doclet)
doclet = extractCategory(doclet)
doclet = extractChainable(doclet)
doclet = extractCustomTags(doclet)
doclet = setTypedefScope(doclet)
doclet = renameThisProperty(doclet)
doclet = removeMemberofFromModule(doclet)
doclet = convertIsEnumFlagToKind(doclet)
return doclet
})
const exported = json.filter(where({ isExported: true }))
const newIDs = exported.map(d => d.id)
newIDs.forEach(function (newID) {
update(json, { isExported: undefined, '!kind': 'module' }, function (doclet) {
return updateIDReferences(doclet, newID)
})
})
json = removeEnumChildren(json)
json = json.map(removeUnwanted)
json = json.map(sortIdentifier)
/* add order field, representing the original order of the documentation */
json.forEach(function (doclet, index) {
doclet.order = index
})
return json
}
/**
Create a unique ID (the jsdoc `longname` field is not guaranteed unique)
@depends setIsExportedFlag
@depends setCodename
*/
function setID (doclet) {
if (doclet.longname) {
doclet.id = doclet.longname
}
if (doclet.kind === 'constructor') {
if (doclet.scope === 'static') {
doclet.id = doclet.longname
delete doclet.scope
} else {
doclet.id = doclet.longname + '()'
}
}
if (doclet.isExported) {
doclet.id = doclet.longname + '--' + doclet.codeName
}
return doclet
}
/**
run after setIsExportedFlag has processed using old name value
@depends setIsExportedFlag
*/
function setCodename (doclet) {
if (doclet.meta && doclet.meta.code) {
doclet.codeName = doclet.meta.code.name
if (doclet.isExported) doclet.name = doclet.codeName
}
return doclet
}
function setIsExportedFlag (doclet) {
if (/module:/.test(doclet.name) && doclet.kind !== 'module' && doclet.kind !== 'constructor') {
doclet.isExported = true
doclet.memberof = doclet.longname
}
return doclet
}
/**
converts .classdesc to .description
@returns Array - contains the class and constructor identifiers
@depends setCodename
*/
function createConstructor (class_) {
if (class_.kind !== 'class') {
throw new Error('should only pass a class to createConstructor')
}
const replacements = []
class_ = Object.assign({}, class_)
const constructorProperties = ['description', 'params', 'examples', 'returns', 'exceptions']
const constructor = pick(class_, constructorProperties)
for (const prop of constructorProperties) delete class_[prop]
if (class_.classdesc) {
class_.description = class_.classdesc
delete class_.classdesc
}
replacements.push(class_)
/* only output a constructor if it's documentated */
if (constructor.description || (constructor.params && constructor.params.length)) {
constructor.id = class_.id
constructor.longname = class_.longname
constructor.name = class_.codeName || class_.name
constructor.kind = 'constructor'
constructor.memberof = class_.longname
replacements.push(constructor)
}
return replacements
}
/* split each class found into two new items, then re-insert them over the original class */
function insertConstructors (data) {
var replacements = []
data.forEach(function (doclet, index) {
if (doclet.kind === 'class') {
replacements.push({ index: index, items: createConstructor(doclet) })
}
})
replacements.reverse().forEach(function (replacement) {
var spliceArgs = [replacement.index, 1].concat(replacement.items)
data.splice.apply(data, spliceArgs)
})
return data
}
/* unfortunately the jsdoc data structure differs between es5 and es6 classes,
hence the need for these four functions */
function getEs6Constructor (data, parent) {
return data.find(i => isES6Constructor(i) && i.memberof === parent.longname)
}
function isES6Class (doclet) {
return testValue(doclet, {
kind: 'class',
meta: { code: { type: 'ClassDeclaration' } }
})
}
function isES6Constructor (doclet) {
return testValue(doclet, {
kind: 'class',
meta: { code: { type: 'MethodDefinition' } }
})
}
function replaceID (id, oldID, newID) {
/* this is required by command-line-args which has similar class names like Definition and Definitions */
return id.replace(new RegExp(`${oldID}`), newID)
}
function updateIDReferences (doclet, newID) {
var oldID = newID.split('--')[0]
if (oldID && !doclet.isExported) {
if (doclet.id) doclet.id = replaceID(doclet.id, oldID, newID)
if (doclet.memberof) doclet.memberof = replaceID(doclet.memberof, oldID, newID)
if (doclet.name) doclet.name = replaceID(doclet.name, oldID, newID)
if (doclet.type && doclet.type.names) {
doclet.type.names = doclet.type.names.map(function (id) {
return replaceID(id, oldID, newID)
})
}
if (doclet.returns) {
doclet.returns = doclet.returns.map(function (doclet) {
if (doclet.type && doclet.type.names) {
doclet.type.names = doclet.type.names.map(function (id) {
return replaceID(id, oldID, newID)
})
}
return doclet
})
}
}
return doclet
}
/* removes unwanted quotes set by jsdoc if you specifiy a memberof such as `jquery.fn` */
function removeQuotes (doclet) {
const re = /["']/g
if (doclet.name) doclet.name = doclet.name.replace(re, '')
if (doclet.memberof) doclet.memberof = doclet.memberof.replace(re, '')
if (doclet.longname) doclet.longname = doclet.longname.replace(re, '')
if (doclet.id) doclet.id = doclet.id.replace(re, '')
return doclet
}
function removeUnwanted (doclet) {
delete doclet.todo
delete doclet.tags
delete doclet.codeName
delete doclet.comment
delete doclet.undocumented
delete doclet.___id
delete doclet.___s
if (doclet.meta) {
const oldMeta = doclet.meta
doclet.meta = {
lineno: oldMeta.lineno,
filename: oldMeta.filename,
path: oldMeta.path
}
}
return doclet
}
function cleanProperties (doclet) {
if (doclet.properties) {
doclet.properties = doclet.properties.map(function (prop) {
return wantedProperties(prop)
})
}
return doclet
}
function wantedProperties (input) {
return omit(input, ['comment', 'meta', 'undocumented', '___id', '___s'])
}
function buildTodoList (doclet) {
var todoList = []
if (doclet.todo) {
var todo = arrayify(doclet.todo)
todoList = todoList.concat(todo.map(function (task) {
return { done: false, task: task }
}))
}
/*
Convert @typicalname and @category from custom to regular tags.
Combine @todo array with @done custom tags to make @todoList
*/
if (doclet.tags) {
var done = doclet.tags.reduce(extract({ title: 'done' }), [])
if (!doclet.tags.length) delete doclet.tags
todoList = todoList.concat(done.map(function (task) {
return { done: true, task: task.value }
}))
}
if (todoList.length) {
doclet.todoList = todoList
}
return doclet
}
function extractTypicalName (doclet) {
if (doclet.tags) {
var typicalName = doclet.tags.reduce(extract({ title: 'typicalname' }), [])
if (typicalName.length) doclet.typicalname = typicalName[0].value
}
return doclet
}
function extractCategory (doclet) {
if (doclet.tags) {
var category = doclet.tags.reduce(extract({ title: 'category' }), [])
if (category.length) doclet.category = category[0].value
}
return doclet
}
function extractChainable (doclet) {
if (doclet.tags) {
var chainable = doclet.tags.reduce(extract({ title: 'chainable' }), [])
if (chainable.length) doclet.chainable = true
}
return doclet
}
/**
@depends removeUnwanted
*/
function extractCustomTags (doclet) {
if (doclet.tags && doclet.tags.length > 0) {
doclet.customTags = doclet.tags.map(function (tag) {
return {
tag: tag.title,
value: tag.value
}
})
}
return doclet
}
function setTypedefScope (doclet) {
if (doclet.kind === 'typedef' && !doclet.scope) doclet.scope = 'global'
return doclet
}
function sort (object, sortFunction) {
var output = {}
var newPropertyOrder = Object.keys(object).filter(function (prop) {
return typeof object[prop] !== 'function'
}).sort(sortFunction)
newPropertyOrder.forEach(function (prop) {
output[prop] = object[prop]
})
return output
}
function sortIdentifier (doclet) {
var fieldOrder = ['id', 'parentId', 'longname', 'name', 'kind', 'scope', 'isExported', 'classdesc', 'augments', 'inherits', 'inherited', 'implements', 'overrides', 'mixes', 'description', 'memberof', 'alias', 'params', 'fires', 'examples', 'returns', 'type', 'defaultvalue', 'readonly', 'thisvalue', 'isEnum', 'properties', 'optional', 'nullable', 'variable', 'author', 'deprecated', 'ignore', 'access', 'requires', 'version', 'since', 'licenses', 'license', 'typicalname', 'category', 'see', 'exceptions', 'codeName', 'todoList', 'customTags', 'chainable', 'meta', 'order']
return sort(doclet, function (a, b) {
if (fieldOrder.indexOf(a) === -1 && fieldOrder.indexOf(b) > -1) {
return 1
} else {
return fieldOrder.indexOf(a) - fieldOrder.indexOf(b)
}
})
}
function update (array, query, newValues) {
for (var i = 0; i < array.length; i++) {
if (testValue(array[i], query)) {
var values = typeof newValues === 'function' ? newValues(array[i]) : newValues
for (var prop in values) {
if (values[prop] !== undefined) array[i][prop] = values[prop]
}
}
}
}
function renameThisProperty (doclet) {
doclet.thisvalue = doclet.this
delete doclet.this
return doclet
}
function removeMemberofFromModule (doclet) {
if (doclet.kind === 'module') {
delete doclet.memberof
delete doclet.scope
}
return doclet
}
function fixES6ConstructorMemberLongnames (data) {
data.forEach(i => {
if (isES6Class(i)) {
const es6constructor = getEs6Constructor(data, i)
if (es6constructor) {
const constructorChildren = data.filter(where({ memberof: es6constructor.longname }))
constructorChildren.forEach(child => {
child.memberof = i.longname
})
}
}
})
return data
}
function convertIsEnumFlagToKind (doclet) {
if (doclet.isEnum) {
doclet.kind = 'enum'
delete doclet.isEnum
}
return doclet
}
/* remove properties which have enum parents.. depends on convertIsEnumFlagToKind */
function removeEnumChildren (json) {
return json.filter(function (doclet) {
var parent = json.find(where({ id: doclet.memberof }))
if (parent && parent.kind === 'enum') {
return false
} else {
return true
}
})
}