2015-03-26 16:08:51 -04:00

144 lines
4.6 KiB
JavaScript

'use strict';
var doctrine = require('doctrine'),
espree = require('espree'),
through = require('through'),
types = require('ast-types'),
extend = require('extend');
/**
* Detect whether a comment is a JSDoc comment: it must be a block
* comment which starts with two asterisks, not any other number of asterisks.
*
* The code parser automatically strips out the first asterisk that's
* required for the comment to be a comment at all, so we count the remaining
* comments.
* @param {Object} comment an ast-types node of the comment
* @return {boolean} whether it is valid
*/
function isJSDocComment(comment) {
var asterisks = comment.value.match(/^(\*+)/);
return comment.type === 'Block' && asterisks && asterisks[ 1 ].length === 1;
}
/**
* Comment-out a shebang line that may sit at the top of a file,
* making it executable on linux-like systems.
* @param {String} code the source code in full
* @return {String} code
*/
function commentShebang(code) {
return (code[0] === '#' && code[1] === '!') ? '//' + code : code;
}
var espreeConfig = {
loc: true,
attachComment: true,
// specify parsing features (default only has blockBindings: true)
ecmaFeatures: {
// enable parsing of arrow functions
arrowFunctions: true,
// enable parsing of let/const
blockBindings: true,
// enable parsing of destructured arrays and objects
destructuring: true,
// enable parsing of regular expression y flag
regexYFlag: true,
// enable parsing of regular expression u flag
regexUFlag: true,
// enable parsing of template strings
templateStrings: true,
// enable parsing of binary literals
binaryLiterals: true,
// enable parsing of ES6 octal literals
octalLiterals: true,
// enable parsing unicode code point escape sequences
unicodeCodePointEscapes: true,
// enable parsing of default parameters
defaultParams: true,
// enable parsing of rest parameters
restParams: true,
// enable parsing of for-of statement
forOf: true,
// enable parsing computed object literal properties
objectLiteralComputedProperties: true,
// enable parsing of shorthand object literal methods
objectLiteralShorthandMethods: true,
// enable parsing of shorthand object literal properties
objectLiteralShorthandProperties: true,
// Allow duplicate object literal properties (except '__proto__')
objectLiteralDuplicateProperties: true,
// enable parsing of generators/yield
generators: true,
// enable parsing spread operator
spread: true,
// enable parsing classes
classes: true,
// enable parsing of modules
modules: true,
// enable React JSX parsing
jsx: true,
// enable return in global scope
globalReturn: true
}
};
/**
* Documentation stream parser: this receives a module-dep item,
* reads the file, parses the JavaScript, parses the JSDoc, and
* emits parsed comments.
* @param {Object} data a chunk of data provided by module-deps
* @return {undefined} this emits data
*/
module.exports = function () {
return through(function (data) {
try {
var code = commentShebang(data.source),
ast = espree.parse(code, espreeConfig),
stream = this;
} catch(e) {
e.message += '@' + data.file;
this.emit('error', e);
}
types.visit(ast, {
visitNode: function (path) {
/**
* Parse a comment with doctrine and decorate the result with file position and code context.
*
* @param {Object} comment the current state of the parsed JSDoc comment
* @return {undefined} this emits data
*/
function parseComment(comment) {
var parsedComment = doctrine.parse(comment.value, { unwrap: true });
parsedComment.context = {
loc: extend({}, path.value.loc),
file: data.file
};
// This is non-enumerable so that it doesn't get stringified in output; e.g. by the
// documentation binary.
Object.defineProperty(parsedComment.context, 'ast', {
enumerable: false,
value: path
});
if (path.parent && path.parent.node) {
parsedComment.context.code = code.substring
.apply(code, path.parent.node.range);
}
stream.push(parsedComment);
}
(path.value.leadingComments || [])
.filter(isJSDocComment)
.forEach(parseComment);
this.traverse(path);
}
});
});
};