@lewishowles/lint-config 0.1.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -63
- package/base.json +57 -51
- package/comments/plugin.js +30 -0
- package/comments/rules/block-comments.js +70 -0
- package/comments/rules/configured-api-calls.js +137 -0
- package/comments/rules/function-documentation.js +111 -0
- package/comments/rules/jsdoc-tag-formatting.js +70 -0
- package/comments/rules/line-comments.js +86 -0
- package/comments/rules/max-line-length.js +159 -0
- package/comments/rules/placement.js +295 -0
- package/comments/rules/sentence-punctuation.js +275 -0
- package/comments/rules/variable-declarations.js +71 -0
- package/comments/rules/vue-component-documentation.js +169 -0
- package/comments/rules/vue-emit-documentation.js +123 -0
- package/comments/rules/vue-prop-documentation.js +224 -0
- package/comments/utils/documentation.js +321 -0
- package/comments/utils/jsdoc.js +756 -0
- package/comments/utils/source.js +312 -0
- package/comments/utils/vue-macro.js +70 -0
- package/comments/utils/wrap.js +118 -0
- package/comments.json +22 -0
- package/package.json +11 -3
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getObjectArgument,
|
|
3
|
+
getObjectProperties,
|
|
4
|
+
getPropertyName,
|
|
5
|
+
isNamedCall,
|
|
6
|
+
} from "../utils/vue-macro.js";
|
|
7
|
+
import { getCommentNeighbours, isDirectiveComment, isLeadingComment } from "../utils/source.js";
|
|
8
|
+
|
|
9
|
+
// Matches the single newline and indentation allowed between a comment and a
|
|
10
|
+
// prop.
|
|
11
|
+
const immediateCommentGapPattern = /^\r?\n[ \t]*$/;
|
|
12
|
+
|
|
13
|
+
// The message shared by runtime props and their withDefaults entries.
|
|
14
|
+
const missingCommentMessage =
|
|
15
|
+
"Vue prop declarations require an immediately preceding block comment.";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Return the runtime props object from a defineProps call or withDefaults
|
|
19
|
+
* wrapper.
|
|
20
|
+
*
|
|
21
|
+
* @param {object} callNode
|
|
22
|
+
* The call to inspect.
|
|
23
|
+
*
|
|
24
|
+
* @returns {object|null}
|
|
25
|
+
* The runtime props object, or null for type-only and unrelated calls.
|
|
26
|
+
*/
|
|
27
|
+
function getPropsObject(callNode) {
|
|
28
|
+
if (isNamedCall(callNode, "defineProps")) {
|
|
29
|
+
return getObjectArgument(callNode, 0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!isNamedCall(callNode, "withDefaults")) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// withDefaults' first argument, expected to be the defineProps call.
|
|
37
|
+
const definePropsCall = callNode.arguments[0];
|
|
38
|
+
|
|
39
|
+
return isNamedCall(definePropsCall, "defineProps") ? getObjectArgument(definePropsCall, 0) : null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Return the defaults object from a withDefaults call.
|
|
44
|
+
*
|
|
45
|
+
* @param {object} callNode
|
|
46
|
+
* The call to inspect.
|
|
47
|
+
*
|
|
48
|
+
* @returns {object|null}
|
|
49
|
+
* The defaults object, when the call has one.
|
|
50
|
+
*/
|
|
51
|
+
function getDefaultsObject(callNode) {
|
|
52
|
+
return isNamedCall(callNode, "withDefaults") ? getObjectArgument(callNode, 1) : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Return whether a defineProps call is already handled by its withDefaults
|
|
57
|
+
* wrapper.
|
|
58
|
+
*
|
|
59
|
+
* @param {object} node
|
|
60
|
+
* The defineProps call to inspect.
|
|
61
|
+
*
|
|
62
|
+
* @returns {boolean}
|
|
63
|
+
* Whether the call is the first argument of withDefaults.
|
|
64
|
+
*/
|
|
65
|
+
function isWrappedDefinePropsCall(node) {
|
|
66
|
+
// The node's enclosing call, when present.
|
|
67
|
+
const parent = node.parent;
|
|
68
|
+
|
|
69
|
+
return isNamedCall(parent, "withDefaults") && parent.arguments[0] === node;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Return whether a property has an immediately preceding block comment.
|
|
74
|
+
*
|
|
75
|
+
* @param {object} sourceCode
|
|
76
|
+
* The Oxlint source code object.
|
|
77
|
+
* @param {object} property
|
|
78
|
+
* The property to inspect.
|
|
79
|
+
*
|
|
80
|
+
* @returns {boolean}
|
|
81
|
+
* Whether the property has the required documentation comment.
|
|
82
|
+
*/
|
|
83
|
+
function hasBlockComment(sourceCode, property) {
|
|
84
|
+
// Finds the closest preceding comment.
|
|
85
|
+
const comment = sourceCode
|
|
86
|
+
.getAllComments()
|
|
87
|
+
.findLast((candidate) => candidate.range[1] <= property.range[0]);
|
|
88
|
+
|
|
89
|
+
if (comment?.type !== "Block" || isDirectiveComment(comment)) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Checks the comments immediately around the property.
|
|
94
|
+
const { next, previous } = getCommentNeighbours(sourceCode, comment);
|
|
95
|
+
// The source text between the comment and the property.
|
|
96
|
+
const gap = sourceCode.text.slice(comment.range[1], property.range[0]);
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
next?.range[0] === property.range[0] &&
|
|
100
|
+
isLeadingComment(sourceCode, comment, previous) &&
|
|
101
|
+
immediateCommentGapPattern.test(gap)
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Return the names of documented properties in an object expression.
|
|
107
|
+
*
|
|
108
|
+
* @param {object} sourceCode
|
|
109
|
+
* The Oxlint source code object.
|
|
110
|
+
* @param {object} objectExpression
|
|
111
|
+
* The object expression to inspect.
|
|
112
|
+
*
|
|
113
|
+
* @returns {Set<string>}
|
|
114
|
+
* The documented property names.
|
|
115
|
+
*/
|
|
116
|
+
function getDocumentedPropertyNames(sourceCode, objectExpression) {
|
|
117
|
+
// Accumulates property names with a qualifying comment.
|
|
118
|
+
const documentedPropertyNames = new Set();
|
|
119
|
+
|
|
120
|
+
for (const property of getObjectProperties(objectExpression)) {
|
|
121
|
+
// The property's name, when it can be determined.
|
|
122
|
+
const propertyName = getPropertyName(property);
|
|
123
|
+
|
|
124
|
+
if (propertyName !== null && hasBlockComment(sourceCode, property)) {
|
|
125
|
+
documentedPropertyNames.add(propertyName);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return documentedPropertyNames;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Report properties without their required block comments.
|
|
134
|
+
*
|
|
135
|
+
* @param {object} context
|
|
136
|
+
* The Oxlint rule context.
|
|
137
|
+
* @param {object} objectExpression
|
|
138
|
+
* The object expression whose properties should be checked.
|
|
139
|
+
* @param {Set<string>|undefined} exemptedPropertyNames
|
|
140
|
+
* Property names documented by a matching defineProps entry.
|
|
141
|
+
*/
|
|
142
|
+
function reportUndocumentedProperties(context, objectExpression, exemptedPropertyNames) {
|
|
143
|
+
for (const property of getObjectProperties(objectExpression)) {
|
|
144
|
+
// The property's name, when it can be determined.
|
|
145
|
+
const propertyName = getPropertyName(property);
|
|
146
|
+
|
|
147
|
+
if (propertyName !== null && exemptedPropertyNames?.has(propertyName)) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!hasBlockComment(context.sourceCode, property)) {
|
|
152
|
+
context.report({ message: missingCommentMessage, node: property });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Check runtime props and withDefaults entries for documentation.
|
|
159
|
+
*
|
|
160
|
+
* @param {object} context
|
|
161
|
+
* The Oxlint rule context.
|
|
162
|
+
* @param {object} node
|
|
163
|
+
* The call expression to inspect.
|
|
164
|
+
*/
|
|
165
|
+
function reportCallDocumentation(context, node) {
|
|
166
|
+
// The call's runtime props object, when present.
|
|
167
|
+
const propsObject = getPropsObject(node);
|
|
168
|
+
|
|
169
|
+
if (!propsObject) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Every runtime defineProps property owns a required comment.
|
|
174
|
+
reportUndocumentedProperties(context, propsObject);
|
|
175
|
+
|
|
176
|
+
// The call's withDefaults defaults object, when present.
|
|
177
|
+
const defaultsObject = getDefaultsObject(node);
|
|
178
|
+
|
|
179
|
+
if (!defaultsObject) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// A matching defineProps comment documents the corresponding default too.
|
|
184
|
+
const documentedPropertyNames = getDocumentedPropertyNames(context.sourceCode, propsObject);
|
|
185
|
+
|
|
186
|
+
reportUndocumentedProperties(context, defaultsObject, documentedPropertyNames);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export default {
|
|
190
|
+
meta: {
|
|
191
|
+
docs: { description: "Require block comments for Vue runtime props." },
|
|
192
|
+
type: "suggestion",
|
|
193
|
+
},
|
|
194
|
+
/**
|
|
195
|
+
* Create the rule's node visitors.
|
|
196
|
+
*
|
|
197
|
+
* @param {object} context
|
|
198
|
+
* The Oxlint rule context.
|
|
199
|
+
*
|
|
200
|
+
* @returns {object}
|
|
201
|
+
* The call-expression visitor for this rule.
|
|
202
|
+
*/
|
|
203
|
+
createOnce(context) {
|
|
204
|
+
return {
|
|
205
|
+
/**
|
|
206
|
+
* Check defineProps calls and their withDefaults wrapper.
|
|
207
|
+
*
|
|
208
|
+
* @param {object} node
|
|
209
|
+
* The call expression to inspect.
|
|
210
|
+
*/
|
|
211
|
+
CallExpression(node) {
|
|
212
|
+
if (!isNamedCall(node, "defineProps") && !isNamedCall(node, "withDefaults")) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (isNamedCall(node, "defineProps") && isWrappedDefinePropsCall(node)) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
reportCallDocumentation(context, node);
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
},
|
|
224
|
+
};
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { getJSDocContent, isJSDoc } from "./jsdoc.js";
|
|
2
|
+
import { getPropertyName } from "./vue-macro.js";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
getCommentNeighbours,
|
|
6
|
+
getCommentText,
|
|
7
|
+
isDirectiveComment,
|
|
8
|
+
isLeadingComment,
|
|
9
|
+
} from "./source.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Return whether a node is a function value, used to tell function-valued
|
|
13
|
+
* options and properties apart from named function declarations.
|
|
14
|
+
*
|
|
15
|
+
* @param {object} node
|
|
16
|
+
* The node to inspect.
|
|
17
|
+
*
|
|
18
|
+
* @returns {boolean}
|
|
19
|
+
* Whether the node is a function expression or arrow function.
|
|
20
|
+
*/
|
|
21
|
+
export function isFunctionValue(node) {
|
|
22
|
+
return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Return the JSDoc comment immediately preceding a node, when the comment
|
|
27
|
+
* qualifies as that node's documentation.
|
|
28
|
+
*
|
|
29
|
+
* @param {object} sourceCode
|
|
30
|
+
* The Oxlint source code object.
|
|
31
|
+
* @param {object} node
|
|
32
|
+
* The node to check for a preceding comment.
|
|
33
|
+
*
|
|
34
|
+
* @returns {object|null}
|
|
35
|
+
* The qualifying JSDoc comment, or null when none precedes the node.
|
|
36
|
+
*/
|
|
37
|
+
function getDocumentationComment(sourceCode, node) {
|
|
38
|
+
// Finds the closest preceding comment.
|
|
39
|
+
const comment = sourceCode
|
|
40
|
+
.getAllComments()
|
|
41
|
+
.findLast((candidate) => candidate.range[1] <= node.range[0]);
|
|
42
|
+
|
|
43
|
+
if (comment?.type !== "Block" || isDirectiveComment(comment)) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Checks the comments immediately around the documented node.
|
|
48
|
+
const { next, previous } = getCommentNeighbours(sourceCode, comment);
|
|
49
|
+
// Confirms there is no blank line before the documented node.
|
|
50
|
+
const gap = sourceCode.text.slice(comment.range[1], node.range[0]);
|
|
51
|
+
|
|
52
|
+
if (
|
|
53
|
+
!next ||
|
|
54
|
+
next.range[0] > node.range[0] ||
|
|
55
|
+
next.range[1] > node.range[1] ||
|
|
56
|
+
!isLeadingComment(sourceCode, comment, previous) ||
|
|
57
|
+
!/^\r?\n[ \t]*$/.test(gap)
|
|
58
|
+
) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return isJSDoc(getCommentText(sourceCode, comment)) ? comment : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Return a default value's original source text.
|
|
67
|
+
*
|
|
68
|
+
* @param {object} sourceCode
|
|
69
|
+
* The Oxlint source code object.
|
|
70
|
+
* @param {object} node
|
|
71
|
+
* The default-value node.
|
|
72
|
+
*
|
|
73
|
+
* @returns {string}
|
|
74
|
+
* The default value's source text.
|
|
75
|
+
*/
|
|
76
|
+
function getDefaultValue(sourceCode, node) {
|
|
77
|
+
return sourceCode.text.slice(node.range[0], node.range[1]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Return JSDoc paths for the properties in an object pattern.
|
|
82
|
+
*
|
|
83
|
+
* @param {object} sourceCode
|
|
84
|
+
* The Oxlint source code object.
|
|
85
|
+
* @param {object} node
|
|
86
|
+
* The object-pattern node.
|
|
87
|
+
* @param {string} parentPath
|
|
88
|
+
* The path for the containing object.
|
|
89
|
+
*
|
|
90
|
+
* @returns {string[]}
|
|
91
|
+
* The required JSDoc parameter paths.
|
|
92
|
+
*/
|
|
93
|
+
function getObjectPatternPaths(sourceCode, node, parentPath) {
|
|
94
|
+
// Collects the parent path alongside each nested property path.
|
|
95
|
+
const paths = [parentPath];
|
|
96
|
+
|
|
97
|
+
for (const property of node.properties) {
|
|
98
|
+
if (property.type !== "Property") {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Skips properties whose key cannot be represented in a JSDoc path.
|
|
103
|
+
const propertyName = getPropertyName(property);
|
|
104
|
+
|
|
105
|
+
if (propertyName === null) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Builds the dotted path used to document this property.
|
|
110
|
+
const propertyPath = `${parentPath}.${propertyName}`;
|
|
111
|
+
// Inspects the property's value to decide how it should be documented.
|
|
112
|
+
const value = property.value;
|
|
113
|
+
|
|
114
|
+
if (value.type === "ObjectPattern") {
|
|
115
|
+
paths.push(...getObjectPatternPaths(sourceCode, value, propertyPath));
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (value.type === "AssignmentPattern") {
|
|
120
|
+
paths.push(`[${propertyPath}=${getDefaultValue(sourceCode, value.right)}]`);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
paths.push(propertyPath);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return paths;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Return JSDoc paths for one declared parameter.
|
|
132
|
+
*
|
|
133
|
+
* @param {object} sourceCode
|
|
134
|
+
* The Oxlint source code object.
|
|
135
|
+
* @param {object} node
|
|
136
|
+
* The parameter node.
|
|
137
|
+
*
|
|
138
|
+
* @returns {string[]}
|
|
139
|
+
* The required JSDoc parameter paths.
|
|
140
|
+
*/
|
|
141
|
+
function getParameterPaths(sourceCode, node) {
|
|
142
|
+
if (node.type === "Identifier") {
|
|
143
|
+
return [node.name];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (node.type === "RestElement" && node.argument.type === "Identifier") {
|
|
147
|
+
return [node.argument.name];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (node.type === "ObjectPattern") {
|
|
151
|
+
return getObjectPatternPaths(sourceCode, node, "options");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (node.type === "AssignmentPattern") {
|
|
155
|
+
return getParameterPaths(sourceCode, node.left);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Return JSDoc parameter names from a documentation block.
|
|
163
|
+
*
|
|
164
|
+
* @param {object} sourceCode
|
|
165
|
+
* The Oxlint source code object.
|
|
166
|
+
* @param {object} comment
|
|
167
|
+
* The JSDoc comment token.
|
|
168
|
+
*
|
|
169
|
+
* @returns {Set<string>}
|
|
170
|
+
* The documented parameter names.
|
|
171
|
+
*/
|
|
172
|
+
function getDocumentedParameters(sourceCode, comment) {
|
|
173
|
+
// Splits the JSDoc block into its individual lines.
|
|
174
|
+
const content = getJSDocContent(getCommentText(sourceCode, comment));
|
|
175
|
+
// Collects the parameter paths documented by @param tags.
|
|
176
|
+
const names = new Set();
|
|
177
|
+
|
|
178
|
+
for (const line of content) {
|
|
179
|
+
// Matches an @param tag and captures its documented path.
|
|
180
|
+
const match = line.trim().match(/^@param(?:\s+\{[^}]+\})?\s+(\[[^\]]+\]|\S+)/);
|
|
181
|
+
|
|
182
|
+
if (match) {
|
|
183
|
+
names.add(match[1]);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return names;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Return whether a function body contains a node type outside nested functions.
|
|
192
|
+
*
|
|
193
|
+
* @param {object} node
|
|
194
|
+
* The node to inspect.
|
|
195
|
+
* @param {string} targetType
|
|
196
|
+
* The statement type to find.
|
|
197
|
+
* @param {Function} matches
|
|
198
|
+
* Return whether a matching statement meets the requirement.
|
|
199
|
+
*
|
|
200
|
+
* @returns {boolean}
|
|
201
|
+
* Whether the target statement appears in the body.
|
|
202
|
+
*/
|
|
203
|
+
function containsStatement(node, targetType, matches = () => true) {
|
|
204
|
+
if (!node || typeof node !== "object") {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (node.type === targetType) {
|
|
209
|
+
return matches(node);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (isFunctionValue(node) || node.type === "FunctionDeclaration") {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
for (const [key, value] of Object.entries(node)) {
|
|
217
|
+
if (key === "parent") {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (Array.isArray(value)) {
|
|
222
|
+
if (value.some((item) => containsStatement(item, targetType, matches))) {
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
} else if (containsStatement(value, targetType, matches)) {
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Return whether a function explicitly returns a value.
|
|
235
|
+
*
|
|
236
|
+
* @param {object} node
|
|
237
|
+
* The function node to inspect.
|
|
238
|
+
*
|
|
239
|
+
* @returns {boolean}
|
|
240
|
+
* Whether the function returns a value.
|
|
241
|
+
*/
|
|
242
|
+
function hasValueReturn(node) {
|
|
243
|
+
if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return containsStatement(
|
|
248
|
+
node.body,
|
|
249
|
+
"ReturnStatement",
|
|
250
|
+
(statement) => statement.argument !== null,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Report missing documentation requirements for one function.
|
|
256
|
+
*
|
|
257
|
+
* @param {object} context
|
|
258
|
+
* The Oxlint rule context.
|
|
259
|
+
* @param {object} node
|
|
260
|
+
* The documentation-position node.
|
|
261
|
+
* @param {object} functionNode
|
|
262
|
+
* The function node to inspect.
|
|
263
|
+
* @param {object} [options]
|
|
264
|
+
* Optional reporting options.
|
|
265
|
+
* @param {boolean} [options.requiresReturns=true]
|
|
266
|
+
* Whether a returned value requires an @returns tag.
|
|
267
|
+
*/
|
|
268
|
+
export function reportFunctionDocumentation(context, node, functionNode, options = {}) {
|
|
269
|
+
// Finds the JSDoc block documenting this function, when present.
|
|
270
|
+
const comment = getDocumentationComment(context.sourceCode, node);
|
|
271
|
+
|
|
272
|
+
if (!comment) {
|
|
273
|
+
context.report({
|
|
274
|
+
message: "Functions require an immediately preceding JSDoc block.",
|
|
275
|
+
node,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Reads the parameter paths already documented by @param tags.
|
|
282
|
+
const documentedParameters = getDocumentedParameters(context.sourceCode, comment);
|
|
283
|
+
|
|
284
|
+
// Derives the parameter paths the function actually requires.
|
|
285
|
+
const parameterPaths = functionNode.params.flatMap((parameter) =>
|
|
286
|
+
getParameterPaths(context.sourceCode, parameter),
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
for (const path of parameterPaths) {
|
|
290
|
+
// The optional JSDoc spelling for this parameter path.
|
|
291
|
+
const optionalPath = `[${path}]`;
|
|
292
|
+
|
|
293
|
+
if (!documentedParameters.has(path) && !documentedParameters.has(optionalPath)) {
|
|
294
|
+
context.report({
|
|
295
|
+
message: `Functions require an @param for ${path}.`,
|
|
296
|
+
node,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Splits the JSDoc block into its individual lines.
|
|
302
|
+
const content = getJSDocContent(getCommentText(context.sourceCode, comment));
|
|
303
|
+
// Checks whether the return value is documented.
|
|
304
|
+
const hasReturns = content.some((line) => /^@returns\b/.test(line.trim()));
|
|
305
|
+
// Checks whether thrown errors are documented.
|
|
306
|
+
const hasThrows = content.some((line) => /^@throws\b/.test(line.trim()));
|
|
307
|
+
|
|
308
|
+
if (options.requiresReturns !== false && hasValueReturn(functionNode) && !hasReturns) {
|
|
309
|
+
context.report({
|
|
310
|
+
message: "Functions that return a value require an @returns tag.",
|
|
311
|
+
node,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (containsStatement(functionNode.body, "ThrowStatement") && !hasThrows) {
|
|
316
|
+
context.report({
|
|
317
|
+
message: "Functions that throw require an @throws tag.",
|
|
318
|
+
node,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
}
|