Skip to content

Commit

Permalink
fix location issues in jsdoc and split param rules
Browse files Browse the repository at this point in the history
- create DocLocation class with line and column
- add loc field to DocType, DocName and DocTag
- split params to several files
- add tests for jsdoc classes
  • Loading branch information
Alexej Yaroshevich committed Nov 10, 2014
1 parent f74570c commit a3ecd83
Show file tree
Hide file tree
Showing 13 changed files with 289 additions and 91 deletions.
81 changes: 61 additions & 20 deletions lib/jsdoc.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
var assert = require('assert');
var commentParser = require('comment-parser');
var TypeParser = require('jsdoctypeparser').Parser;
var TypeBuilder = require('jsdoctypeparser').Builder;
Expand All @@ -8,45 +9,45 @@ TypeBuilder.ENABLE_EXCEPTIONS = true;

module.exports = {
/**
* @param {string} comment
* @param {string} commentNode
* @returns {DocComment}
*/
createDocComment: function(comment) {
return new DocComment(comment);
createDocCommentByCommentNode: function(commentNode) {
var loc = commentNode.loc;
var lines = [Array(loc.start.column + 1).join(' '), '/*', commentNode.value, '*/']
.join('').split('\n').map(function(v) {
return v.substr(loc.start.column);
});
var value = lines.join('\n');
return new DocComment(value, loc);
},

doc: DocComment,
tag: DocTag,
type: DocType
type: DocType,
location: DocLocation
};

/**
* jsdoc comment object
* @param {object} commentNode
* @param {string} value
* @param {{start: DocLocation}} loc
* @constructor
*/
function DocComment(commentNode) {
// normalize data
var loc = commentNode.loc;
var lines = [Array(loc.start.column + 1).join(' '), '/*', commentNode.value, '*/']
.join('').split('\n').map(function(v) {
return v.substr(loc.start.column);
});
var value = lines.join('\n');

function DocComment(value, loc) {
// parse comments
var _parsed = _parseComment(value) || {};

// fill up fields
this.loc = loc;
this.value = value;
this.lines = lines;
this.lines = value.split('\n');
this.valid = _parsed.hasOwnProperty('line');

// doc parsed data
this.description = _parsed.description || null;
this.tags = (_parsed.tags || []).map(function(tag) {
return new DocTag(tag);
return new DocTag(tag, new DocLocation(tag.line, 3, loc.start));
});

/**
Expand Down Expand Up @@ -79,9 +80,10 @@ function DocComment(commentNode) {
/**
* Simple jsdoc tag object
* @param {Object} tag
* @param {DocLocation} loc
* @constructor
*/
function DocTag(tag) {
function DocTag(tag, loc) {
this.id = tag.tag;
this.line = tag.line;
this.value = tag.value;
Expand All @@ -90,21 +92,31 @@ function DocTag(tag) {
this.optional = tag.optional;
this.default = tag.default;

this.loc = loc;

if (tag.name) {
this.name = tag.name;
this.name = {
loc: this.loc.shift(0, tag.value.indexOf(tag.name)),
value: tag.name
};
}
if (tag.type) {
this.type = new DocType(tag.type);
this.type = new DocType(tag.type, this.loc.shift(0, tag.value.indexOf(tag.type)));
}
}

/**
* Parses jsdoctype string and provides several methods to work with it
* @param {string} type
* @param {DocLocation} loc
* @constructor
*/
function DocType(type) {
function DocType(type, loc) {
assert(type, 'type can\'t be empty');
assert(loc, 'location should be passed');

this.value = type;
this.loc = loc;

var parsed = _parseDocType(type);
var data = parsed.valid ? _simplifyType(parsed) : [];
Expand All @@ -122,6 +134,35 @@ function DocType(type) {
};
}

/**
* DocLocation
* @constructor
* @param {Number} line
* @param {Number} column
* @param {(Object|DocLocation)} [rel]
*/
function DocLocation(line, column, rel) {
assert(Number.isFinite(line) && line >= 0, 'line should be greater than zero');
assert(Number.isFinite(column) && column >= 0, 'column should be greater than zero');
rel = rel || {};
this.line = Number(line) + Number(rel.line || 0);
this.column = Number(column) + Number(rel.column || 0);
}

/**
* Shift location by line and column
* @param {Number|Object} line
* @param {Number} [column]
* @return {DocLocation}
*/
DocLocation.prototype.shift = function(line, column) {
if (typeof line === 'object') {
column = line.column;
line = line.line;
}
return new DocLocation(line, column, this);
};

/**
* Comment parsing helper
* @param {String} comment
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/validate-jsdoc.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ function patchNodesInFile(file) {
function getJsdoc() {
if (!this.hasOwnProperty('_jsdoc')) {
var res = findDocCommentBeforeLine(this.loc.start.line);
this._jsdoc = res ? jsdoc.createDocComment(res) : null;
this._jsdoc = res ? jsdoc.createDocCommentByCommentNode(res) : null;
}
return this._jsdoc;
}
Expand Down
33 changes: 33 additions & 0 deletions lib/rules/validate-jsdoc/check-param-names.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module.exports = validateCheckParamNames;
module.exports.scopes = ['function'];
module.exports.options = {
checkParamNames: {allowedValues: [true]}
};

/**
* validator for check-param-names
* @param {(FunctionDeclaration|FunctionExpression)} node
* @param {Function} err
*/
function validateCheckParamNames(node, err) {
node.jsdoc.iterateByType(['param', 'arg', 'argument'],
/**
* tag checker
* @param {DocType} tag
* @param {Number} i index
*/
function(tag, i) {
var param = node.params[i];

// checking validity
if (!tag.name) {
return err('missing param name', tag.loc);
}

// checking name
if (tag.name.value !== param.name) {
return err('expected ' + param.name + ' but got ' + tag.name.value,
tag.name.loc || node.jsdoc.loc.start);
}
});
}
4 changes: 2 additions & 2 deletions lib/rules/validate-jsdoc/check-redundant-access.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ function checkRedundantAccess(node, err) {
}

if (access) {
err('Multiple access definition');
err('Multiple access definition', tag.loc);
return;
}

if (tag.id === 'access' && !tag.name) {
err('Invalid access definition');
err('Invalid access definition', tag.loc);
}

access = tag.id === 'access' ? tag.name : tag.id || 'unspecified';
Expand Down
33 changes: 33 additions & 0 deletions lib/rules/validate-jsdoc/check-redundant-params.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module.exports = validateCheckParamNames;
module.exports.scopes = ['function'];
module.exports.options = {
checkRedundantParams: {allowedValues: [true]}
};

/**
* validator for check-param-names
* @param {(FunctionDeclaration|FunctionExpression)} node
* @param {Function} err
*/
function validateCheckParamNames(node, err) {
node.jsdoc.iterateByType(['param', 'arg', 'argument'],
/**
* tag checker
* @param {DocType} tag
* @param {Number} i index
*/
function(tag, i) {
// skip if there is dot in param name (object's inner param)
if (tag.name.value.indexOf('.') !== -1) {
return;
}

var param = node.params[i];
var _optional = tag.optional || (tag.type && tag.type.optional);

// checking redundant
if (!_optional && !param) {
return err('redundant param statement');
}
});
}
5 changes: 2 additions & 3 deletions lib/rules/validate-jsdoc/check-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function validateTypesInTags(file, errors) {
}

// trying to create DocComment object
var node = jsdoc.createDocComment(commentNode);
var node = jsdoc.createDocCommentByCommentNode(commentNode);
if (!node.valid) {
return;
}
Expand All @@ -57,8 +57,7 @@ function validateTypesInTags(file, errors) {
}
if (!tag.type.valid) {
// throw an error if not valid
errors.add('jsDoc checkTypes: Expected valid type instead of ' + tag.value,
node.loc.start.line + tag.line, node.loc.start.column + tag.id.length + 1);
errors.add('jsDoc checkTypes: Expected valid type instead of ' + tag.type.value, tag.type.loc);
}
});
});
Expand Down
6 changes: 5 additions & 1 deletion lib/rules/validate-jsdoc/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ var assert = require('assert');

var validatorsByName = module.exports = {
checkTypes: require('./check-types'),
param: require('./param'),

checkParamNames: require('./check-param-names'),
checkRedundantParams: require('./check-redundant-params'),
requireParamTypes: require('./require-param-types'),

returns: require('./returns'),
checkRedundantAccess: require('./check-redundant-access'),
enforceExistence: require('./enforce-existence'),
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/validate-jsdoc/leading-underscore-access.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function validateLeadingUnderscoresAccess(node, err) {
var access;
node.jsdoc.iterate(function(tag) {
if (!access && ['private', 'protected', 'public', 'access'].indexOf(tag.id) !== -1) {
access = (tag.id === 'access' ? tag.name : tag.id);
access = (tag.id === 'access' ? tag.name.value : tag.id);
}
});

Expand Down
50 changes: 0 additions & 50 deletions lib/rules/validate-jsdoc/param.js

This file was deleted.

19 changes: 19 additions & 0 deletions lib/rules/validate-jsdoc/require-param-types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module.exports = validateParamTag;
module.exports.tags = ['param', 'arg', 'argument'];
module.exports.scopes = ['function'];
module.exports.options = {
requireParamTypes: {allowedValues: [true]}
};

/**
* validator for @param
* @param {(FunctionDeclaration|FunctionExpression)} node
* @param {DocTag} tag
* @param {Function} err
*/
function validateParamTag(node, tag, err) {
// checking existance
if (!tag.type) {
return err('missing param type');
}
}
13 changes: 3 additions & 10 deletions lib/rules/validate-jsdoc/returns.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@

var esprimaHelpers = require('../../esprima-helpers');

module.exports = validateReturnsTag;
module.exports.tags = ['return', 'returns'];
module.exports.scopes = ['function'];
module.exports.options = {
checkReturnTypes: {
allowedValues: [true]
},
requireReturnTypes: {
allowedValues: [true]
},
checkRedundantReturns: {
allowedValues: [true]
}
checkReturnTypes: {allowedValues: [true]},
requireReturnTypes: {allowedValues: [true]},
checkRedundantReturns: {allowedValues: [true]}
};

/**
Expand Down
Loading

0 comments on commit a3ecd83

Please sign in to comment.