var esprima = require('esprima'); var fs = require('fs'); var skip = /^\s+\*/gm; var mapName = function(elm){ return elm.name; }; var traverseForType = function(type, expr, callback){ if(!expr || !expr.type){ return; } if(expr.type === 'BlockStatement'){ expr.body.forEach(function(expr){ if(expr.type === type){ if(expr.argument.type === 'NewExpression'){ callback(expr.argument.callee.name); } else if(expr.argument.type === 'Literal'){ callback(expr.argument.raw); } else if(expr.argument.type === 'Identifier'){ callback(expr.argument.name); } else if(expr.argument.type === 'ObjectExpression'){ returns.push('[Object]'); } else if(expr.argument.type === 'ArrayExpression'){ returns.push('[Array]'); } else if(expr.argument.type === 'FunctionExpression'){ returns.push('[Function]'); } } else { traverseForType(type, expr, callback); } }); } else if(expr.type === 'IfStatement'){ traverseForType(type, expr.consequent, callback) if(expr.alternate){ traverseForType(type, expr.alternate, callback); } } else if(expr.type === 'TryStatement'){ traverseForType(type, expr.block, callback); expr.handlers.forEach(function(expr){ traverseForType(type, expr.body, callback); }); } else if(expr.type === 'WhileStatement'){ traverseForType(type, expr.body, callback); } } var findReturns = function(expr){ var returns = [] traverseForType('ReturnStatement', expr, function(name){ returns.push(name); }); return returns; }; var findRaises = function(expr){ var raises = [] traverseForType('ThrowStatement', expr, function(name){ raises.push(name); }); return raises; }; var parseExpressions = function(expr){ var expressions = {}; var _parse = function(expr){ if(!expr){ return; } else if(expr.length && typeof expr.forEach === 'function'){ return expr.forEach(_parse); } if(expr.type === 'FunctionExpression'){ expressions[expr.loc.start.line] = { name: expr.id, params: expr.params.map(mapName), returns: findReturns(expr.body), raises: findRaises(expr.body), }; _parse(expr.body); } else if(expr.type === 'FunctionDeclaration'){ expressions[expr.loc.start.line] = { name: expr.id.name, params: expr.params.map(mapName), returns: findReturns(expr.body), raises: findRaises(expr.body), }; _parse(expr.body); } else if(expr.type === 'AssignmentExpression' && expr.right.type === 'FunctionExpression'){ var data = {}; if(expr.left.type === 'MemberExpression'){ data.name = expr.left.property.name; if(expr.left.object.type === 'MemberExpression'){ if(expr.left.object.property.name === 'prototype'){ data.class = expr.left.object.object.name; } } } if(expr.right.type === 'FunctionExpression'){ data.params = expr.right.params.map(mapName); data.returns = findReturns(expr.right.body); data.raises = findRaises(expr.right.body); } expressions[expr.loc.start.line] = data; } else if(expr.type === 'VariableDeclarator' && expr.init.type === 'FunctionExpression'){ expressions[expr.loc.start.line] = { name: expr.id.name, params: expr.init.params.map(mapName), returns: findReturns(expr.init.body), raises: findRaises(expr.init.body), }; } else if(expr.type === 'IfStatement'){ _parse(expr.consequent); _parse(expr.alternate); } else { _parse(expr.expression || expr.callee || expr.body || expr.declarations); } }; _parse(expr); return expressions; }; /* * comment: | * Parse documentation information from the provided filename argument * params: * - name: filename * type: String * comment: filename of the script you wish to parse * - name: options * type: Object * comment: | * Available options: * - tolerant: try to keep parsing the file even if errors are encountered [default: true] * returns: * type: Array * comment: a list of doc comments parsed from the source filename */ module.exports.parseFile = function(filename, options){ return module.exports.parseContents(fs.readFileSync(filename, 'utf-8'), options); }; /* * comment: | * Parse documentation information from the provided string content * * This function is also available under `docast.parse(contents, options)` * params: * - name: contents * type: String * comment: string content to parse docs from * - name: options * type: Object * comment: | * Available options: * - tolerant: try to keep parsing the file even if errors are encountered [default: true] * returns: * type: Array * comment: a list of doc comments parsed from the source string contents */ module.exports.parseContents = function(contents, options){ options = options || {}; var tolerant = (options.tolerant === undefined)? true: options.tolerant; var parsed = esprima.parse(contents, {comment: true, loc: true, tolerant: tolerant}); var expressions = parseExpressions(parsed); var results = []; parsed.comments.forEach(function(comment){ if(comment.type === 'Block'){ var body = comment.value; body = body.replace(skip, ''); data = expressions[comment.loc.end.line + 1] || {}; data.doc = body; results.push(data); } }); return results; }; module.exports.parse = module.exports.parseFile;