]>
git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/valid-jsdoc.js
2 * @fileoverview Validates JSDoc comments are syntactically correct
3 * @author Nicholas C. Zakas
4 * @deprecated in ESLint v5.10.0
8 //------------------------------------------------------------------------------
10 //------------------------------------------------------------------------------
12 const doctrine
= require("doctrine");
14 //------------------------------------------------------------------------------
16 //------------------------------------------------------------------------------
18 /** @type {import('../shared/types').Rule} */
24 description
: "enforce valid JSDoc comments",
26 url
: "https://eslint.org/docs/rules/valid-jsdoc"
35 additionalProperties
: {
41 additionalProperties
: {
49 requireParamDescription
: {
53 requireReturnDescription
: {
69 additionalProperties
: false
75 unexpectedTag
: "Unexpected @{{title}} tag; function has no return statement.",
76 expected
: "Expected JSDoc for '{{name}}' but found '{{jsdocName}}'.",
77 use: "Use @{{name}} instead.",
78 useType
: "Use '{{expectedTypeName}}' instead of '{{currentTypeName}}'.",
79 syntaxError
: "JSDoc syntax error.",
80 missingBrace
: "JSDoc type missing brace.",
81 missingParamDesc
: "Missing JSDoc parameter description for '{{name}}'.",
82 missingParamType
: "Missing JSDoc parameter type for '{{name}}'.",
83 missingReturnType
: "Missing JSDoc return type.",
84 missingReturnDesc
: "Missing JSDoc return description.",
85 missingReturn
: "Missing JSDoc @{{returns}} for function.",
86 missingParam
: "Missing JSDoc for parameter '{{name}}'.",
87 duplicateParam
: "Duplicate JSDoc parameter '{{name}}'.",
88 unsatisfiedDesc
: "JSDoc description does not satisfy the regex pattern."
97 const options
= context
.options
[0] || {},
98 prefer
= options
.prefer
|| {},
99 sourceCode
= context
.getSourceCode(),
101 // these both default to true, so you have to explicitly make them false
102 requireReturn
= options
.requireReturn
!== false,
103 requireParamDescription
= options
.requireParamDescription
!== false,
104 requireReturnDescription
= options
.requireReturnDescription
!== false,
105 requireReturnType
= options
.requireReturnType
!== false,
106 requireParamType
= options
.requireParamType
!== false,
107 preferType
= options
.preferType
|| {},
108 checkPreferType
= Object
.keys(preferType
).length
!== 0;
110 //--------------------------------------------------------------------------
112 //--------------------------------------------------------------------------
114 // Using a stack to store if a function returns or not (handling nested functions)
118 * Check if node type is a Class
119 * @param {ASTNode} node node to check.
120 * @returns {boolean} True is its a class
123 function isTypeClass(node
) {
124 return node
.type
=== "ClassExpression" || node
.type
=== "ClassDeclaration";
128 * When parsing a new function, store it in our function stack.
129 * @param {ASTNode} node A function node to check.
133 function startFunction(node
) {
135 returnPresent
: (node
.type
=== "ArrowFunctionExpression" && node
.body
.type
!== "BlockStatement") ||
136 isTypeClass(node
) || node
.async
141 * Indicate that return has been found in the current function.
142 * @param {ASTNode} node The return node.
146 function addReturn(node
) {
147 const functionState
= fns
[fns
.length
- 1];
149 if (functionState
&& node
.argument
!== null) {
150 functionState
.returnPresent
= true;
155 * Check if return tag type is void or undefined
156 * @param {Object} tag JSDoc tag
157 * @returns {boolean} True if its of type void or undefined
160 function isValidReturnType(tag
) {
161 return tag
.type
=== null || tag
.type
.name
=== "void" || tag
.type
.type
=== "UndefinedLiteral";
165 * Check if type should be validated based on some exceptions
166 * @param {Object} type JSDoc tag
167 * @returns {boolean} True if it can be validated
170 function canTypeBeValidated(type
) {
171 return type
!== "UndefinedLiteral" && // {undefined} as there is no name property available.
172 type
!== "NullLiteral" && // {null}
173 type
!== "NullableLiteral" && // {?}
174 type
!== "FunctionType" && // {function(a)}
175 type
!== "AllLiteral"; // {*}
179 * Extract the current and expected type based on the input type object
180 * @param {Object} type JSDoc tag
181 * @returns {{currentType: Doctrine.Type, expectedTypeName: string}} The current type annotation and
182 * the expected name of the annotation
185 function getCurrentExpectedTypes(type
) {
190 } else if (type
.expression
) {
191 currentType
= type
.expression
;
196 expectedTypeName
: currentType
&& preferType
[currentType
.name
]
201 * Gets the location of a JSDoc node in a file
202 * @param {Token} jsdocComment The comment that this node is parsed from
203 * @param {{range: number[]}} parsedJsdocNode A tag or other node which was parsed from this comment
204 * @returns {{start: SourceLocation, end: SourceLocation}} The 0-based source location for the tag
206 function getAbsoluteRange(jsdocComment
, parsedJsdocNode
) {
208 start
: sourceCode
.getLocFromIndex(jsdocComment
.range
[0] + 2 + parsedJsdocNode
.range
[0]),
209 end
: sourceCode
.getLocFromIndex(jsdocComment
.range
[0] + 2 + parsedJsdocNode
.range
[1])
214 * Validate type for a given JSDoc node
215 * @param {Object} jsdocNode JSDoc node
216 * @param {Object} type JSDoc tag
220 function validateType(jsdocNode
, type
) {
221 if (!type
|| !canTypeBeValidated(type
.type
)) {
225 const typesToCheck
= [];
229 case "TypeApplication": // {Array.<String>}
230 elements
= type
.applications
[0].type
=== "UnionType" ? type
.applications
[0].elements
: type
.applications
;
231 typesToCheck
.push(getCurrentExpectedTypes(type
));
233 case "RecordType": // {{20:String}}
234 elements
= type
.fields
;
236 case "UnionType": // {String|number|Test}
237 case "ArrayType": // {[String, number, Test]}
238 elements
= type
.elements
;
240 case "FieldType": // Array.<{count: number, votes: number}>
242 typesToCheck
.push(getCurrentExpectedTypes(type
.value
));
246 typesToCheck
.push(getCurrentExpectedTypes(type
));
249 elements
.forEach(validateType
.bind(null, jsdocNode
));
251 typesToCheck
.forEach(typeToCheck
=> {
252 if (typeToCheck
.expectedTypeName
&&
253 typeToCheck
.expectedTypeName
!== typeToCheck
.currentType
.name
) {
256 messageId
: "useType",
257 loc
: getAbsoluteRange(jsdocNode
, typeToCheck
.currentType
),
259 currentTypeName
: typeToCheck
.currentType
.name
,
260 expectedTypeName
: typeToCheck
.expectedTypeName
263 return fixer
.replaceTextRange(
264 typeToCheck
.currentType
.range
.map(indexInComment
=> jsdocNode
.range
[0] + 2 + indexInComment
),
265 typeToCheck
.expectedTypeName
274 * Validate the JSDoc node and output warnings if anything is wrong.
275 * @param {ASTNode} node The AST node to check.
279 function checkJSDoc(node
) {
280 const jsdocNode
= sourceCode
.getJSDocComment(node
),
281 functionData
= fns
.pop(),
282 paramTagsByName
= Object
.create(null),
284 let hasReturns
= false,
286 hasConstructor
= false,
291 // make sure only to validate JSDoc comments
296 jsdoc
= doctrine
.parse(jsdocNode
.value
, {
304 if (/braces/iu.test(ex
.message
)) {
305 context
.report({ node
: jsdocNode
, messageId
: "missingBrace" });
307 context
.report({ node
: jsdocNode
, messageId
: "syntaxError" });
313 jsdoc
.tags
.forEach(tag
=> {
315 switch (tag
.title
.toLowerCase()) {
331 hasConstructor
= true;
351 // check tag preferences
352 if (Object
.prototype.hasOwnProperty
.call(prefer
, tag
.title
) && tag
.title
!== prefer
[tag
.title
]) {
353 const entireTagRange
= getAbsoluteRange(jsdocNode
, tag
);
359 start
: entireTagRange
.start
,
361 line
: entireTagRange
.start
.line
,
362 column
: entireTagRange
.start
.column
+ `@${tag.title}`.length
365 data
: { name
: prefer
[tag
.title
] },
367 return fixer
.replaceTextRange(
369 jsdocNode
.range
[0] + tag
.range
[0] + 3,
370 jsdocNode
.range
[0] + tag
.range
[0] + tag
.title
.length
+ 3
378 // validate the types
379 if (checkPreferType
&& tag
.type
) {
380 validateType(jsdocNode
, tag
.type
);
384 paramTags
.forEach(param
=> {
385 if (requireParamType
&& !param
.type
) {
388 messageId
: "missingParamType",
389 loc
: getAbsoluteRange(jsdocNode
, param
),
390 data
: { name
: param
.name
}
393 if (!param
.description
&& requireParamDescription
) {
396 messageId
: "missingParamDesc",
397 loc
: getAbsoluteRange(jsdocNode
, param
),
398 data
: { name
: param
.name
}
401 if (paramTagsByName
[param
.name
]) {
404 messageId
: "duplicateParam",
405 loc
: getAbsoluteRange(jsdocNode
, param
),
406 data
: { name
: param
.name
}
408 } else if (param
.name
.indexOf(".") === -1) {
409 paramTagsByName
[param
.name
] = param
;
414 if (!requireReturn
&& !functionData
.returnPresent
&& (returnsTag
.type
=== null || !isValidReturnType(returnsTag
)) && !isAbstract
) {
417 messageId
: "unexpectedTag",
418 loc
: getAbsoluteRange(jsdocNode
, returnsTag
),
420 title
: returnsTag
.title
424 if (requireReturnType
&& !returnsTag
.type
) {
425 context
.report({ node
: jsdocNode
, messageId
: "missingReturnType" });
428 if (!isValidReturnType(returnsTag
) && !returnsTag
.description
&& requireReturnDescription
) {
429 context
.report({ node
: jsdocNode
, messageId
: "missingReturnDesc" });
434 // check for functions missing @returns
435 if (!isOverride
&& !hasReturns
&& !hasConstructor
&& !isInterface
&&
436 node
.parent
.kind
!== "get" && node
.parent
.kind
!== "constructor" &&
437 node
.parent
.kind
!== "set" && !isTypeClass(node
)) {
438 if (requireReturn
|| (functionData
.returnPresent
&& !node
.async
)) {
441 messageId
: "missingReturn",
443 returns
: prefer
.returns
|| "returns"
449 // check the parameters
450 const jsdocParamNames
= Object
.keys(paramTagsByName
);
453 node
.params
.forEach((param
, paramsIndex
) => {
454 const bindingParam
= param
.type
=== "AssignmentPattern"
458 // TODO(nzakas): Figure out logical things to do with destructured, default, rest params
459 if (bindingParam
.type
=== "Identifier") {
460 const name
= bindingParam
.name
;
462 if (jsdocParamNames
[paramsIndex
] && (name
!== jsdocParamNames
[paramsIndex
])) {
465 messageId
: "expected",
466 loc
: getAbsoluteRange(jsdocNode
, paramTagsByName
[jsdocParamNames
[paramsIndex
]]),
469 jsdocName
: jsdocParamNames
[paramsIndex
]
472 } else if (!paramTagsByName
[name
] && !isOverride
) {
475 messageId
: "missingParam",
485 if (options
.matchDescription
) {
486 const regex
= new RegExp(options
.matchDescription
, "u");
488 if (!regex
.test(jsdoc
.description
)) {
489 context
.report({ node
: jsdocNode
, messageId
: "unsatisfiedDesc" });
497 //--------------------------------------------------------------------------
499 //--------------------------------------------------------------------------
502 ArrowFunctionExpression
: startFunction
,
503 FunctionExpression
: startFunction
,
504 FunctionDeclaration
: startFunction
,
505 ClassExpression
: startFunction
,
506 ClassDeclaration
: startFunction
,
507 "ArrowFunctionExpression:exit": checkJSDoc
,
508 "FunctionExpression:exit": checkJSDoc
,
509 "FunctionDeclaration:exit": checkJSDoc
,
510 "ClassExpression:exit": checkJSDoc
,
511 "ClassDeclaration:exit": checkJSDoc
,
512 ReturnStatement
: addReturn