summaryrefslogtreecommitdiff
path: root/tools/eslint/lib/rules/valid-jsdoc.js
diff options
context:
space:
mode:
authorRich Trott <rtrott@gmail.com>2016-04-30 22:24:35 -0700
committerRich Trott <rtrott@gmail.com>2016-05-04 10:26:25 -0700
commit7a8dd69e1c318f6b3d5ef0d2a2844333816b7193 (patch)
tree664a4bedfc50f4e06e743de90475f499cf7a4265 /tools/eslint/lib/rules/valid-jsdoc.js
parent1027b9447ed56ab416d8916b5b4cb212240f1481 (diff)
downloadnode-new-7a8dd69e1c318f6b3d5ef0d2a2844333816b7193.tar.gz
tools: update ESLint to 2.9.0
ESLint 2.9.0 fixes some minor bugs that we have been experiencing and introduces some new rules that we may wish to consider. PR-URL: https://github.com/nodejs/node/pull/6498 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Roman Reiss <me@silverwind.io>
Diffstat (limited to 'tools/eslint/lib/rules/valid-jsdoc.js')
-rw-r--r--tools/eslint/lib/rules/valid-jsdoc.js645
1 files changed, 330 insertions, 315 deletions
diff --git a/tools/eslint/lib/rules/valid-jsdoc.js b/tools/eslint/lib/rules/valid-jsdoc.js
index 299095064d..e7d6fdeadf 100644
--- a/tools/eslint/lib/rules/valid-jsdoc.js
+++ b/tools/eslint/lib/rules/valid-jsdoc.js
@@ -1,7 +1,6 @@
/**
* @fileoverview Validates JSDoc comments are syntactically correct
* @author Nicholas C. Zakas
- * @copyright 2014 Nicholas C. Zakas. All rights reserved.
*/
"use strict";
@@ -15,367 +14,383 @@ var doctrine = require("doctrine");
// Rule Definition
//------------------------------------------------------------------------------
-module.exports = function(context) {
-
- var options = context.options[0] || {},
- prefer = options.prefer || {},
- sourceCode = context.getSourceCode(),
-
- // these both default to true, so you have to explicitly make them false
- requireReturn = options.requireReturn !== false,
- requireParamDescription = options.requireParamDescription !== false,
- requireReturnDescription = options.requireReturnDescription !== false,
- requireReturnType = options.requireReturnType !== false,
- preferType = options.preferType || {},
- checkPreferType = Object.keys(preferType).length !== 0;
-
- //--------------------------------------------------------------------------
- // Helpers
- //--------------------------------------------------------------------------
-
- // Using a stack to store if a function returns or not (handling nested functions)
- var fns = [];
-
- /**
- * Check if node type is a Class
- * @param {ASTNode} node node to check.
- * @returns {boolean} True is its a class
- * @private
- */
- function isTypeClass(node) {
- return node.type === "ClassExpression" || node.type === "ClassDeclaration";
- }
-
- /**
- * When parsing a new function, store it in our function stack.
- * @param {ASTNode} node A function node to check.
- * @returns {void}
- * @private
- */
- function startFunction(node) {
- fns.push({
- returnPresent: (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") ||
- isTypeClass(node)
- });
- }
+module.exports = {
+ meta: {
+ docs: {
+ description: "enforce valid JSDoc comments",
+ category: "Possible Errors",
+ recommended: false
+ },
- /**
- * Indicate that return has been found in the current function.
- * @param {ASTNode} node The return node.
- * @returns {void}
- * @private
- */
- function addReturn(node) {
- var functionState = fns[fns.length - 1];
-
- if (functionState && node.argument !== null) {
- functionState.returnPresent = true;
+ schema: [
+ {
+ type: "object",
+ properties: {
+ prefer: {
+ type: "object",
+ additionalProperties: {
+ type: "string"
+ }
+ },
+ preferType: {
+ type: "object",
+ additionalProperties: {
+ type: "string"
+ }
+ },
+ requireReturn: {
+ type: "boolean"
+ },
+ requireParamDescription: {
+ type: "boolean"
+ },
+ requireReturnDescription: {
+ type: "boolean"
+ },
+ matchDescription: {
+ type: "string"
+ },
+ requireReturnType: {
+ type: "boolean"
+ }
+ },
+ additionalProperties: false
+ }
+ ]
+ },
+
+ create: function(context) {
+
+ var options = context.options[0] || {},
+ prefer = options.prefer || {},
+ sourceCode = context.getSourceCode(),
+
+ // these both default to true, so you have to explicitly make them false
+ requireReturn = options.requireReturn !== false,
+ requireParamDescription = options.requireParamDescription !== false,
+ requireReturnDescription = options.requireReturnDescription !== false,
+ requireReturnType = options.requireReturnType !== false,
+ preferType = options.preferType || {},
+ checkPreferType = Object.keys(preferType).length !== 0;
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ // Using a stack to store if a function returns or not (handling nested functions)
+ var fns = [];
+
+ /**
+ * Check if node type is a Class
+ * @param {ASTNode} node node to check.
+ * @returns {boolean} True is its a class
+ * @private
+ */
+ function isTypeClass(node) {
+ return node.type === "ClassExpression" || node.type === "ClassDeclaration";
}
- }
- /**
- * Check if return tag type is void or undefined
- * @param {Object} tag JSDoc tag
- * @returns {boolean} True if its of type void or undefined
- * @private
- */
- function isValidReturnType(tag) {
- 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.name;
- } else if (type.expression) {
- currentType = type.expression.name;
+ /**
+ * When parsing a new function, store it in our function stack.
+ * @param {ASTNode} node A function node to check.
+ * @returns {void}
+ * @private
+ */
+ function startFunction(node) {
+ fns.push({
+ returnPresent: (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") ||
+ isTypeClass(node)
+ });
}
- expectedType = currentType && preferType[currentType];
-
- return {
- currentType: currentType,
- expectedType: expectedType
- };
- }
+ /**
+ * Indicate that return has been found in the current function.
+ * @param {ASTNode} node The return node.
+ * @returns {void}
+ * @private
+ */
+ function addReturn(node) {
+ var functionState = fns[fns.length - 1];
+
+ if (functionState && node.argument !== null) {
+ functionState.returnPresent = true;
+ }
+ }
- /**
- * Check if return tag type is void or undefined
- * @param {Object} jsdocNode JSDoc node
- * @param {Object} type JSDoc tag
- * @returns {void}
- * @private
- */
- function validateType(jsdocNode, type) {
- if (!type || !canTypeBeValidated(type.type)) {
- return;
+ /**
+ * Check if return tag type is void or undefined
+ * @param {Object} tag JSDoc tag
+ * @returns {boolean} True if its of type void or undefined
+ * @private
+ */
+ function isValidReturnType(tag) {
+ return tag.type === null || tag.type.name === "void" || tag.type.type === "UndefinedLiteral";
}
- var typesToCheck = [];
- var elements = [];
-
- switch (type.type) {
- case "TypeApplication": // {Array.<String>}
- elements = type.applications[0].type === "UnionType" ? type.applications[0].elements : type.applications;
- typesToCheck.push(getCurrentExpectedTypes(type));
- break;
- case "RecordType": // {{20:String}}
- elements = type.fields;
- break;
- case "UnionType": // {String|number|Test}
- case "ArrayType": // {[String, number, Test]}
- elements = type.elements;
- break;
- case "FieldType": // Array.<{count: number, votes: number}>
- typesToCheck.push(getCurrentExpectedTypes(type.value));
- break;
- default:
- typesToCheck.push(getCurrentExpectedTypes(type));
+ /**
+ * 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"; // {*}
}
- elements.forEach(validateType.bind(null, jsdocNode));
-
- typesToCheck.forEach(function(typeToCheck) {
- if (typeToCheck.expectedType &&
- typeToCheck.expectedType !== typeToCheck.currentType) {
- context.report({
- node: jsdocNode,
- message: "Use '{{expectedType}}' instead of '{{currentType}}'.",
- data: {
- currentType: typeToCheck.currentType,
- expectedType: typeToCheck.expectedType
- }
- });
+ /**
+ * 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.name;
+ } else if (type.expression) {
+ currentType = type.expression.name;
}
- });
- }
- /**
- * Validate the JSDoc node and output warnings if anything is wrong.
- * @param {ASTNode} node The AST node to check.
- * @returns {void}
- * @private
- */
- function checkJSDoc(node) {
- var jsdocNode = sourceCode.getJSDocComment(node),
- functionData = fns.pop(),
- hasReturns = false,
- hasConstructor = false,
- isInterface = false,
- isOverride = false,
- params = Object.create(null),
- jsdoc;
-
- // make sure only to validate JSDoc comments
- if (jsdocNode) {
-
- try {
- jsdoc = doctrine.parse(jsdocNode.value, {
- strict: true,
- unwrap: true,
- sloppy: true
- });
- } catch (ex) {
+ expectedType = currentType && preferType[currentType];
- if (/braces/i.test(ex.message)) {
- context.report(jsdocNode, "JSDoc type missing brace.");
- } else {
- context.report(jsdocNode, "JSDoc syntax error.");
- }
+ return {
+ currentType: currentType,
+ expectedType: expectedType
+ };
+ }
+ /**
+ * Check if return tag type is void or undefined
+ * @param {Object} jsdocNode JSDoc node
+ * @param {Object} type JSDoc tag
+ * @returns {void}
+ * @private
+ */
+ function validateType(jsdocNode, type) {
+ if (!type || !canTypeBeValidated(type.type)) {
return;
}
- jsdoc.tags.forEach(function(tag) {
-
- switch (tag.title.toLowerCase()) {
+ var typesToCheck = [];
+ var elements = [];
+
+ switch (type.type) {
+ case "TypeApplication": // {Array.<String>}
+ elements = type.applications[0].type === "UnionType" ? type.applications[0].elements : type.applications;
+ typesToCheck.push(getCurrentExpectedTypes(type));
+ break;
+ case "RecordType": // {{20:String}}
+ elements = type.fields;
+ break;
+ case "UnionType": // {String|number|Test}
+ case "ArrayType": // {[String, number, Test]}
+ elements = type.elements;
+ break;
+ case "FieldType": // Array.<{count: number, votes: number}>
+ typesToCheck.push(getCurrentExpectedTypes(type.value));
+ break;
+ default:
+ typesToCheck.push(getCurrentExpectedTypes(type));
+ }
- case "param":
- case "arg":
- case "argument":
- if (!tag.type) {
- context.report(jsdocNode, "Missing JSDoc parameter type for '{{name}}'.", { name: tag.name });
+ elements.forEach(validateType.bind(null, jsdocNode));
+
+ typesToCheck.forEach(function(typeToCheck) {
+ if (typeToCheck.expectedType &&
+ typeToCheck.expectedType !== typeToCheck.currentType) {
+ context.report({
+ node: jsdocNode,
+ message: "Use '{{expectedType}}' instead of '{{currentType}}'.",
+ data: {
+ currentType: typeToCheck.currentType,
+ expectedType: typeToCheck.expectedType
}
+ });
+ }
+ });
+ }
- if (!tag.description && requireParamDescription) {
- context.report(jsdocNode, "Missing JSDoc parameter description for '{{name}}'.", { name: tag.name });
- }
+ /**
+ * Validate the JSDoc node and output warnings if anything is wrong.
+ * @param {ASTNode} node The AST node to check.
+ * @returns {void}
+ * @private
+ */
+ function checkJSDoc(node) {
+ var jsdocNode = sourceCode.getJSDocComment(node),
+ functionData = fns.pop(),
+ hasReturns = false,
+ hasConstructor = false,
+ isInterface = false,
+ isOverride = false,
+ isAbstract = false,
+ params = Object.create(null),
+ jsdoc;
+
+ // make sure only to validate JSDoc comments
+ if (jsdocNode) {
+
+ try {
+ jsdoc = doctrine.parse(jsdocNode.value, {
+ strict: true,
+ unwrap: true,
+ sloppy: true
+ });
+ } catch (ex) {
+
+ if (/braces/i.test(ex.message)) {
+ context.report(jsdocNode, "JSDoc type missing brace.");
+ } else {
+ context.report(jsdocNode, "JSDoc syntax error.");
+ }
- if (params[tag.name]) {
- context.report(jsdocNode, "Duplicate JSDoc parameter '{{name}}'.", { name: tag.name });
- } else if (tag.name.indexOf(".") === -1) {
- params[tag.name] = 1;
- }
- break;
+ return;
+ }
- case "return":
- case "returns":
- hasReturns = true;
+ jsdoc.tags.forEach(function(tag) {
- if (!requireReturn && !functionData.returnPresent && (tag.type === null || !isValidReturnType(tag))) {
- context.report(jsdocNode, "Unexpected @" + tag.title + " tag; function has no return statement.");
- } else {
- if (requireReturnType && !tag.type) {
- context.report(jsdocNode, "Missing JSDoc return type.");
+ switch (tag.title.toLowerCase()) {
+
+ case "param":
+ case "arg":
+ case "argument":
+ if (!tag.type) {
+ context.report(jsdocNode, "Missing JSDoc parameter type for '{{name}}'.", { name: tag.name });
}
- if (!isValidReturnType(tag) && !tag.description && requireReturnDescription) {
- context.report(jsdocNode, "Missing JSDoc return description.");
+ if (!tag.description && requireParamDescription) {
+ context.report(jsdocNode, "Missing JSDoc parameter description for '{{name}}'.", { name: tag.name });
}
- }
- break;
+ if (params[tag.name]) {
+ context.report(jsdocNode, "Duplicate JSDoc parameter '{{name}}'.", { name: tag.name });
+ } else if (tag.name.indexOf(".") === -1) {
+ params[tag.name] = 1;
+ }
+ break;
+
+ case "return":
+ case "returns":
+ hasReturns = true;
+
+ if (!requireReturn && !functionData.returnPresent && (tag.type === null || !isValidReturnType(tag)) && !isAbstract) {
+ context.report(jsdocNode, "Unexpected @" + tag.title + " tag; function has no return statement.");
+ } else {
+ if (requireReturnType && !tag.type) {
+ context.report(jsdocNode, "Missing JSDoc return type.");
+ }
+
+ if (!isValidReturnType(tag) && !tag.description && requireReturnDescription) {
+ context.report(jsdocNode, "Missing JSDoc return description.");
+ }
+ }
- case "constructor":
- case "class":
- hasConstructor = true;
- break;
+ break;
- case "override":
- case "inheritdoc":
- isOverride = true;
- break;
+ case "constructor":
+ case "class":
+ hasConstructor = true;
+ break;
- case "interface":
- isInterface = true;
- break;
+ case "override":
+ case "inheritdoc":
+ isOverride = true;
+ break;
- // no default
- }
+ case "abstract":
+ case "virtual":
+ isAbstract = true;
+ break;
- // check tag preferences
- if (prefer.hasOwnProperty(tag.title) && tag.title !== prefer[tag.title]) {
- context.report(jsdocNode, "Use @{{name}} instead.", { name: prefer[tag.title] });
- }
+ case "interface":
+ isInterface = true;
+ break;
- // validate the types
- if (checkPreferType && tag.type) {
- validateType(jsdocNode, tag.type);
- }
- });
+ // no default
+ }
- // check for functions missing @returns
- if (!isOverride && !hasReturns && !hasConstructor && !isInterface &&
- node.parent.kind !== "get" && node.parent.kind !== "constructor" &&
- node.parent.kind !== "set" && !isTypeClass(node)) {
- if (requireReturn || functionData.returnPresent) {
- context.report(jsdocNode, "Missing JSDoc @" + (prefer.returns || "returns") + " for function.");
+ // check tag preferences
+ if (prefer.hasOwnProperty(tag.title) && tag.title !== prefer[tag.title]) {
+ context.report(jsdocNode, "Use @{{name}} instead.", { name: prefer[tag.title] });
+ }
+
+ // validate the types
+ if (checkPreferType && tag.type) {
+ validateType(jsdocNode, tag.type);
+ }
+ });
+
+ // check for functions missing @returns
+ if (!isOverride && !hasReturns && !hasConstructor && !isInterface &&
+ node.parent.kind !== "get" && node.parent.kind !== "constructor" &&
+ node.parent.kind !== "set" && !isTypeClass(node)) {
+ if (requireReturn || functionData.returnPresent) {
+ context.report(jsdocNode, "Missing JSDoc @" + (prefer.returns || "returns") + " for function.");
+ }
}
- }
- // check the parameters
- var jsdocParams = Object.keys(params);
+ // check the parameters
+ var jsdocParams = Object.keys(params);
- if (node.params) {
- node.params.forEach(function(param, i) {
- var name = param.name;
+ if (node.params) {
+ node.params.forEach(function(param, i) {
+ var name = param.name;
- if (param.type === "AssignmentPattern") {
- name = param.left.name;
- }
+ if (param.type === "AssignmentPattern") {
+ name = param.left.name;
+ }
- // TODO(nzakas): Figure out logical things to do with destructured, default, rest params
- if (param.type === "Identifier" || param.type === "AssignmentPattern") {
- if (jsdocParams[i] && (name !== jsdocParams[i])) {
- context.report(jsdocNode, "Expected JSDoc for '{{name}}' but found '{{jsdocName}}'.", {
- name: name,
- jsdocName: jsdocParams[i]
- });
- } else if (!params[name] && !isOverride) {
- context.report(jsdocNode, "Missing JSDoc for parameter '{{name}}'.", {
- name: name
- });
+ // TODO(nzakas): Figure out logical things to do with destructured, default, rest params
+ if (param.type === "Identifier" || param.type === "AssignmentPattern") {
+ if (jsdocParams[i] && (name !== jsdocParams[i])) {
+ context.report(jsdocNode, "Expected JSDoc for '{{name}}' but found '{{jsdocName}}'.", {
+ name: name,
+ jsdocName: jsdocParams[i]
+ });
+ } else if (!params[name] && !isOverride) {
+ context.report(jsdocNode, "Missing JSDoc for parameter '{{name}}'.", {
+ name: name
+ });
+ }
}
- }
- });
- }
+ });
+ }
- if (options.matchDescription) {
- var regex = new RegExp(options.matchDescription);
+ if (options.matchDescription) {
+ var regex = new RegExp(options.matchDescription);
- if (!regex.test(jsdoc.description)) {
- context.report(jsdocNode, "JSDoc description does not satisfy the regex pattern.");
+ if (!regex.test(jsdoc.description)) {
+ context.report(jsdocNode, "JSDoc description does not satisfy the regex pattern.");
+ }
}
+
}
}
- }
-
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- return {
- "ArrowFunctionExpression": startFunction,
- "FunctionExpression": startFunction,
- "FunctionDeclaration": startFunction,
- "ClassExpression": startFunction,
- "ClassDeclaration": startFunction,
- "ArrowFunctionExpression:exit": checkJSDoc,
- "FunctionExpression:exit": checkJSDoc,
- "FunctionDeclaration:exit": checkJSDoc,
- "ClassExpression:exit": checkJSDoc,
- "ClassDeclaration:exit": checkJSDoc,
- "ReturnStatement": addReturn
- };
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
-};
+ return {
+ ArrowFunctionExpression: startFunction,
+ FunctionExpression: startFunction,
+ FunctionDeclaration: startFunction,
+ ClassExpression: startFunction,
+ ClassDeclaration: startFunction,
+ "ArrowFunctionExpression:exit": checkJSDoc,
+ "FunctionExpression:exit": checkJSDoc,
+ "FunctionDeclaration:exit": checkJSDoc,
+ "ClassExpression:exit": checkJSDoc,
+ "ClassDeclaration:exit": checkJSDoc,
+ ReturnStatement: addReturn
+ };
-module.exports.schema = [
- {
- "type": "object",
- "properties": {
- "prefer": {
- "type": "object",
- "additionalProperties": {
- "type": "string"
- }
- },
- "preferType": {
- "type": "object",
- "additionalProperties": {
- "type": "string"
- }
- },
- "requireReturn": {
- "type": "boolean"
- },
- "requireParamDescription": {
- "type": "boolean"
- },
- "requireReturnDescription": {
- "type": "boolean"
- },
- "matchDescription": {
- "type": "string"
- },
- "requireReturnType": {
- "type": "boolean"
- }
- },
- "additionalProperties": false
}
-];
+};