@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,290 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getCommentNeighbours,
|
|
3
|
+
getCommentText,
|
|
4
|
+
getLineCommentGroups,
|
|
5
|
+
getLineIndent,
|
|
6
|
+
getLineStart,
|
|
7
|
+
getNewline,
|
|
8
|
+
isDirectiveComment,
|
|
9
|
+
isLeadingComment,
|
|
10
|
+
} from "../utils/source.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Return continuation comments indexed by their group leader.
|
|
14
|
+
*
|
|
15
|
+
* @param {object[][]} lineCommentGroups
|
|
16
|
+
* The adjacent line-comment groups.
|
|
17
|
+
*
|
|
18
|
+
* @returns {object}
|
|
19
|
+
* The continuation comments and their leaders.
|
|
20
|
+
*/
|
|
21
|
+
function getLineCommentContinuations(lineCommentGroups) {
|
|
22
|
+
// The comments that follow a group's leader, across every group.
|
|
23
|
+
const continuationComments = new Set();
|
|
24
|
+
// Each group's continuation comments, indexed by their leader.
|
|
25
|
+
const continuationsByLeader = new Map();
|
|
26
|
+
|
|
27
|
+
for (const group of lineCommentGroups) {
|
|
28
|
+
if (group.length < 2) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// The group's leading comment and its continuations.
|
|
33
|
+
const [leader, ...continuations] = group;
|
|
34
|
+
|
|
35
|
+
continuationsByLeader.set(leader, continuations);
|
|
36
|
+
|
|
37
|
+
for (const continuation of continuations) {
|
|
38
|
+
continuationComments.add(continuation);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { continuationComments, continuationsByLeader };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Return indentation relative to a comment's current indentation.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} indentation
|
|
49
|
+
* The line indentation.
|
|
50
|
+
* @param {string} commentIndent
|
|
51
|
+
* The leading comment's current indentation.
|
|
52
|
+
*
|
|
53
|
+
* @returns {string}
|
|
54
|
+
* The indentation to preserve after reindenting.
|
|
55
|
+
*/
|
|
56
|
+
function getRelativeIndent(indentation, commentIndent) {
|
|
57
|
+
return indentation.startsWith(commentIndent)
|
|
58
|
+
? indentation.slice(commentIndent.length)
|
|
59
|
+
: indentation;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Reindent every line of a leading comment.
|
|
64
|
+
*
|
|
65
|
+
* @param {object} sourceCode
|
|
66
|
+
* The Oxlint source code object.
|
|
67
|
+
* @param {object} comment
|
|
68
|
+
* The leading comment token.
|
|
69
|
+
* @param {string} commentIndent
|
|
70
|
+
* The comment's current indentation.
|
|
71
|
+
* @param {string} expectedIndent
|
|
72
|
+
* The documented code's indentation.
|
|
73
|
+
*
|
|
74
|
+
* @returns {string}
|
|
75
|
+
* The reindented comment text.
|
|
76
|
+
*/
|
|
77
|
+
function getReindentedCommentText(sourceCode, comment, commentIndent, expectedIndent) {
|
|
78
|
+
return getCommentText(sourceCode, comment)
|
|
79
|
+
.split(/\r\n|\n|\r/)
|
|
80
|
+
.map((line, lineIndex) => {
|
|
81
|
+
if (lineIndex === 0) {
|
|
82
|
+
return `${expectedIndent}${line}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// The line's current indentation.
|
|
86
|
+
const lineIndent = line.match(/^[ \t]*/)[0];
|
|
87
|
+
// The indentation to preserve relative to the comment's own indent.
|
|
88
|
+
const relativeIndent = getRelativeIndent(lineIndent, commentIndent);
|
|
89
|
+
|
|
90
|
+
return `${expectedIndent}${relativeIndent}${line.slice(lineIndent.length)}`;
|
|
91
|
+
})
|
|
92
|
+
.join(getNewline(sourceCode.text));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Return replacements that align a leading comment group with its code.
|
|
97
|
+
*
|
|
98
|
+
* @param {object} sourceCode
|
|
99
|
+
* The Oxlint source code object.
|
|
100
|
+
* @param {object} comment
|
|
101
|
+
* The leading comment token.
|
|
102
|
+
* @param {object[]} continuations
|
|
103
|
+
* The comment group's continuation tokens.
|
|
104
|
+
* @param {string} actualIndent
|
|
105
|
+
* The comment's current indentation.
|
|
106
|
+
* @param {string} expectedIndent
|
|
107
|
+
* The documented code's indentation.
|
|
108
|
+
*
|
|
109
|
+
* @returns {object[]}
|
|
110
|
+
* The indentation replacements.
|
|
111
|
+
*/
|
|
112
|
+
function getCommentIndentationFixes(
|
|
113
|
+
sourceCode,
|
|
114
|
+
comment,
|
|
115
|
+
continuations,
|
|
116
|
+
actualIndent,
|
|
117
|
+
expectedIndent,
|
|
118
|
+
) {
|
|
119
|
+
if (actualIndent === expectedIndent) {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// The replacements, starting with the leading comment's reindent.
|
|
124
|
+
const fixes = [
|
|
125
|
+
{
|
|
126
|
+
range: [getLineStart(sourceCode, comment.range[0]), comment.range[1]],
|
|
127
|
+
text: getReindentedCommentText(sourceCode, comment, actualIndent, expectedIndent),
|
|
128
|
+
},
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
for (const continuation of continuations) {
|
|
132
|
+
// The continuation's current indentation.
|
|
133
|
+
const continuationIndent = getLineIndent(sourceCode, continuation.range[0]) ?? "";
|
|
134
|
+
// The indentation to preserve relative to the leading comment's indent.
|
|
135
|
+
const relativeIndent = getRelativeIndent(continuationIndent, actualIndent);
|
|
136
|
+
|
|
137
|
+
fixes.push({
|
|
138
|
+
range: [getLineStart(sourceCode, continuation.range[0]), continuation.range[1]],
|
|
139
|
+
text: `${expectedIndent}${relativeIndent}${getCommentText(sourceCode, continuation)}`,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return fixes;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Return the replacement that closes the gap after a final leading comment.
|
|
148
|
+
*
|
|
149
|
+
* @param {object} sourceCode
|
|
150
|
+
* The Oxlint source code object.
|
|
151
|
+
* @param {object} comment
|
|
152
|
+
* The leading comment token.
|
|
153
|
+
* @param {object} next
|
|
154
|
+
* The documented source token.
|
|
155
|
+
* @param {object|undefined} followingComment
|
|
156
|
+
* The comment after this one in source order, when there is one.
|
|
157
|
+
* @param {string} expectedIndent
|
|
158
|
+
* The documented code's indentation.
|
|
159
|
+
*
|
|
160
|
+
* @returns {object|null}
|
|
161
|
+
* The gap replacement, or null when none is needed.
|
|
162
|
+
*/
|
|
163
|
+
function getCommentGapFix(sourceCode, comment, next, followingComment, expectedIndent) {
|
|
164
|
+
// Whether another comment sits between this one and its documented code.
|
|
165
|
+
const followingCommentIntervenes =
|
|
166
|
+
followingComment !== undefined && followingComment.range[0] <= next.range[0];
|
|
167
|
+
|
|
168
|
+
if (followingCommentIntervenes && !isDirectiveComment(followingComment)) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Stop at an intervening directive so the fix range never overlaps it.
|
|
173
|
+
const gapEnd = followingCommentIntervenes ? followingComment.range[0] : next.range[0];
|
|
174
|
+
|
|
175
|
+
// What currently follows the comment, up to the code or directive.
|
|
176
|
+
const gap = sourceCode.text.slice(comment.range[1], gapEnd);
|
|
177
|
+
// The gap the documented code's indentation requires.
|
|
178
|
+
const desiredGap = `${getNewline(sourceCode.text)}${expectedIndent}`;
|
|
179
|
+
|
|
180
|
+
return gap === desiredGap ? null : { range: [comment.range[1], gapEnd], text: desiredGap };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Create the immediate-comment-placement rule.
|
|
185
|
+
*
|
|
186
|
+
* @returns {object}
|
|
187
|
+
* The Oxlint rule definition.
|
|
188
|
+
*/
|
|
189
|
+
export default {
|
|
190
|
+
meta: {
|
|
191
|
+
docs: { description: "Keep comments immediately before documented code." },
|
|
192
|
+
fixable: "code",
|
|
193
|
+
type: "layout",
|
|
194
|
+
},
|
|
195
|
+
/**
|
|
196
|
+
* Create the rule's node visitors.
|
|
197
|
+
*
|
|
198
|
+
* @param {object} context
|
|
199
|
+
* The Oxlint rule context.
|
|
200
|
+
*
|
|
201
|
+
* @returns {object}
|
|
202
|
+
* The visitor functions for this rule.
|
|
203
|
+
*/
|
|
204
|
+
createOnce(context) {
|
|
205
|
+
return {
|
|
206
|
+
/**
|
|
207
|
+
* Align every leading comment in the file with its documented code.
|
|
208
|
+
*/
|
|
209
|
+
Program() {
|
|
210
|
+
// Every comment token in the file, in source order.
|
|
211
|
+
const comments = context.sourceCode.getAllComments();
|
|
212
|
+
|
|
213
|
+
// The continuation comments and their group leaders.
|
|
214
|
+
const { continuationComments, continuationsByLeader } = getLineCommentContinuations(
|
|
215
|
+
getLineCommentGroups(context.sourceCode),
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
for (const [index, comment] of comments.entries()) {
|
|
219
|
+
if (
|
|
220
|
+
comment.type === "Shebang" ||
|
|
221
|
+
isDirectiveComment(comment) ||
|
|
222
|
+
continuationComments.has(comment)
|
|
223
|
+
) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// The comment's neighbouring token and comment.
|
|
228
|
+
const { next, previous } = getCommentNeighbours(context.sourceCode, comment);
|
|
229
|
+
|
|
230
|
+
if (next === null || !isLeadingComment(context.sourceCode, comment, previous)) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// The documented code's indentation.
|
|
235
|
+
const expectedIndent = getLineIndent(context.sourceCode, next.range[0]);
|
|
236
|
+
// The comment's current indentation.
|
|
237
|
+
const actualIndent = getLineIndent(context.sourceCode, comment.range[0]);
|
|
238
|
+
|
|
239
|
+
if (expectedIndent === null || actualIndent === null) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// The reindentation fixes for the comment and its continuations.
|
|
244
|
+
const fixes = getCommentIndentationFixes(
|
|
245
|
+
context.sourceCode,
|
|
246
|
+
comment,
|
|
247
|
+
continuationsByLeader.get(comment) ?? [],
|
|
248
|
+
actualIndent,
|
|
249
|
+
expectedIndent,
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
// The next comment token, used to avoid overlapping gap fixes.
|
|
253
|
+
const followingComment = comments[index + 1];
|
|
254
|
+
|
|
255
|
+
// The fix that closes the gap between the comment and its code, when needed.
|
|
256
|
+
const gapFix = getCommentGapFix(
|
|
257
|
+
context.sourceCode,
|
|
258
|
+
comment,
|
|
259
|
+
next,
|
|
260
|
+
followingComment,
|
|
261
|
+
expectedIndent,
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
if (gapFix !== null) {
|
|
265
|
+
fixes.push(gapFix);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (fixes.length === 0) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
context.report({
|
|
273
|
+
/**
|
|
274
|
+
* Apply the comment's alignment fixes.
|
|
275
|
+
*
|
|
276
|
+
* @param {object} fixer
|
|
277
|
+
* The Oxlint fixer.
|
|
278
|
+
*
|
|
279
|
+
* @returns {object[]}
|
|
280
|
+
* The fixes to apply.
|
|
281
|
+
*/
|
|
282
|
+
fix: (fixer) => fixes.map((fix) => fixer.replaceTextRange(fix.range, fix.text)),
|
|
283
|
+
message: "Comment must be immediately before the documented code.",
|
|
284
|
+
node: comment,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
},
|
|
290
|
+
};
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { addTerminalPunctuation, capitaliseSentence, formatSentence } from "../utils/wrap.js";
|
|
2
|
+
import { formatJSDocPunctuation, isJSDoc } from "../utils/jsdoc.js";
|
|
3
|
+
import {
|
|
4
|
+
getCommentText,
|
|
5
|
+
getLineCommentGroups,
|
|
6
|
+
getLineIndent,
|
|
7
|
+
getNewline,
|
|
8
|
+
replaceMinimalComment,
|
|
9
|
+
} from "../utils/source.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Replace only the value of a line comment.
|
|
13
|
+
*
|
|
14
|
+
* @param {object} sourceCode
|
|
15
|
+
* The Oxlint source code object.
|
|
16
|
+
* @param {object} comment
|
|
17
|
+
* The line comment token.
|
|
18
|
+
* @param {string} value
|
|
19
|
+
* The replacement comment value.
|
|
20
|
+
*
|
|
21
|
+
* @returns {string}
|
|
22
|
+
* The replacement comment text.
|
|
23
|
+
*/
|
|
24
|
+
function replaceLineCommentValue(sourceCode, comment, value) {
|
|
25
|
+
// The comment's raw source text.
|
|
26
|
+
const commentText = getCommentText(sourceCode, comment);
|
|
27
|
+
|
|
28
|
+
return `${commentText.slice(0, 2)}${value}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Format the prose text in a standard block-comment line.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} line
|
|
35
|
+
* The block-comment line.
|
|
36
|
+
* @param {function} formatProse
|
|
37
|
+
* The formatter for the line's prose.
|
|
38
|
+
*
|
|
39
|
+
* @returns {string}
|
|
40
|
+
* The formatted block-comment line.
|
|
41
|
+
*/
|
|
42
|
+
function formatBlockCommentLine(line, formatProse) {
|
|
43
|
+
// The line's leading `*` decoration, when present.
|
|
44
|
+
const marker = line.match(/^\s*\*\s?/);
|
|
45
|
+
|
|
46
|
+
if (marker === null) {
|
|
47
|
+
return line;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return `${marker[0]}${formatProse(line.slice(marker[0].length).trim())}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Return a sentence-formatted line-comment group.
|
|
55
|
+
*
|
|
56
|
+
* @param {object} sourceCode
|
|
57
|
+
* The Oxlint source code object.
|
|
58
|
+
* @param {object[]} comments
|
|
59
|
+
* The adjacent line comments.
|
|
60
|
+
*
|
|
61
|
+
* @returns {object|null}
|
|
62
|
+
* The first and last replacements, or null when no sentence needs work.
|
|
63
|
+
*/
|
|
64
|
+
function formatLineCommentGroup(sourceCode, comments) {
|
|
65
|
+
// The group's first comment token.
|
|
66
|
+
const firstComment = comments[0];
|
|
67
|
+
// The group's last comment token.
|
|
68
|
+
const lastComment = comments.at(-1);
|
|
69
|
+
// The first comment's undecorated text.
|
|
70
|
+
const firstText = firstComment.value.trim();
|
|
71
|
+
|
|
72
|
+
if (firstText === "" || firstText.startsWith("@")) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// The first comment's value, capitalised or fully sentence-formatted.
|
|
77
|
+
const firstValue =
|
|
78
|
+
comments.length === 1
|
|
79
|
+
? formatSentence(firstComment.value)
|
|
80
|
+
: capitaliseSentence(firstComment.value);
|
|
81
|
+
|
|
82
|
+
// The last comment's value, punctuated to close the sentence.
|
|
83
|
+
const lastValue = comments.length === 1 ? firstValue : addTerminalPunctuation(lastComment.value);
|
|
84
|
+
// The replacement comment text for the first and last comments.
|
|
85
|
+
const firstReplacement = replaceLineCommentValue(sourceCode, firstComment, firstValue);
|
|
86
|
+
// The replacement comment text for the last comment.
|
|
87
|
+
const lastReplacement = replaceLineCommentValue(sourceCode, lastComment, lastValue);
|
|
88
|
+
|
|
89
|
+
if (
|
|
90
|
+
firstReplacement === getCommentText(sourceCode, firstComment) &&
|
|
91
|
+
lastReplacement === getCommentText(sourceCode, lastComment)
|
|
92
|
+
) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return { firstComment, firstReplacement, lastComment, lastReplacement };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Format prose in an ordinary block comment.
|
|
101
|
+
*
|
|
102
|
+
* @param {object} sourceCode
|
|
103
|
+
* The Oxlint source code object.
|
|
104
|
+
* @param {object} comment
|
|
105
|
+
* The block comment token.
|
|
106
|
+
*
|
|
107
|
+
* @returns {string}
|
|
108
|
+
* The sentence-formatted comment text.
|
|
109
|
+
*/
|
|
110
|
+
function formatOrdinaryBlockComment(sourceCode, comment) {
|
|
111
|
+
// The comment's raw source text.
|
|
112
|
+
const commentText = getCommentText(sourceCode, comment);
|
|
113
|
+
// The indentation the comment's lines are aligned to.
|
|
114
|
+
const indentation = getLineIndent(sourceCode, comment.range[0]);
|
|
115
|
+
// The comment body, stripped of its /* */ delimiters.
|
|
116
|
+
const content = commentText.slice(2, -2).trim();
|
|
117
|
+
|
|
118
|
+
if (indentation === null || content === "" || /^(?:eslint|oxlint|istanbul|c8)-/.test(content)) {
|
|
119
|
+
return commentText;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!commentText.includes("\n") && !commentText.includes("\r")) {
|
|
123
|
+
return `/* ${formatSentence(content)} */`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// The comment's individual source lines.
|
|
127
|
+
const lines = commentText.split(/\r\n|\n|\r/);
|
|
128
|
+
|
|
129
|
+
// The indexes of lines carrying prose, excluding the delimiter lines.
|
|
130
|
+
const proseLineIndexes = lines
|
|
131
|
+
.slice(1, -1)
|
|
132
|
+
.map((line, index) => ({ index: index + 1, text: line.replace(/^\s*\*?\s?/, "").trim() }))
|
|
133
|
+
.filter((line) => line.text !== "")
|
|
134
|
+
.map((line) => line.index);
|
|
135
|
+
|
|
136
|
+
if (lines[0] === "/*" && lines.at(-1).trim() === "*/" && proseLineIndexes.length > 0) {
|
|
137
|
+
// The comment lines, formatted in place.
|
|
138
|
+
const formattedLines = [...lines];
|
|
139
|
+
// The first and last prose line indexes, which start and end the sentence.
|
|
140
|
+
const firstProseLine = proseLineIndexes[0];
|
|
141
|
+
// The last prose line index, which ends the sentence.
|
|
142
|
+
const lastProseLine = proseLineIndexes.at(-1);
|
|
143
|
+
|
|
144
|
+
formattedLines[firstProseLine] = formatBlockCommentLine(
|
|
145
|
+
formattedLines[firstProseLine],
|
|
146
|
+
capitaliseSentence,
|
|
147
|
+
);
|
|
148
|
+
formattedLines[lastProseLine] = formatBlockCommentLine(
|
|
149
|
+
formattedLines[lastProseLine],
|
|
150
|
+
addTerminalPunctuation,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
return formattedLines.join(getNewline(sourceCode.text));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// The comment's prose, joined into a single paragraph.
|
|
157
|
+
const paragraphs = content
|
|
158
|
+
.split(/\r\n|\n|\r/)
|
|
159
|
+
.map((line) => line.replace(/^\s*\*?\s?/, "").trim())
|
|
160
|
+
.filter(Boolean)
|
|
161
|
+
.join(" ");
|
|
162
|
+
|
|
163
|
+
return ["/*", `${indentation} * ${formatSentence(paragraphs)}`, `${indentation} */`].join(
|
|
164
|
+
getNewline(sourceCode.text),
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Create the complete-sentence comment rule.
|
|
170
|
+
*
|
|
171
|
+
* @returns {object}
|
|
172
|
+
* The Oxlint rule definition.
|
|
173
|
+
*/
|
|
174
|
+
export default {
|
|
175
|
+
meta: {
|
|
176
|
+
docs: { description: "Format existing comments as complete sentences." },
|
|
177
|
+
fixable: "code",
|
|
178
|
+
type: "layout",
|
|
179
|
+
},
|
|
180
|
+
/**
|
|
181
|
+
* Create the rule's node visitors.
|
|
182
|
+
*
|
|
183
|
+
* @param {object} context
|
|
184
|
+
* The Oxlint rule context.
|
|
185
|
+
*
|
|
186
|
+
* @returns {object}
|
|
187
|
+
* The visitor functions for this rule.
|
|
188
|
+
*/
|
|
189
|
+
createOnce(context) {
|
|
190
|
+
return {
|
|
191
|
+
/**
|
|
192
|
+
* Format every comment in the file as a complete sentence.
|
|
193
|
+
*/
|
|
194
|
+
Program() {
|
|
195
|
+
for (const commentGroup of getLineCommentGroups(context.sourceCode)) {
|
|
196
|
+
// The group's replacement text, or null when it already reads as a sentence.
|
|
197
|
+
const formattedGroup = formatLineCommentGroup(context.sourceCode, commentGroup);
|
|
198
|
+
|
|
199
|
+
if (formattedGroup) {
|
|
200
|
+
context.report({
|
|
201
|
+
/**
|
|
202
|
+
* Apply the formatted replacement to the comment group.
|
|
203
|
+
*
|
|
204
|
+
* @param {object} fixer
|
|
205
|
+
* The Oxlint fixer.
|
|
206
|
+
*
|
|
207
|
+
* @returns {object[]}
|
|
208
|
+
* The fixes to apply.
|
|
209
|
+
*/
|
|
210
|
+
fix: (fixer) => {
|
|
211
|
+
// The fixes to apply, starting with the first comment's replacement.
|
|
212
|
+
const fixes = [
|
|
213
|
+
replaceMinimalComment(
|
|
214
|
+
fixer,
|
|
215
|
+
formattedGroup.firstComment,
|
|
216
|
+
getCommentText(context.sourceCode, formattedGroup.firstComment),
|
|
217
|
+
formattedGroup.firstReplacement,
|
|
218
|
+
),
|
|
219
|
+
];
|
|
220
|
+
|
|
221
|
+
if (formattedGroup.lastComment.range[0] !== formattedGroup.firstComment.range[0]) {
|
|
222
|
+
fixes.push(
|
|
223
|
+
replaceMinimalComment(
|
|
224
|
+
fixer,
|
|
225
|
+
formattedGroup.lastComment,
|
|
226
|
+
getCommentText(context.sourceCode, formattedGroup.lastComment),
|
|
227
|
+
formattedGroup.lastReplacement,
|
|
228
|
+
),
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return fixes;
|
|
233
|
+
},
|
|
234
|
+
message: "Comment text must be a complete sentence.",
|
|
235
|
+
node: formattedGroup.firstComment,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
for (const comment of context.sourceCode.getAllComments()) {
|
|
241
|
+
if (comment.type !== "Block") {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// The comment's raw source text.
|
|
246
|
+
const commentText = getCommentText(context.sourceCode, comment);
|
|
247
|
+
|
|
248
|
+
// The comment, sentence-formatted using the JSDoc or ordinary-block formatter.
|
|
249
|
+
const formattedComment = isJSDoc(commentText)
|
|
250
|
+
? formatJSDocPunctuation(context.sourceCode, comment)
|
|
251
|
+
: formatOrdinaryBlockComment(context.sourceCode, comment);
|
|
252
|
+
|
|
253
|
+
if (formattedComment !== commentText) {
|
|
254
|
+
context.report({
|
|
255
|
+
/**
|
|
256
|
+
* Apply the sentence-formatted replacement to the comment.
|
|
257
|
+
*
|
|
258
|
+
* @param {object} fixer
|
|
259
|
+
* The Oxlint fixer.
|
|
260
|
+
*
|
|
261
|
+
* @returns {object}
|
|
262
|
+
* The fix to apply.
|
|
263
|
+
*/
|
|
264
|
+
fix: (fixer) => {
|
|
265
|
+
return replaceMinimalComment(fixer, comment, commentText, formattedComment);
|
|
266
|
+
},
|
|
267
|
+
message: "Comment text must be a complete sentence.",
|
|
268
|
+
node: comment,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
},
|
|
275
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { getDocumentationNode } from "../utils/documentation.js";
|
|
2
|
+
import { hasImmediateLineComment } from "../utils/source.js";
|
|
3
|
+
|
|
4
|
+
// Declaration kinds that require an immediately preceding line comment.
|
|
5
|
+
const documentedKinds = new Set(["await using", "const", "let", "using"]);
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Return whether a declaration is inside a loop header.
|
|
9
|
+
*
|
|
10
|
+
* @param {object} node
|
|
11
|
+
* The variable declaration node.
|
|
12
|
+
*
|
|
13
|
+
* @returns {boolean}
|
|
14
|
+
* Whether the declaration is exempt from the rule.
|
|
15
|
+
*/
|
|
16
|
+
function isLoopHeaderDeclaration(node) {
|
|
17
|
+
// Inspects the declaration's parent to identify loop headers.
|
|
18
|
+
const parent = node.parent;
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
(parent.type === "ForStatement" && parent.init === node) ||
|
|
22
|
+
((parent.type === "ForInStatement" || parent.type === "ForOfStatement") && parent.left === node)
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Create the variable-declaration comment rule.
|
|
28
|
+
*
|
|
29
|
+
* @returns {object}
|
|
30
|
+
* The Oxlint rule definition.
|
|
31
|
+
*/
|
|
32
|
+
export default {
|
|
33
|
+
meta: {
|
|
34
|
+
docs: { description: "Require comments before variable declarations." },
|
|
35
|
+
type: "suggestion",
|
|
36
|
+
},
|
|
37
|
+
/**
|
|
38
|
+
* Create the rule's node visitors.
|
|
39
|
+
*
|
|
40
|
+
* @param {object} context
|
|
41
|
+
* The Oxlint rule context.
|
|
42
|
+
*
|
|
43
|
+
* @returns {object}
|
|
44
|
+
* The visitor functions for this rule.
|
|
45
|
+
*/
|
|
46
|
+
createOnce(context) {
|
|
47
|
+
return {
|
|
48
|
+
/**
|
|
49
|
+
* Check a variable declaration for a preceding line comment.
|
|
50
|
+
*
|
|
51
|
+
* @param {object} node
|
|
52
|
+
* The variable declaration node.
|
|
53
|
+
*/
|
|
54
|
+
VariableDeclaration(node) {
|
|
55
|
+
if (!documentedKinds.has(node.kind) || isLoopHeaderDeclaration(node)) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// A lone const class expression is documented by class-documentation.
|
|
60
|
+
if (
|
|
61
|
+
node.kind === "const" &&
|
|
62
|
+
node.declarations.length === 1 &&
|
|
63
|
+
node.declarations[0].id?.type === "Identifier" &&
|
|
64
|
+
node.declarations[0].init?.type === "ClassExpression"
|
|
65
|
+
) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Resolves any export wrapper before checking for documentation.
|
|
70
|
+
const documentationNode = getDocumentationNode(node);
|
|
71
|
+
|
|
72
|
+
if (!hasImmediateLineComment(context.sourceCode, documentationNode)) {
|
|
73
|
+
context.report({
|
|
74
|
+
message: "Variable declarations require an immediately preceding line comment.",
|
|
75
|
+
node: documentationNode,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (node.declarations.length > 1) {
|
|
80
|
+
context.report({
|
|
81
|
+
message: "Declare one variable per declaration statement.",
|
|
82
|
+
node,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
};
|