diff options
author | Rich Trott <rtrott@gmail.com> | 2016-04-30 22:24:35 -0700 |
---|---|---|
committer | Rich Trott <rtrott@gmail.com> | 2016-05-04 10:26:25 -0700 |
commit | 7a8dd69e1c318f6b3d5ef0d2a2844333816b7193 (patch) | |
tree | 664a4bedfc50f4e06e743de90475f499cf7a4265 /tools/eslint/lib/rules/valid-jsdoc.js | |
parent | 1027b9447ed56ab416d8916b5b4cb212240f1481 (diff) | |
download | node-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.js | 645 |
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 } -]; +}; |