Generate docs from javascript source via AST parsing
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

185 lines
6.1 KiB

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;