UNPKG

esxdoc

Version:

Good Documentation Generator For JavaScript

421 lines (365 loc) 12.8 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _colorLogger = require('color-logger'); var _colorLogger2 = _interopRequireDefault(_colorLogger); var _assert = require('assert'); var _assert2 = _interopRequireDefault(_assert); var _ASTUtil = require('../Util/ASTUtil.js'); var _ASTUtil2 = _interopRequireDefault(_ASTUtil); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const logger = new _colorLogger2.default('ParamParser'); /** * Param Type Parser class. */ class ParamParser { /** * parse param value. * @param {string} value - param value. * @param {boolean} [type=true] if true, contain param type. * @param {boolean} [name=true] if true, contain param name. * @param {boolean} [desc=true] if true, contain param description. * @return {{typeText: string, paramName: string, paramDesc: string}} parsed value. * * @example * let value = '{number} param - this is number param'; * let {typeText, paramName, paramDesc} = ParamParser.parseParamValue(value); * * let value = '{number} this is number return value'; * let {typeText, paramDesc} = ParamParser.parseParamValue(value, true, false, true); * * let value = '{number}'; * let {typeText} = ParamParser.parseParamValue(value, true, false, false); */ static parseParamValue(value, type = true, name = true, desc = true) { value = value.trim(); let match; let typeText = null; let paramName = null; let paramDesc = null; // e.g {number} if (type) { const reg = /^\{([^@]*?)\}(\s+|$)/; // ``@`` is special char in ``{@link foo}`` match = value.match(reg); if (match) { typeText = match[1]; value = value.replace(reg, ''); } else { typeText = '*'; } } // e.g. [p1=123] if (name) { if (value.charAt(0) === '[') { paramName = ''; let counter = 0; for (const c of value) { paramName += c; if (c === '[') counter++; if (c === ']') counter--; if (counter === 0) break; } if (paramName) { value = value.substr(paramName.length).trim(); } } else { match = value.match(/^(\S+)/); if (match) { paramName = match[1]; value = value.replace(/^\S+\s*/, ''); } } } // e.g. this is p1 desc. if (desc) { match = value.match(/^\-?\s*((:?.|\n)*)$/m); if (match) { paramDesc = match[1]; } } (0, _assert2.default)(typeText || paramName || paramDesc, `param is invalid. param = "${ value }"`); return { typeText, paramName, paramDesc }; } /** * parse param text and build formatted result. * @param {string} typeText - param type text. * @param {string} [paramName] - param name. * @param {string} [paramDesc] - param description. * @returns {ParsedParam} formatted result. * * @example * let value = '{number} param - this is number param'; * let {typeText, paramName, paramDesc} = ParamParser.parseParamValue(value); * let result = ParamParser.parseParam(typeText, paramName, paramDesc); */ static parseParam(typeText = null, paramName = null, paramDesc = null) { const result = {}; if (typeText) { // check nullable if (typeText[0] === '?') { result.nullable = true; } else if (typeText[0] === '!') { result.nullable = false; } else { result.nullable = null; } typeText = typeText.replace(/^[?!]/, ''); // check record and union if (typeText[0] === '{') { result.types = [typeText]; } else if (typeText[0] === '(') { typeText = typeText.replace(/^[(]/, '').replace(/[)]$/, ''); result.types = typeText.split('|'); } else if (typeText.includes('|')) { if (typeText.match(/<.*?\|.*?>/)) { // union in generics. e.g. `Array<string|number>` // hack: in this case, process this type in DocBuilder#_buildTypeDocLinkHTML result.types = [typeText]; } else if (typeText.match(/^\.\.\.\(.*?\)/)) { // union with spread. e.g. `...(string|number)` // hack: in this case, process this type in DocBuilder#_buildTypeDocLinkHTML result.types = [typeText]; } else { result.types = typeText.split('|'); } } else { result.types = [typeText]; } if (typeText.indexOf('...') === 0) { result.spread = true; } else { result.spread = false; } } else { result.types = ['']; } if (result.types.some(t => !t)) { throw new Error(`Empty Type found name=${ paramName } desc=${ paramDesc }`); } if (paramName) { // check optional if (paramName[0] === '[') { result.optional = true; paramName = paramName.replace(/^[\[]/, '').replace(/[\]]$/, ''); } else { result.optional = false; } // check default value const pair = paramName.split('='); if (pair.length === 2) { result.defaultValue = pair[1]; try { const raw = JSON.parse(pair[1]); result.defaultRaw = raw; } catch (e) { result.defaultRaw = pair[1]; } } result.name = pair[0].trim(); } result.description = paramDesc; return result; } /* eslint-disable complexity */ /* eslint-disable max-statements */ /** * guess param type by using param default arguments. * @param {Object} params - node of callable AST node. * @returns {ParsedParam[]} guess param results. * * @example * // with method * let results = ParamParser.guessParams(node.value.params); * * // with function * let results = ParamParser.guessParams(node.params); */ static guessParams(params) { const _params = []; for (let i = 0; i < params.length; i++) { const param = params[i]; const result = {}; switch (param.type) { case 'Identifier': // e.g. func(a){} result.name = param.name; result.types = ['*']; break; case 'AssignmentPattern': if (param.left.type === 'Identifier') { result.name = param.left.name; } else if (param.left.type === 'ObjectPattern') { result.name = `objectPattern${ i === 0 ? '' : i }`; } else if (param.left.type === 'ArrayPattern') { result.name = `arrayPattern${ i === 0 ? '' : i }`; } result.optional = true; if (param.right.type.includes('Literal')) { // e.g. func(a = 10){} result.types = param.right.value === null ? ['*'] : [typeof param.right.value]; result.defaultRaw = param.right.value; result.defaultValue = `${ result.defaultRaw }`; } else if (param.right.type === 'ArrayExpression') { // e.g. func(a = [123]){} result.types = param.right.elements.length ? [`${ typeof param.right.elements[0].value }[]`] : ['*[]']; result.defaultRaw = param.right.elements.map(elm => elm.value); result.defaultValue = `${ JSON.stringify(result.defaultRaw) }`; } else if (param.right.type === 'ObjectExpression') { const typeMap = {}; for (const prop of param.left.properties || []) { typeMap[prop.key.name] = '*'; } // e.g. func(a = {key: 123}){} const obj = {}; for (const prop of param.right.properties) { obj[prop.key.name] = prop.value.value; typeMap[prop.key.name] = typeof prop.value.value; } const types = []; for (const key of Object.keys(typeMap)) { types.push(`"${ key }": ${ typeMap[key] }`); } result.types = [`{${ types.join(', ') }}`]; result.defaultRaw = obj; result.defaultValue = `${ JSON.stringify(result.defaultRaw) }`; } else if (param.right.type === 'Identifier') { // e.g. func(a = value){} result.types = ['*']; result.defaultRaw = param.right.name; result.defaultValue = `${ param.right.name }`; } else { // e.g. func(a = new Foo()){}, func(a = foo()){} // CallExpression, NewExpression result.types = ['*']; } break; case 'RestElement': // e.g. func(...a){} result.name = `${ param.argument.name }`; result.types = ['...*']; result.spread = true; break; case 'ObjectPattern': { const objectPattern = []; const raw = {}; for (const property of param.properties) { if (property.type === 'ObjectProperty') { objectPattern.push(`"${ property.key.name }": *`); raw[property.key.name] = null; } else if (property.type === 'RestProperty') { objectPattern.push(`...${ property.argument.name }: Object`); raw[property.argument.name] = {}; } } result.name = `objectPattern${ i === 0 ? '' : i }`; result.types = [`{${ objectPattern.join(', ') }}`]; result.defaultRaw = raw; result.defaultValue = `${ JSON.stringify(result.defaultRaw) }`; break; } case 'ArrayPattern': { // e.g. func([a, b = 10]){} let arrayType = null; const raw = []; for (const element of param.elements) { if (element.type === 'Identifier') { raw.push('null'); } else if (element.type === 'AssignmentPattern') { if ('value' in element.right) { if (!arrayType && element.right.value !== null) arrayType = typeof element.right.value; raw.push(JSON.stringify(element.right.value)); } else { raw.push('*'); } } } if (!arrayType) arrayType = '*'; result.name = `arrayPattern${ i === 0 ? '' : i }`; result.types = [`${ arrayType }[]`]; result.defaultRaw = raw; result.defaultValue = `[${ raw.join(', ') }]`; break; } default: logger.w('unknown param.type', param); } _params.push(result); } return _params; } /** * guess return type by using return node. * @param {ASTNode} body - callable body node. * @returns {ParsedParam|null} */ static guessReturnParam(body) { const result = {}; const guessType = this.guessType.bind(this); _ASTUtil2.default.traverse(body, (node, parent, path) => { // `return` in Function is not the body's `return` if (node.type.includes('Function')) { path.skip(); return; } if (node.type !== 'ReturnStatement') return; if (!node.argument) return; result.types = guessType(node.argument).types; }); if (result.types) { return result; } return null; } /** * guess self type by using assignment node. * @param {ASTNode} right - assignment right node. * @returns {ParsedParam} */ static guessType(right) { if (!right) { return { types: ['*'] }; } if (right.type === 'TemplateLiteral') { return { types: ['string'] }; } if (right.type === 'NullLiteral') { return { types: ['*'] }; } if (right.type.includes('Literal')) { return { types: [typeof right.value] }; } if (right.type === 'ArrayExpression') { if (right.elements.length) { return { types: [`${ typeof right.elements[0].value }[]`] }; } else { return { types: ['*[]'] }; } } if (right.type === 'ObjectExpression') { const typeMap = {}; for (const prop of right.properties) { const name = typeof prop.key === 'undefined' ? prop.key : prop.key.name || prop.key.value; switch (prop.type) { case 'ObjectProperty': typeMap[name] = prop.value.value ? typeof prop.value.value : '*'; break; case 'ObjectMethod': typeMap[name] = 'function'; break; default: typeMap[name] = '*'; } } const types = []; for (const key of Object.keys(typeMap)) { types.push(`"${ key }": ${ typeMap[key] }`); } return { types: [`{${ types.join(', ') }}`] }; } return { types: ['*'] }; } } exports.default = ParamParser;