Skip to content

Commit

Permalink
Update: Add preferType option to valid-jsdoc rule (fixes #3056)
Browse files Browse the repository at this point in the history
  • Loading branch information
gyandeeps committed Jan 7, 2016
1 parent 1f7726e commit 7210138
Show file tree
Hide file tree
Showing 3 changed files with 274 additions and 1 deletion.
79 changes: 79 additions & 0 deletions docs/rules/valid-jsdoc.md
Expand Up @@ -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. Please make all the keys in lowercase as we compare them by making the type from jsdoc to lowercase and the value should be case sensitive as in how you expect it to be.
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.
Expand Down
99 changes: 98 additions & 1 deletion lib/rules/valid-jsdoc.js
Expand Up @@ -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
Expand Down Expand Up @@ -81,6 +83,91 @@ 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.toLowerCase()];

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) {
if (canTypeBeValidated(type.value || type)) { // we have to use type.value for RecordType
typesToCheck.push(getCurrentExpectedTypes(type.value || 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.
Expand Down Expand Up @@ -179,6 +266,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
Expand Down Expand Up @@ -253,6 +344,12 @@ module.exports.schema = [
"type": "string"
}
},
"preferType": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"requireReturn": {
"type": "boolean"
},
Expand Down
97 changes: 97 additions & 0 deletions tests/lib/rules/valid-jsdoc.js
Expand Up @@ -607,6 +607,103 @@ 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"
},
{
message: "Use 'ASTNode' instead of 'Astnode'.",
type: "Block"
}
]
}
]
});

0 comments on commit 7210138

Please sign in to comment.