Skip to content

Commit

Permalink
rework jsdoc rules validators, implement comment-parser
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexej Yaroshevich committed Aug 21, 2014
1 parent 4b2dfad commit 6b499e3
Show file tree
Hide file tree
Showing 11 changed files with 124 additions and 61 deletions.
2 changes: 1 addition & 1 deletion .jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"noarg" : true,
"unused" : true,
"maxlen" : 120,
"predef" : ["describe", "it", "beforeEach", "afterEach"]
"mocha" : true
}
49 changes: 44 additions & 5 deletions lib/jsdoc-helpers.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
var doctrineParser = require('doctrine');
var parse = require('comment-parser');

module.exports = {
parseComments : jsDocParseComments,
tagValidator : jsDocTagValidator,

parse : jsDocParseType,
match : jsDocMatchType
Expand All @@ -14,23 +16,60 @@ function jsDocParseComments (comments) {
*/
return {
node: getJsDocForNode,
line: getJsDocForLine
parse: parseJsDoc
};

function getJsDocForNode (node) {
return getJsDocForLine(node.loc.start.line);
var jsdoc = getJsDocForLine(node.loc.start.line);
if (jsdoc) {
jsdoc.data = parseJsDoc(jsdoc.value);
jsdoc.forEachTag = function (fn) {
if (!this.data.tags || !this.data.tags.length) return;
this.data.tags.forEach(fn);
};
}
return jsdoc;
}

function getJsDocForLine (line) {
line--;
for (var i = 0, l = comments.length; i < l; i++) {
var comment = comments[i];
if (comment.loc.end.line === line && comment.type === 'Block' && comment.value.charAt(0) === '*') {
return comment;
var commentNode = comments[i];
if (commentNode.loc.end.line === line && commentNode.type === 'Block' &&
commentNode.value.charAt(0) === '*') {
return commentNode;
}
}
return null;
}

function parseJsDoc (comment) {
return parse('/*' + comment + '*/', {
lineNumbers: true,
rawValue: true
})[0] || [];
}
}

function jsDocTagValidator (validator) {
return function (node, err) {
var that = this;
if (!node.jsDoc) {
return;
}
node.jsDoc.forEachTag(function (tag, i) {
// call line validator
validator.call(that, node, tag, fixErrLocation(err, node, i));
});
};

function fixErrLocation (err, node, shift) {
return function (text, loc) {
loc = loc || {};
loc.line = loc.hasOwnProperty('line') ? loc.line : (node.loc.start.line + shift);
err(text, loc);
};
}
}

/**
Expand Down
51 changes: 16 additions & 35 deletions lib/rules/validate-jsdoc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
var assert = require('assert');

var jsDocHelpers = require('../jsdoc-helpers');
var validatorsByName = require('./validate-jsdoc/index');

module.exports = function() {};

Expand All @@ -17,66 +18,46 @@ module.exports.prototype = {
},

check: function(file, errors) {
var lineValidators = this.loadLineValidators();
var validators = this.loadValidators();

// skip if there is nothing to check
if (!lineValidators.length) {
if (!validators.length) {
return;
}

var jsDocs = jsDocHelpers.parseComments(file.getComments());
var that = this;

file.iterateNodesByType([
'FunctionDeclaration',
'FunctionExpression'

], function(node) {
var jsDoc = jsDocs.node(node);
if (!jsDoc) {
return;
}
node.jsDoc = jsDocs.node(node);

node.jsDoc = jsDoc.value.split('\n');
node.jsDoc = node.jsDoc || {};
node.jsDoc.paramIndex = 0;

function addError (text, locStart) {
locStart = locStart || {};
errors.add(
text,
locStart.line || (jsDoc.loc.start.line + i),
locStart.column || (node.jsDoc[i].indexOf('@'))
);
for (var j = 0, k = validators.length; j < k; j += 1) {
validators[j].call(that, node, addError);
}

for (var i = 0, l = node.jsDoc.length; i < l; i++) {
var line = node.jsDoc[i].trim();
if (line.charAt(0) !== '*') {
continue;
}

line = line.substr(1).trim();

for (var j = 0, k = lineValidators.length; j < k; j++) {
lineValidators[j](node, line, addError);
}
function addError (text, loc) {
loc = loc || {};
loc.line = loc.hasOwnProperty('line') ? loc.line : (node.jsDoc.loc.start.line);
loc.column = loc.hasOwnProperty('column') ? loc.column : 0; //node.jsDoc[i].indexOf('@');
errors.add(text, loc.line, loc.column);
}
});

},

loadLineValidators: function() {
loadValidators: function() {
var passedOptions = this._optionsList;
var validators = [];
if (!passedOptions) {
return validators;
}

var availableValidators = [
'param',
'returns'
];
availableValidators.forEach(function (name) {
var v = require('./validate-jsdoc/' + name);
Object.keys(validatorsByName).forEach(function (name) {
var v = validatorsByName[name];
if (!v.coveredOptions) {
return;
}
Expand Down
4 changes: 4 additions & 0 deletions lib/rules/validate-jsdoc/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
param: require('./param'),
returns: require('./returns')
};
9 changes: 6 additions & 3 deletions lib/rules/validate-jsdoc/param.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

var jsDocHelpers = require('../../jsdoc-helpers');

module.exports = validateParamLine;
module.exports = jsDocHelpers.tagValidator(validateParamLine);
module.exports.coveredOptions = [
'checkParamNames',
'requireParamTypes',
Expand All @@ -12,10 +12,13 @@ module.exports.coveredOptions = [
/**
* validator for @param
* @param {{type: 'FunctionDeclaration'}|{type: 'FunctionExpression'}} node
* @param {Number} line
* @param {JSDocTag} tag
* @param {Function} err
*/
function validateParamLine(node, line, err) {
function validateParamLine(node, tag, err) {
node.jsDoc.paramIndex = node.jsDoc.paramIndex || 0;

var line = tag.value;
var options = this._options;
if (line.indexOf('@param') !== 0) {
return;
Expand Down
7 changes: 4 additions & 3 deletions lib/rules/validate-jsdoc/returns.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
var jsDocHelpers = require('../../jsdoc-helpers');
var esprimaHelpers = require('../../esprima-helpers');

module.exports = validateReturnsLine;
module.exports = jsDocHelpers.tagValidator(validateReturnsTag);
module.exports.coveredOptions = [
'checkReturnTypes',
'requireReturnTypes',
Expand All @@ -13,11 +13,12 @@ module.exports.coveredOptions = [
/**
* validator for @return/@returns
* @param {(FunctionDeclaration|FunctionExpression)} node
* @param {Number} line
* @param {JSDocTag} tag
* @param {Function} err
*/
function validateReturnsLine(node, line, err) {
function validateReturnsTag(node, tag, err) {
var options = this._options;
var line = tag.value;
if (line.indexOf('@return') !== 0) {
return;
}
Expand Down
13 changes: 8 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "jscs-jsdoc",
"author": "Alexej Yaroshevich <[email protected]>",
"description": "JSCS jsdoc plugin",
"version": "0.0.6",
"version": "0.0.7",
"main": "lib/index",
"homepage": "https://github.com/zxqfox/jscs-jsdoc",
"license": "MIT",
Expand All @@ -20,20 +20,23 @@
"node": ">= 0.8.0"
},
"dependencies": {
"doctrine": "~0.5.0"
"comment-parser": "zxqfox/comment-parser#jscs-jsdoc",
"doctrine": "^0.5.2"
},
"devDependencies": {
"jscs": ">=1.3.0 <2.0",
"jshint": "~2.4.4",
"browserify": "~3.30.2",
"chai": "^1.9.1",
"esprima": "^1.2.2",
"jscs": ">=1.3.0 <2.0",
"jshint": "~2.5.4",
"mocha": "~1.17.1"
},
"peerDependencies": {
"jscs": ">=1.3.0 <2.0"
},
"scripts": {
"lint": "jshint . && jscs lib test",
"test": "npm run lint && mocha -u bdd -R spec",
"test": "npm run lint && mocha",
"browserify": "browserify --standalone JscsPluginJsdoc lib/index.js -o jscs-jsdoc-browser.js"
},
"files": [
Expand Down
30 changes: 30 additions & 0 deletions test/init.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
var parse = require('comment-parser');

global.parse = parse;
global.fnBody = fnBody;

function fnBody(func) {
var str = func.toString();
var out = str.slice(
str.indexOf('{') + 1,
str.lastIndexOf('}')
);

// strip trailing spaces and tabs
out = out.replace(/^\n*|[ \t]*$/g, '');

// strip preceding indentation
var blockIndent = 0;
out.match(/^([ \t]*)/gm).map(function (v) {
if (!blockIndent || (v.length > 0 && v.length < blockIndent)) {
blockIndent = v.length;
}
});

// rebuild block without inner indent
out = !blockIndent ? out : out.split('\n').map(function (v) {
return v.substr(blockIndent);
}).join('\n');

return out;
}
2 changes: 2 additions & 0 deletions test/mocha.opts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

-u bdd -R spec
4 changes: 2 additions & 2 deletions test/test.validate-jsdoc-basic.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
var Checker = require('jscs/lib/checker');
var assert = require('assert');

describe('rules/validate-jsdoc', function () {
describe('rules/validate-jsdoc @param', function () {

var checker;
beforeEach(function () {
Expand All @@ -18,7 +18,7 @@ describe('rules/validate-jsdoc', function () {
checker.checkString(
'var x = 1;\n' +
'/**\n' +
' * @param' +
' * @param\n' +
' */\n' +
'function funcName(xxx) {\n' +
'\n' +
Expand Down
14 changes: 7 additions & 7 deletions test/test.validate-jsdoc-returns.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
var Checker = require('jscs/lib/checker');
var assert = require('assert');

describe('rules/validate-jsdoc', function () {
describe('rules/validate-jsdoc @returns', function () {

var checker;
beforeEach(function () {
Expand All @@ -12,13 +12,13 @@ describe('rules/validate-jsdoc', function () {

describe('require-return-types', function () {

it('should report invalid @returns jsdoc', function () {
it('should report invalid @returns', function () {
checker.configure({ jsDoc: { requireReturnTypes: true } });
assert(
checker.checkString(
'var x = 1;\n' +
'/**\n' +
' * @return' +
' * @return\n' +
' */\n' +
'function funcName() {\n' +
'\n' +
Expand Down Expand Up @@ -52,21 +52,21 @@ describe('rules/validate-jsdoc', function () {
assert(
checker.checkString(
'/**\n' +
' * @return {string}' +
' * @return {string}\n' +
' */\n' +
'function funcName() {\n' +
'\n' +
'}\n' +

'/**\n' +
' * @returns {String}' +
' * @returns {String}\n' +
' */\n' +
'function funcName() {\n' +
'var x = function () { return 1; }\n' +
'}\n' +

'/**\n' +
' * @returns {String}' +
' * @returns {String}\n' +
' */\n' +
'function funcName() {\n' +
'return;\n' +
Expand All @@ -80,7 +80,7 @@ describe('rules/validate-jsdoc', function () {
assert(
checker.checkString(
'/**\n' +
' * @returns {String}' +
' * @returns {String}\n' +
' */\n' +
'function funcName() {\n' +
'var x = function () { return 1; }\n' +
Expand Down

0 comments on commit 6b499e3

Please sign in to comment.