@lewishowles/lint-config 0.2.0 → 0.4.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/CHANGELOG.md +15 -0
- package/README.md +87 -64
- package/base.json +57 -57
- package/comments/plugin.js +32 -0
- package/comments/rules/block-comments.js +70 -0
- package/comments/rules/class-documentation.js +131 -0
- package/comments/rules/configured-api-calls.js +143 -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 +290 -0
- package/comments/rules/sentence-punctuation.js +275 -0
- package/comments/rules/variable-declarations.js +88 -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 +349 -0
- package/comments/utils/jsdoc.js +756 -0
- package/comments/utils/source.js +346 -0
- package/comments/utils/vue-macro.js +70 -0
- package/comments/utils/wrap.js +118 -0
- package/comments.json +23 -0
- package/package.json +12 -3
|
@@ -0,0 +1,349 @@
|
|
|
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 the declaration node that owns the documentation position.
|
|
13
|
+
*
|
|
14
|
+
* @param {object} node
|
|
15
|
+
* The declaration node.
|
|
16
|
+
*
|
|
17
|
+
* @returns {object}
|
|
18
|
+
* The node immediately following the documentation block.
|
|
19
|
+
*/
|
|
20
|
+
export function getDocumentationNode(node) {
|
|
21
|
+
// Walks up through export wrappers to find the documented position.
|
|
22
|
+
let documentationNode = node;
|
|
23
|
+
|
|
24
|
+
while (
|
|
25
|
+
documentationNode.parent?.type === "ExportDefaultDeclaration" ||
|
|
26
|
+
documentationNode.parent?.type === "ExportNamedDeclaration"
|
|
27
|
+
) {
|
|
28
|
+
documentationNode = documentationNode.parent;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return documentationNode;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Return whether a node is a function value, used to tell function-valued
|
|
36
|
+
* options and properties apart from named function declarations.
|
|
37
|
+
*
|
|
38
|
+
* @param {object} node
|
|
39
|
+
* The node to inspect.
|
|
40
|
+
*
|
|
41
|
+
* @returns {boolean}
|
|
42
|
+
* Whether the node is a function expression or arrow function.
|
|
43
|
+
*/
|
|
44
|
+
export function isFunctionValue(node) {
|
|
45
|
+
return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Return the JSDoc comment immediately preceding a node, when the comment
|
|
50
|
+
* qualifies as that node's documentation.
|
|
51
|
+
*
|
|
52
|
+
* @param {object} sourceCode
|
|
53
|
+
* The Oxlint source code object.
|
|
54
|
+
* @param {object} node
|
|
55
|
+
* The node to check for a preceding comment.
|
|
56
|
+
*
|
|
57
|
+
* @returns {object|null}
|
|
58
|
+
* The qualifying JSDoc comment, or null when none precedes the node.
|
|
59
|
+
*/
|
|
60
|
+
function getDocumentationComment(sourceCode, node) {
|
|
61
|
+
// Finds the closest preceding comment.
|
|
62
|
+
const comment = sourceCode
|
|
63
|
+
.getAllComments()
|
|
64
|
+
.findLast((candidate) => candidate.range[1] <= node.range[0]);
|
|
65
|
+
|
|
66
|
+
if (comment?.type !== "Block" || isDirectiveComment(comment)) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Checks the comments immediately around the documented node.
|
|
71
|
+
const { next, previous } = getCommentNeighbours(sourceCode, comment);
|
|
72
|
+
// Confirms there is no blank line before the documented node.
|
|
73
|
+
const gap = sourceCode.text.slice(comment.range[1], node.range[0]);
|
|
74
|
+
|
|
75
|
+
if (
|
|
76
|
+
!next ||
|
|
77
|
+
next.range[0] > node.range[0] ||
|
|
78
|
+
next.range[1] > node.range[1] ||
|
|
79
|
+
!isLeadingComment(sourceCode, comment, previous) ||
|
|
80
|
+
!/^\r?\n[ \t]*$/.test(gap)
|
|
81
|
+
) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return isJSDoc(getCommentText(sourceCode, comment)) ? comment : null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Return a default value's original source text.
|
|
90
|
+
*
|
|
91
|
+
* @param {object} sourceCode
|
|
92
|
+
* The Oxlint source code object.
|
|
93
|
+
* @param {object} node
|
|
94
|
+
* The default-value node.
|
|
95
|
+
*
|
|
96
|
+
* @returns {string}
|
|
97
|
+
* The default value's source text.
|
|
98
|
+
*/
|
|
99
|
+
function getDefaultValue(sourceCode, node) {
|
|
100
|
+
return sourceCode.text.slice(node.range[0], node.range[1]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Return JSDoc paths for the properties in an object pattern.
|
|
105
|
+
*
|
|
106
|
+
* @param {object} sourceCode
|
|
107
|
+
* The Oxlint source code object.
|
|
108
|
+
* @param {object} node
|
|
109
|
+
* The object-pattern node.
|
|
110
|
+
* @param {string} parentPath
|
|
111
|
+
* The path for the containing object.
|
|
112
|
+
*
|
|
113
|
+
* @returns {string[]}
|
|
114
|
+
* The required JSDoc parameter paths.
|
|
115
|
+
*/
|
|
116
|
+
function getObjectPatternPaths(sourceCode, node, parentPath) {
|
|
117
|
+
// Collects the parent path alongside each nested property path.
|
|
118
|
+
const paths = [parentPath];
|
|
119
|
+
|
|
120
|
+
for (const property of node.properties) {
|
|
121
|
+
if (property.type !== "Property") {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Skips properties whose key cannot be represented in a JSDoc path.
|
|
126
|
+
const propertyName = getPropertyName(property);
|
|
127
|
+
|
|
128
|
+
if (propertyName === null) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Builds the dotted path used to document this property.
|
|
133
|
+
const propertyPath = `${parentPath}.${propertyName}`;
|
|
134
|
+
// Inspects the property's value to decide how it should be documented.
|
|
135
|
+
const value = property.value;
|
|
136
|
+
|
|
137
|
+
if (value.type === "ObjectPattern") {
|
|
138
|
+
paths.push(...getObjectPatternPaths(sourceCode, value, propertyPath));
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (value.type === "AssignmentPattern") {
|
|
143
|
+
paths.push(`[${propertyPath}=${getDefaultValue(sourceCode, value.right)}]`);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
paths.push(propertyPath);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return paths;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Return JSDoc paths for one declared parameter.
|
|
155
|
+
*
|
|
156
|
+
* @param {object} sourceCode
|
|
157
|
+
* The Oxlint source code object.
|
|
158
|
+
* @param {object} node
|
|
159
|
+
* The parameter node.
|
|
160
|
+
*
|
|
161
|
+
* @returns {string[]}
|
|
162
|
+
* The required JSDoc parameter paths.
|
|
163
|
+
*/
|
|
164
|
+
function getParameterPaths(sourceCode, node) {
|
|
165
|
+
if (node.type === "Identifier") {
|
|
166
|
+
return [node.name];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (node.type === "RestElement" && node.argument.type === "Identifier") {
|
|
170
|
+
return [node.argument.name];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (node.type === "ObjectPattern") {
|
|
174
|
+
return getObjectPatternPaths(sourceCode, node, "options");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (node.type === "AssignmentPattern") {
|
|
178
|
+
return getParameterPaths(sourceCode, node.left);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Return JSDoc parameter names from a documentation block.
|
|
186
|
+
*
|
|
187
|
+
* @param {object} sourceCode
|
|
188
|
+
* The Oxlint source code object.
|
|
189
|
+
* @param {object} comment
|
|
190
|
+
* The JSDoc comment token.
|
|
191
|
+
*
|
|
192
|
+
* @returns {Set<string>}
|
|
193
|
+
* The documented parameter names.
|
|
194
|
+
*/
|
|
195
|
+
function getDocumentedParameters(sourceCode, comment) {
|
|
196
|
+
// Splits the JSDoc block into its individual lines.
|
|
197
|
+
const content = getJSDocContent(getCommentText(sourceCode, comment));
|
|
198
|
+
// Collects the parameter paths documented by @param tags.
|
|
199
|
+
const names = new Set();
|
|
200
|
+
|
|
201
|
+
for (const line of content) {
|
|
202
|
+
// Matches an @param tag and captures its documented path.
|
|
203
|
+
const match = line.trim().match(/^@param(?:\s+\{[^}]+\})?\s+(\[[^\]]+\]|\S+)/);
|
|
204
|
+
|
|
205
|
+
if (match) {
|
|
206
|
+
names.add(match[1]);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return names;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Return whether a function body contains a node type outside nested functions.
|
|
215
|
+
*
|
|
216
|
+
* @param {object} node
|
|
217
|
+
* The node to inspect.
|
|
218
|
+
* @param {string} targetType
|
|
219
|
+
* The statement type to find.
|
|
220
|
+
* @param {Function} matches
|
|
221
|
+
* Return whether a matching statement meets the requirement.
|
|
222
|
+
*
|
|
223
|
+
* @returns {boolean}
|
|
224
|
+
* Whether the target statement appears in the body.
|
|
225
|
+
*/
|
|
226
|
+
function containsStatement(node, targetType, matches = () => true) {
|
|
227
|
+
if (!node || typeof node !== "object") {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (node.type === targetType) {
|
|
232
|
+
return matches(node);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (isFunctionValue(node) || node.type === "FunctionDeclaration") {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
for (const [key, value] of Object.entries(node)) {
|
|
240
|
+
if (key === "parent") {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (Array.isArray(value)) {
|
|
245
|
+
if (value.some((item) => containsStatement(item, targetType, matches))) {
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
} else if (containsStatement(value, targetType, matches)) {
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Return whether a function explicitly returns a value.
|
|
258
|
+
*
|
|
259
|
+
* @param {object} node
|
|
260
|
+
* The function node to inspect.
|
|
261
|
+
*
|
|
262
|
+
* @returns {boolean}
|
|
263
|
+
* Whether the function returns a value.
|
|
264
|
+
*/
|
|
265
|
+
function hasValueReturn(node) {
|
|
266
|
+
if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return containsStatement(
|
|
271
|
+
node.body,
|
|
272
|
+
"ReturnStatement",
|
|
273
|
+
(statement) => statement.argument !== null,
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Report missing documentation requirements for one function.
|
|
279
|
+
*
|
|
280
|
+
* @param {object} context
|
|
281
|
+
* The Oxlint rule context.
|
|
282
|
+
* @param {object} node
|
|
283
|
+
* The documentation-position node.
|
|
284
|
+
* @param {object} functionNode
|
|
285
|
+
* The function node to inspect.
|
|
286
|
+
* @param {object} [options]
|
|
287
|
+
* Optional reporting options.
|
|
288
|
+
* @param {boolean} [options.requiresReturns=true]
|
|
289
|
+
* Whether a returned value requires an @returns tag.
|
|
290
|
+
* @param {string} [options.subject="Functions"]
|
|
291
|
+
* The declaration kind named in report messages, such as "Constructors".
|
|
292
|
+
*/
|
|
293
|
+
export function reportFunctionDocumentation(context, node, functionNode, options = {}) {
|
|
294
|
+
// Defaults to "Functions" when the caller names no declaration kind.
|
|
295
|
+
const subject = options.subject ?? "Functions";
|
|
296
|
+
|
|
297
|
+
// Finds the JSDoc block documenting this function, when present.
|
|
298
|
+
const comment = getDocumentationComment(context.sourceCode, node);
|
|
299
|
+
|
|
300
|
+
if (!comment) {
|
|
301
|
+
context.report({
|
|
302
|
+
message: `${subject} require an immediately preceding JSDoc block.`,
|
|
303
|
+
node,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Reads the parameter paths already documented by @param tags.
|
|
310
|
+
const documentedParameters = getDocumentedParameters(context.sourceCode, comment);
|
|
311
|
+
|
|
312
|
+
// Derives the parameter paths the function actually requires.
|
|
313
|
+
const parameterPaths = functionNode.params.flatMap((parameter) =>
|
|
314
|
+
getParameterPaths(context.sourceCode, parameter),
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
for (const path of parameterPaths) {
|
|
318
|
+
// The optional JSDoc spelling for this parameter path.
|
|
319
|
+
const optionalPath = `[${path}]`;
|
|
320
|
+
|
|
321
|
+
if (!documentedParameters.has(path) && !documentedParameters.has(optionalPath)) {
|
|
322
|
+
context.report({
|
|
323
|
+
message: `${subject} require an @param for ${path}.`,
|
|
324
|
+
node,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Splits the JSDoc block into its individual lines.
|
|
330
|
+
const content = getJSDocContent(getCommentText(context.sourceCode, comment));
|
|
331
|
+
// Checks whether the return value is documented.
|
|
332
|
+
const hasReturns = content.some((line) => /^@returns\b/.test(line.trim()));
|
|
333
|
+
// Checks whether thrown errors are documented.
|
|
334
|
+
const hasThrows = content.some((line) => /^@throws\b/.test(line.trim()));
|
|
335
|
+
|
|
336
|
+
if (options.requiresReturns !== false && hasValueReturn(functionNode) && !hasReturns) {
|
|
337
|
+
context.report({
|
|
338
|
+
message: `${subject} that return a value require an @returns tag.`,
|
|
339
|
+
node,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (containsStatement(functionNode.body, "ThrowStatement") && !hasThrows) {
|
|
344
|
+
context.report({
|
|
345
|
+
message: `${subject} that throw require an @throws tag.`,
|
|
346
|
+
node,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|