first commit
This commit is contained in:
515
app_vue/node_modules/eslint/lib/rules/valid-jsdoc.js
generated
vendored
Normal file
515
app_vue/node_modules/eslint/lib/rules/valid-jsdoc.js
generated
vendored
Normal file
@ -0,0 +1,515 @@
|
||||
/**
|
||||
* @fileoverview Validates JSDoc comments are syntactically correct
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const doctrine = require("doctrine");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
|
||||
docs: {
|
||||
description: "enforce valid JSDoc comments",
|
||||
category: "Possible Errors",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/rules/valid-jsdoc"
|
||||
},
|
||||
|
||||
schema: [
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
prefer: {
|
||||
type: "object",
|
||||
additionalProperties: {
|
||||
type: "string"
|
||||
}
|
||||
},
|
||||
preferType: {
|
||||
type: "object",
|
||||
additionalProperties: {
|
||||
type: "string"
|
||||
}
|
||||
},
|
||||
requireReturn: {
|
||||
type: "boolean",
|
||||
default: true
|
||||
},
|
||||
requireParamDescription: {
|
||||
type: "boolean",
|
||||
default: true
|
||||
},
|
||||
requireReturnDescription: {
|
||||
type: "boolean",
|
||||
default: true
|
||||
},
|
||||
matchDescription: {
|
||||
type: "string"
|
||||
},
|
||||
requireReturnType: {
|
||||
type: "boolean",
|
||||
default: true
|
||||
},
|
||||
requireParamType: {
|
||||
type: "boolean",
|
||||
default: true
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
],
|
||||
|
||||
fixable: "code",
|
||||
messages: {
|
||||
unexpectedTag: "Unexpected @{{title}} tag; function has no return statement.",
|
||||
expected: "Expected JSDoc for '{{name}}' but found '{{jsdocName}}'.",
|
||||
use: "Use @{{name}} instead.",
|
||||
useType: "Use '{{expectedTypeName}}' instead of '{{currentTypeName}}'.",
|
||||
syntaxError: "JSDoc syntax error.",
|
||||
missingBrace: "JSDoc type missing brace.",
|
||||
missingParamDesc: "Missing JSDoc parameter description for '{{name}}'.",
|
||||
missingParamType: "Missing JSDoc parameter type for '{{name}}'.",
|
||||
missingReturnType: "Missing JSDoc return type.",
|
||||
missingReturnDesc: "Missing JSDoc return description.",
|
||||
missingReturn: "Missing JSDoc @{{returns}} for function.",
|
||||
missingParam: "Missing JSDoc for parameter '{{name}}'.",
|
||||
duplicateParam: "Duplicate JSDoc parameter '{{name}}'.",
|
||||
unsatisfiedDesc: "JSDoc description does not satisfy the regex pattern."
|
||||
},
|
||||
|
||||
deprecated: true,
|
||||
replacedBy: []
|
||||
},
|
||||
|
||||
create(context) {
|
||||
|
||||
const 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,
|
||||
requireParamType = options.requireParamType !== 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)
|
||||
const 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) || node.async
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that return has been found in the current function.
|
||||
* @param {ASTNode} node The return node.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function addReturn(node) {
|
||||
const functionState = fns[fns.length - 1];
|
||||
|
||||
if (functionState && node.argument !== null) {
|
||||
functionState.returnPresent = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {{currentType: Doctrine.Type, expectedTypeName: string}} The current type annotation and
|
||||
* the expected name of the annotation
|
||||
* @private
|
||||
*/
|
||||
function getCurrentExpectedTypes(type) {
|
||||
let currentType;
|
||||
|
||||
if (type.name) {
|
||||
currentType = type;
|
||||
} else if (type.expression) {
|
||||
currentType = type.expression;
|
||||
}
|
||||
|
||||
return {
|
||||
currentType,
|
||||
expectedTypeName: currentType && preferType[currentType.name]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the location of a JSDoc node in a file
|
||||
* @param {Token} jsdocComment The comment that this node is parsed from
|
||||
* @param {{range: number[]}} parsedJsdocNode A tag or other node which was parsed from this comment
|
||||
* @returns {{start: SourceLocation, end: SourceLocation}} The 0-based source location for the tag
|
||||
*/
|
||||
function getAbsoluteRange(jsdocComment, parsedJsdocNode) {
|
||||
return {
|
||||
start: sourceCode.getLocFromIndex(jsdocComment.range[0] + 2 + parsedJsdocNode.range[0]),
|
||||
end: sourceCode.getLocFromIndex(jsdocComment.range[0] + 2 + parsedJsdocNode.range[1])
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate type for a given JSDoc node
|
||||
* @param {Object} jsdocNode JSDoc node
|
||||
* @param {Object} type JSDoc tag
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function validateType(jsdocNode, type) {
|
||||
if (!type || !canTypeBeValidated(type.type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const typesToCheck = [];
|
||||
let 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}>
|
||||
if (type.value) {
|
||||
typesToCheck.push(getCurrentExpectedTypes(type.value));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
typesToCheck.push(getCurrentExpectedTypes(type));
|
||||
}
|
||||
|
||||
elements.forEach(validateType.bind(null, jsdocNode));
|
||||
|
||||
typesToCheck.forEach(typeToCheck => {
|
||||
if (typeToCheck.expectedTypeName &&
|
||||
typeToCheck.expectedTypeName !== typeToCheck.currentType.name) {
|
||||
context.report({
|
||||
node: jsdocNode,
|
||||
messageId: "useType",
|
||||
loc: getAbsoluteRange(jsdocNode, typeToCheck.currentType),
|
||||
data: {
|
||||
currentTypeName: typeToCheck.currentType.name,
|
||||
expectedTypeName: typeToCheck.expectedTypeName
|
||||
},
|
||||
fix(fixer) {
|
||||
return fixer.replaceTextRange(
|
||||
typeToCheck.currentType.range.map(indexInComment => jsdocNode.range[0] + 2 + indexInComment),
|
||||
typeToCheck.expectedTypeName
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
const jsdocNode = sourceCode.getJSDocComment(node),
|
||||
functionData = fns.pop(),
|
||||
paramTagsByName = Object.create(null),
|
||||
paramTags = [];
|
||||
let hasReturns = false,
|
||||
returnsTag,
|
||||
hasConstructor = false,
|
||||
isInterface = false,
|
||||
isOverride = false,
|
||||
isAbstract = false;
|
||||
|
||||
// make sure only to validate JSDoc comments
|
||||
if (jsdocNode) {
|
||||
let jsdoc;
|
||||
|
||||
try {
|
||||
jsdoc = doctrine.parse(jsdocNode.value, {
|
||||
strict: true,
|
||||
unwrap: true,
|
||||
sloppy: true,
|
||||
range: true
|
||||
});
|
||||
} catch (ex) {
|
||||
|
||||
if (/braces/iu.test(ex.message)) {
|
||||
context.report({ node: jsdocNode, messageId: "missingBrace" });
|
||||
} else {
|
||||
context.report({ node: jsdocNode, messageId: "syntaxError" });
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
jsdoc.tags.forEach(tag => {
|
||||
|
||||
switch (tag.title.toLowerCase()) {
|
||||
|
||||
case "param":
|
||||
case "arg":
|
||||
case "argument":
|
||||
paramTags.push(tag);
|
||||
break;
|
||||
|
||||
case "return":
|
||||
case "returns":
|
||||
hasReturns = true;
|
||||
returnsTag = tag;
|
||||
break;
|
||||
|
||||
case "constructor":
|
||||
case "class":
|
||||
hasConstructor = true;
|
||||
break;
|
||||
|
||||
case "override":
|
||||
case "inheritdoc":
|
||||
isOverride = true;
|
||||
break;
|
||||
|
||||
case "abstract":
|
||||
case "virtual":
|
||||
isAbstract = true;
|
||||
break;
|
||||
|
||||
case "interface":
|
||||
isInterface = true;
|
||||
break;
|
||||
|
||||
// no default
|
||||
}
|
||||
|
||||
// check tag preferences
|
||||
if (Object.prototype.hasOwnProperty.call(prefer, tag.title) && tag.title !== prefer[tag.title]) {
|
||||
const entireTagRange = getAbsoluteRange(jsdocNode, tag);
|
||||
|
||||
context.report({
|
||||
node: jsdocNode,
|
||||
messageId: "use",
|
||||
loc: {
|
||||
start: entireTagRange.start,
|
||||
end: {
|
||||
line: entireTagRange.start.line,
|
||||
column: entireTagRange.start.column + `@${tag.title}`.length
|
||||
}
|
||||
},
|
||||
data: { name: prefer[tag.title] },
|
||||
fix(fixer) {
|
||||
return fixer.replaceTextRange(
|
||||
[
|
||||
jsdocNode.range[0] + tag.range[0] + 3,
|
||||
jsdocNode.range[0] + tag.range[0] + tag.title.length + 3
|
||||
],
|
||||
prefer[tag.title]
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// validate the types
|
||||
if (checkPreferType && tag.type) {
|
||||
validateType(jsdocNode, tag.type);
|
||||
}
|
||||
});
|
||||
|
||||
paramTags.forEach(param => {
|
||||
if (requireParamType && !param.type) {
|
||||
context.report({
|
||||
node: jsdocNode,
|
||||
messageId: "missingParamType",
|
||||
loc: getAbsoluteRange(jsdocNode, param),
|
||||
data: { name: param.name }
|
||||
});
|
||||
}
|
||||
if (!param.description && requireParamDescription) {
|
||||
context.report({
|
||||
node: jsdocNode,
|
||||
messageId: "missingParamDesc",
|
||||
loc: getAbsoluteRange(jsdocNode, param),
|
||||
data: { name: param.name }
|
||||
});
|
||||
}
|
||||
if (paramTagsByName[param.name]) {
|
||||
context.report({
|
||||
node: jsdocNode,
|
||||
messageId: "duplicateParam",
|
||||
loc: getAbsoluteRange(jsdocNode, param),
|
||||
data: { name: param.name }
|
||||
});
|
||||
} else if (param.name.indexOf(".") === -1) {
|
||||
paramTagsByName[param.name] = param;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasReturns) {
|
||||
if (!requireReturn && !functionData.returnPresent && (returnsTag.type === null || !isValidReturnType(returnsTag)) && !isAbstract) {
|
||||
context.report({
|
||||
node: jsdocNode,
|
||||
messageId: "unexpectedTag",
|
||||
loc: getAbsoluteRange(jsdocNode, returnsTag),
|
||||
data: {
|
||||
title: returnsTag.title
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (requireReturnType && !returnsTag.type) {
|
||||
context.report({ node: jsdocNode, messageId: "missingReturnType" });
|
||||
}
|
||||
|
||||
if (!isValidReturnType(returnsTag) && !returnsTag.description && requireReturnDescription) {
|
||||
context.report({ node: jsdocNode, messageId: "missingReturnDesc" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 && !node.async)) {
|
||||
context.report({
|
||||
node: jsdocNode,
|
||||
messageId: "missingReturn",
|
||||
data: {
|
||||
returns: prefer.returns || "returns"
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// check the parameters
|
||||
const jsdocParamNames = Object.keys(paramTagsByName);
|
||||
|
||||
if (node.params) {
|
||||
node.params.forEach((param, paramsIndex) => {
|
||||
const bindingParam = param.type === "AssignmentPattern"
|
||||
? param.left
|
||||
: param;
|
||||
|
||||
// TODO(nzakas): Figure out logical things to do with destructured, default, rest params
|
||||
if (bindingParam.type === "Identifier") {
|
||||
const name = bindingParam.name;
|
||||
|
||||
if (jsdocParamNames[paramsIndex] && (name !== jsdocParamNames[paramsIndex])) {
|
||||
context.report({
|
||||
node: jsdocNode,
|
||||
messageId: "expected",
|
||||
loc: getAbsoluteRange(jsdocNode, paramTagsByName[jsdocParamNames[paramsIndex]]),
|
||||
data: {
|
||||
name,
|
||||
jsdocName: jsdocParamNames[paramsIndex]
|
||||
}
|
||||
});
|
||||
} else if (!paramTagsByName[name] && !isOverride) {
|
||||
context.report({
|
||||
node: jsdocNode,
|
||||
messageId: "missingParam",
|
||||
data: {
|
||||
name
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (options.matchDescription) {
|
||||
const regex = new RegExp(options.matchDescription, "u");
|
||||
|
||||
if (!regex.test(jsdoc.description)) {
|
||||
context.report({ node: jsdocNode, messageId: "unsatisfiedDesc" });
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// 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
|
||||
};
|
||||
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user