Skip to content

Commit a3ecd83

Browse files
author
Alexej Yaroshevich
committed
fix location issues in jsdoc and split param rules
- 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
1 parent f74570c commit a3ecd83

File tree

13 files changed

+289
-91
lines changed

13 files changed

+289
-91
lines changed

lib/jsdoc.js

Lines changed: 61 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
var assert = require('assert');
12
var commentParser = require('comment-parser');
23
var TypeParser = require('jsdoctypeparser').Parser;
34
var TypeBuilder = require('jsdoctypeparser').Builder;
@@ -8,45 +9,45 @@ TypeBuilder.ENABLE_EXCEPTIONS = true;
89

910
module.exports = {
1011
/**
11-
* @param {string} comment
12+
* @param {string} commentNode
1213
* @returns {DocComment}
1314
*/
14-
createDocComment: function(comment) {
15-
return new DocComment(comment);
15+
createDocCommentByCommentNode: function(commentNode) {
16+
var loc = commentNode.loc;
17+
var lines = [Array(loc.start.column + 1).join(' '), '/*', commentNode.value, '*/']
18+
.join('').split('\n').map(function(v) {
19+
return v.substr(loc.start.column);
20+
});
21+
var value = lines.join('\n');
22+
return new DocComment(value, loc);
1623
},
1724

1825
doc: DocComment,
1926
tag: DocTag,
20-
type: DocType
27+
type: DocType,
28+
location: DocLocation
2129
};
2230

2331
/**
2432
* jsdoc comment object
25-
* @param {object} commentNode
33+
* @param {string} value
34+
* @param {{start: DocLocation}} loc
2635
* @constructor
2736
*/
28-
function DocComment(commentNode) {
29-
// normalize data
30-
var loc = commentNode.loc;
31-
var lines = [Array(loc.start.column + 1).join(' '), '/*', commentNode.value, '*/']
32-
.join('').split('\n').map(function(v) {
33-
return v.substr(loc.start.column);
34-
});
35-
var value = lines.join('\n');
36-
37+
function DocComment(value, loc) {
3738
// parse comments
3839
var _parsed = _parseComment(value) || {};
3940

4041
// fill up fields
4142
this.loc = loc;
4243
this.value = value;
43-
this.lines = lines;
44+
this.lines = value.split('\n');
4445
this.valid = _parsed.hasOwnProperty('line');
4546

4647
// doc parsed data
4748
this.description = _parsed.description || null;
4849
this.tags = (_parsed.tags || []).map(function(tag) {
49-
return new DocTag(tag);
50+
return new DocTag(tag, new DocLocation(tag.line, 3, loc.start));
5051
});
5152

5253
/**
@@ -79,9 +80,10 @@ function DocComment(commentNode) {
7980
/**
8081
* Simple jsdoc tag object
8182
* @param {Object} tag
83+
* @param {DocLocation} loc
8284
* @constructor
8385
*/
84-
function DocTag(tag) {
86+
function DocTag(tag, loc) {
8587
this.id = tag.tag;
8688
this.line = tag.line;
8789
this.value = tag.value;
@@ -90,21 +92,31 @@ function DocTag(tag) {
9092
this.optional = tag.optional;
9193
this.default = tag.default;
9294

95+
this.loc = loc;
96+
9397
if (tag.name) {
94-
this.name = tag.name;
98+
this.name = {
99+
loc: this.loc.shift(0, tag.value.indexOf(tag.name)),
100+
value: tag.name
101+
};
95102
}
96103
if (tag.type) {
97-
this.type = new DocType(tag.type);
104+
this.type = new DocType(tag.type, this.loc.shift(0, tag.value.indexOf(tag.type)));
98105
}
99106
}
100107

101108
/**
102109
* Parses jsdoctype string and provides several methods to work with it
103110
* @param {string} type
111+
* @param {DocLocation} loc
104112
* @constructor
105113
*/
106-
function DocType(type) {
114+
function DocType(type, loc) {
115+
assert(type, 'type can\'t be empty');
116+
assert(loc, 'location should be passed');
117+
107118
this.value = type;
119+
this.loc = loc;
108120

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

137+
/**
138+
* DocLocation
139+
* @constructor
140+
* @param {Number} line
141+
* @param {Number} column
142+
* @param {(Object|DocLocation)} [rel]
143+
*/
144+
function DocLocation(line, column, rel) {
145+
assert(Number.isFinite(line) && line >= 0, 'line should be greater than zero');
146+
assert(Number.isFinite(column) && column >= 0, 'column should be greater than zero');
147+
rel = rel || {};
148+
this.line = Number(line) + Number(rel.line || 0);
149+
this.column = Number(column) + Number(rel.column || 0);
150+
}
151+
152+
/**
153+
* Shift location by line and column
154+
* @param {Number|Object} line
155+
* @param {Number} [column]
156+
* @return {DocLocation}
157+
*/
158+
DocLocation.prototype.shift = function(line, column) {
159+
if (typeof line === 'object') {
160+
column = line.column;
161+
line = line.line;
162+
}
163+
return new DocLocation(line, column, this);
164+
};
165+
125166
/**
126167
* Comment parsing helper
127168
* @param {String} comment

lib/rules/validate-jsdoc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ function patchNodesInFile(file) {
186186
function getJsdoc() {
187187
if (!this.hasOwnProperty('_jsdoc')) {
188188
var res = findDocCommentBeforeLine(this.loc.start.line);
189-
this._jsdoc = res ? jsdoc.createDocComment(res) : null;
189+
this._jsdoc = res ? jsdoc.createDocCommentByCommentNode(res) : null;
190190
}
191191
return this._jsdoc;
192192
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
module.exports = validateCheckParamNames;
2+
module.exports.scopes = ['function'];
3+
module.exports.options = {
4+
checkParamNames: {allowedValues: [true]}
5+
};
6+
7+
/**
8+
* validator for check-param-names
9+
* @param {(FunctionDeclaration|FunctionExpression)} node
10+
* @param {Function} err
11+
*/
12+
function validateCheckParamNames(node, err) {
13+
node.jsdoc.iterateByType(['param', 'arg', 'argument'],
14+
/**
15+
* tag checker
16+
* @param {DocType} tag
17+
* @param {Number} i index
18+
*/
19+
function(tag, i) {
20+
var param = node.params[i];
21+
22+
// checking validity
23+
if (!tag.name) {
24+
return err('missing param name', tag.loc);
25+
}
26+
27+
// checking name
28+
if (tag.name.value !== param.name) {
29+
return err('expected ' + param.name + ' but got ' + tag.name.value,
30+
tag.name.loc || node.jsdoc.loc.start);
31+
}
32+
});
33+
}

lib/rules/validate-jsdoc/check-redundant-access.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ function checkRedundantAccess(node, err) {
2222
}
2323

2424
if (access) {
25-
err('Multiple access definition');
25+
err('Multiple access definition', tag.loc);
2626
return;
2727
}
2828

2929
if (tag.id === 'access' && !tag.name) {
30-
err('Invalid access definition');
30+
err('Invalid access definition', tag.loc);
3131
}
3232

3333
access = tag.id === 'access' ? tag.name : tag.id || 'unspecified';
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
module.exports = validateCheckParamNames;
2+
module.exports.scopes = ['function'];
3+
module.exports.options = {
4+
checkRedundantParams: {allowedValues: [true]}
5+
};
6+
7+
/**
8+
* validator for check-param-names
9+
* @param {(FunctionDeclaration|FunctionExpression)} node
10+
* @param {Function} err
11+
*/
12+
function validateCheckParamNames(node, err) {
13+
node.jsdoc.iterateByType(['param', 'arg', 'argument'],
14+
/**
15+
* tag checker
16+
* @param {DocType} tag
17+
* @param {Number} i index
18+
*/
19+
function(tag, i) {
20+
// skip if there is dot in param name (object's inner param)
21+
if (tag.name.value.indexOf('.') !== -1) {
22+
return;
23+
}
24+
25+
var param = node.params[i];
26+
var _optional = tag.optional || (tag.type && tag.type.optional);
27+
28+
// checking redundant
29+
if (!_optional && !param) {
30+
return err('redundant param statement');
31+
}
32+
});
33+
}

lib/rules/validate-jsdoc/check-types.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ function validateTypesInTags(file, errors) {
4545
}
4646

4747
// trying to create DocComment object
48-
var node = jsdoc.createDocComment(commentNode);
48+
var node = jsdoc.createDocCommentByCommentNode(commentNode);
4949
if (!node.valid) {
5050
return;
5151
}
@@ -57,8 +57,7 @@ function validateTypesInTags(file, errors) {
5757
}
5858
if (!tag.type.valid) {
5959
// throw an error if not valid
60-
errors.add('jsDoc checkTypes: Expected valid type instead of ' + tag.value,
61-
node.loc.start.line + tag.line, node.loc.start.column + tag.id.length + 1);
60+
errors.add('jsDoc checkTypes: Expected valid type instead of ' + tag.type.value, tag.type.loc);
6261
}
6362
});
6463
});

lib/rules/validate-jsdoc/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ var assert = require('assert');
22

33
var validatorsByName = module.exports = {
44
checkTypes: require('./check-types'),
5-
param: require('./param'),
5+
6+
checkParamNames: require('./check-param-names'),
7+
checkRedundantParams: require('./check-redundant-params'),
8+
requireParamTypes: require('./require-param-types'),
9+
610
returns: require('./returns'),
711
checkRedundantAccess: require('./check-redundant-access'),
812
enforceExistence: require('./enforce-existence'),

lib/rules/validate-jsdoc/leading-underscore-access.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function validateLeadingUnderscoresAccess(node, err) {
4040
var access;
4141
node.jsdoc.iterate(function(tag) {
4242
if (!access && ['private', 'protected', 'public', 'access'].indexOf(tag.id) !== -1) {
43-
access = (tag.id === 'access' ? tag.name : tag.id);
43+
access = (tag.id === 'access' ? tag.name.value : tag.id);
4444
}
4545
});
4646

lib/rules/validate-jsdoc/param.js

Lines changed: 0 additions & 50 deletions
This file was deleted.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module.exports = validateParamTag;
2+
module.exports.tags = ['param', 'arg', 'argument'];
3+
module.exports.scopes = ['function'];
4+
module.exports.options = {
5+
requireParamTypes: {allowedValues: [true]}
6+
};
7+
8+
/**
9+
* validator for @param
10+
* @param {(FunctionDeclaration|FunctionExpression)} node
11+
* @param {DocTag} tag
12+
* @param {Function} err
13+
*/
14+
function validateParamTag(node, tag, err) {
15+
// checking existance
16+
if (!tag.type) {
17+
return err('missing param type');
18+
}
19+
}

0 commit comments

Comments
 (0)