Skip to content
Permalink
Browse files

Update: Add `preferType` option to `valid-jsdoc` rule (fixes #3056)

  • Loading branch information...
gyandeeps committed Jan 4, 2016
1 parent 1ebf887 commit 5a633bfc549f61041e8cb8ac4c88619147309bd2
Showing with 346 additions and 1 deletion.
  1. +79 −0 docs/rules/valid-jsdoc.md
  2. +99 −1 lib/rules/valid-jsdoc.js
  3. +168 −0 tests/lib/rules/valid-jsdoc.js
@@ -188,6 +188,85 @@ By default ESLint requires you to specify `type` for `@return` tag for every doc
}]
```

#### preferType

It will validate all the types from jsdoc with the options setup by the user. Inside the options, key should be what the type you want to check and the value of it should be what the expected type should be. Note that we don't check for spelling mistakes with this option.
In the example below, it will expect the "object" to start with an uppercase and all the "string" type to start with a lowercase.

```json
"valid-jsdoc": [2, {
"preferType": {
"String": "string",
"object": "Object",
"test": "TesT"
}
}]
```

The following patterns are considered problems with option `preferType` setup as above:

```js
/**
* Adds two numbers together.
* @param {String} num1 The first parameter.
* @returns {object} The sum of the two numbers.
*/
function foo(param1) {
return {a: param1};
}
/**
* Adds two numbers together.
* @param {Array<String>} num1 The first parameter.
* @param {{1:test}} num2 The second parameter.
* @returns {object} The sum of the two numbers.
*/
function foo(param1, param2) {
return {a: param1};
}
/**
* Adds two numbers together.
* @param {String|int} num1 The first parameter.
* @returns {object} The sum of the two numbers.
*/
function foo(param1) {
return {a: param1};
}
```

The following patterns are not considered problems with option `preferType` setup as above:

```js
/**
* Adds two numbers together.
* @param {string} num1 The first parameter.
* @returns {Object} The sum of the two numbers.
*/
function foo(param1) {
return {a: param1};
}
/**
* Adds two numbers together.
* @param {Array<string>} num1 The first parameter.
* @param {{1:TesT}} num2 The second parameter.
* @returns {Object} The sum of the two numbers.
*/
function foo(param1, param2) {
return {a: param1};
}
/**
* Adds two numbers together.
* @param {string|int} num1 The first parameter.
* @returns {Object} The sum of the two numbers.
*/
function foo(param1) {
return {a: param1};
}
```

## When Not To Use It

If you aren't using JSDoc, then you can safely turn this rule off.
@@ -25,7 +25,9 @@ module.exports = function(context) {
requireReturn = options.requireReturn !== false,
requireParamDescription = options.requireParamDescription !== false,
requireReturnDescription = options.requireReturnDescription !== false,
requireReturnType = options.requireReturnType !== false;
requireReturnType = options.requireReturnType !== false,
preferType = options.preferType || {},
checkPreferType = Object.keys(preferType).length !== 0;

//--------------------------------------------------------------------------
// Helpers
@@ -81,6 +83,92 @@ module.exports = function(context) {
return tag.type === null || tag.type.name === "void" || tag.type.type === "UndefinedLiteral";
}

/**
* Check if type should be validated based on some exceptions
* @param {Object} type JSDoc tag
* @returns {boolean} True if it can be validated
* @private
*/
function canTypeBeValidated(type) {
return type !== "UndefinedLiteral" && // {undefined} as there is no name property available.
type !== "NullLiteral" && // {null}
type !== "NullableLiteral" && // {?}
type !== "FunctionType" && // {function(a)}
type !== "AllLiteral"; // {*}
}

/**
* Extract the current and expected type based on the input type object
* @param {Object} type JSDoc tag
* @returns {Object} current and expected type object
* @private
*/
function getCurrentExpectedTypes(type) {
var currentType;
var expectedType;

if (!type.name) {
currentType = type.expression.name;
} else {
currentType = type.name;
}

expectedType = preferType[currentType];

return {
currentType: currentType,
expectedType: expectedType
};
}

/**
* Check if return tag type is void or undefined
* @param {Object} tag JSDoc tag
* @param {Object} jsdocNode JSDoc node
* @returns {void}
* @private
*/
function validateTagType(tag, jsdocNode) {
if (!tag.type || !canTypeBeValidated(tag.type.type)) {
return;
}

var typesToCheck = [];
var elements = [];

if (tag.type.type === "TypeApplication") { // {Array.<String>}
elements = tag.type.applications;
typesToCheck.push(getCurrentExpectedTypes(tag.type));
} else if (tag.type.type === "RecordType") { // {{20:String}}
elements = tag.type.fields;
} else if (tag.type.type === "UnionType") { // {String|number|Test}
elements = tag.type.elements;
} else {
typesToCheck.push(getCurrentExpectedTypes(tag.type));
}

elements.forEach(function(type) {
type = type.value ? type.value : type; // we have to use type.value for RecordType
if (canTypeBeValidated(type.type)) {
typesToCheck.push(getCurrentExpectedTypes(type));
}
});

typesToCheck.forEach(function(type) {
if (type.expectedType &&
type.expectedType !== type.currentType) {
context.report({
node: jsdocNode,
message: "Use '{{expectedType}}' instead of '{{currentType}}'.",
data: {
currentType: type.currentType,
expectedType: type.expectedType
}
});
}
});
}

/**
* Validate the JSDoc node and output warnings if anything is wrong.
* @param {ASTNode} node The AST node to check.
@@ -179,6 +267,10 @@ module.exports = function(context) {
context.report(jsdocNode, "Use @{{name}} instead.", { name: prefer[tag.title] });
}

// validate the types
if (checkPreferType) {
validateTagType(tag, jsdocNode);
}
});

// check for functions missing @returns
@@ -253,6 +345,12 @@ module.exports.schema = [
"type": "string"
}
},
"preferType": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"requireReturn": {
"type": "boolean"
},
@@ -223,6 +223,81 @@ ruleTester.run("valid-jsdoc", rule, {
" */\n" +
"function foo() {}",
options: [{requireReturn: false}]
},
// type validations
{
code:
"/**\n" +
"* Foo\n" +
"* @param {Array.<*>} hi - desc\n" +
"* @returns {*} returns a node\n" +
"*/\n" +
"function foo(hi){}",
options: [{
preferType: {
"String": "string",
"Astnode": "ASTNode"
}
}]
},
{
code:
"/**\n" +
"* Foo\n" +
"* @param {string} hi - desc\n" +
"* @returns {ASTNode} returns a node\n" +
"*/\n" +
"function foo(hi){}",
options: [{
preferType: {
"String": "string",
"Astnode": "ASTNode"
}
}]
},
{
code:
"/**\n" +
"* Foo\n" +
"* @param {{20:string}} hi - desc\n" +
"* @returns {Astnode} returns a node\n" +
"*/\n" +
"function foo(hi){}",
options: [{
preferType: {
"String": "string",
"astnode": "ASTNode"
}
}]
},
{
code:
"/**\n" +
"* Foo\n" +
"* @param {String|number|Test} hi - desc\n" +
"* @returns {Astnode} returns a node\n" +
"*/\n" +
"function foo(hi){}",
options: [{
preferType: {
"test": "Test"
}
}]
},
{
code:
"/**\n" +
"* Foo\n" +
"* @param {Array.<string>} hi - desc\n" +
"* @returns {Astnode} returns a node\n" +
"*/\n" +
"function foo(hi){}",
options: [{
preferType: {
"String": "string",
"astnode": "ASTNode"
}
}]
}
],

@@ -660,6 +735,99 @@ ruleTester.run("valid-jsdoc", rule, {
message: "JSDoc syntax error.",
type: "Block"
}]
},

// type validations
{
code:
"/**\n" +
"* Foo\n" +
"* @param {String} hi - desc\n" +
"* @returns {Astnode} returns a node\n" +
"*/\n" +
"function foo(hi){}",
options: [{
preferType: {
"String": "string",
"Astnode": "ASTNode"
}
}],
errors: [
{
message: "Use 'string' instead of 'String'.",
type: "Block"
},
{
message: "Use 'ASTNode' instead of 'Astnode'.",
type: "Block"
}
]
},
{
code:
"/**\n" +
"* Foo\n" +
"* @param {{20:String}} hi - desc\n" +
"* @returns {Astnode} returns a node\n" +
"*/\n" +
"function foo(hi){}",
options: [{
preferType: {
"String": "string",
"Astnode": "ASTNode"
}
}],
errors: [
{
message: "Use 'string' instead of 'String'.",
type: "Block"
},
{
message: "Use 'ASTNode' instead of 'Astnode'.",
type: "Block"
}
]
},
{
code:
"/**\n" +
"* Foo\n" +
"* @param {String|number|test} hi - desc\n" +
"* @returns {Astnode} returns a node\n" +
"*/\n" +
"function foo(hi){}",
options: [{
preferType: {
"test": "Test"
}
}],
errors: [
{
message: "Use 'Test' instead of 'test'.",
type: "Block"
}
]
},
{
code:
"/**\n" +
"* Foo\n" +
"* @param {Array.<String>} hi - desc\n" +
"* @returns {Astnode} returns a node\n" +
"*/\n" +
"function foo(hi){}",
options: [{
preferType: {
"String": "string",
"astnode": "ASTNode"
}
}],
errors: [
{
message: "Use 'string' instead of 'String'.",
type: "Block"
}
]
}
]
});

0 comments on commit 5a633bf

Please sign in to comment.
You can’t perform that action at this time.