@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,346 @@
|
|
|
1
|
+
// The token types treated as comments.
|
|
2
|
+
const commentTypes = new Set(["Block", "Line", "Shebang"]);
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Return whether a source item is a comment token.
|
|
6
|
+
*
|
|
7
|
+
* @param {object} item
|
|
8
|
+
* The source item to inspect.
|
|
9
|
+
*
|
|
10
|
+
* @returns {boolean}
|
|
11
|
+
* Whether the item is a comment token.
|
|
12
|
+
*/
|
|
13
|
+
export function isComment(item) {
|
|
14
|
+
return commentTypes.has(item.type);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Return the source text for a comment token.
|
|
19
|
+
*
|
|
20
|
+
* @param {object} sourceCode
|
|
21
|
+
* The Oxlint source code object.
|
|
22
|
+
* @param {object} comment
|
|
23
|
+
* The comment token.
|
|
24
|
+
*
|
|
25
|
+
* @returns {string}
|
|
26
|
+
* The comment's source text.
|
|
27
|
+
*/
|
|
28
|
+
export function getCommentText(sourceCode, comment) {
|
|
29
|
+
return sourceCode.text.slice(comment.range[0], comment.range[1]);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Return the smallest replacement that changes one comment to formatted text.
|
|
34
|
+
*
|
|
35
|
+
* @param {object} comment
|
|
36
|
+
* The comment token.
|
|
37
|
+
* @param {string} commentText
|
|
38
|
+
* The comment's source text.
|
|
39
|
+
* @param {string} formattedComment
|
|
40
|
+
* The formatted replacement text.
|
|
41
|
+
*
|
|
42
|
+
* @returns {object}
|
|
43
|
+
* The source range and replacement text.
|
|
44
|
+
*/
|
|
45
|
+
export function getMinimalCommentReplacement(comment, commentText, formattedComment) {
|
|
46
|
+
// The count of unchanged characters shared at the start of both texts.
|
|
47
|
+
let start = 0;
|
|
48
|
+
|
|
49
|
+
while (
|
|
50
|
+
start < commentText.length &&
|
|
51
|
+
start < formattedComment.length &&
|
|
52
|
+
commentText[start] === formattedComment[start]
|
|
53
|
+
) {
|
|
54
|
+
start += 1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// The count of unchanged characters shared at the end of both texts.
|
|
58
|
+
let end = 0;
|
|
59
|
+
|
|
60
|
+
while (
|
|
61
|
+
end < commentText.length - start &&
|
|
62
|
+
end < formattedComment.length - start &&
|
|
63
|
+
commentText.at(-end - 1) === formattedComment.at(-end - 1)
|
|
64
|
+
) {
|
|
65
|
+
end += 1;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
range: [comment.range[0] + start, comment.range[1] - end],
|
|
70
|
+
text: formattedComment.slice(start, formattedComment.length - end),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Replace a comment using the smallest changed source range.
|
|
76
|
+
*
|
|
77
|
+
* @param {object} fixer
|
|
78
|
+
* The Oxlint fixer.
|
|
79
|
+
* @param {object} comment
|
|
80
|
+
* The comment token.
|
|
81
|
+
* @param {string} sourceText
|
|
82
|
+
* The comment's source text.
|
|
83
|
+
* @param {string} formattedText
|
|
84
|
+
* The formatted replacement text.
|
|
85
|
+
*
|
|
86
|
+
* @returns {object}
|
|
87
|
+
* The fixer replacement.
|
|
88
|
+
*/
|
|
89
|
+
export function replaceMinimalComment(fixer, comment, sourceText, formattedText) {
|
|
90
|
+
// The smallest source range and text that changes the comment.
|
|
91
|
+
const replacement = getMinimalCommentReplacement(comment, sourceText, formattedText);
|
|
92
|
+
|
|
93
|
+
return fixer.replaceTextRange(replacement.range, replacement.text);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Return whether a comment is an inline tool directive.
|
|
98
|
+
*
|
|
99
|
+
* @param {object} comment
|
|
100
|
+
* The comment token.
|
|
101
|
+
*
|
|
102
|
+
* @returns {boolean}
|
|
103
|
+
* Whether the comment starts with a recognised directive prefix.
|
|
104
|
+
*/
|
|
105
|
+
export function isDirectiveComment(comment) {
|
|
106
|
+
return /^(?:eslint|oxlint|istanbul|c8)-/.test(comment.value.trim());
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Return the source line containing an offset.
|
|
111
|
+
*
|
|
112
|
+
* @param {object} sourceCode
|
|
113
|
+
* The Oxlint source code object.
|
|
114
|
+
* @param {number} offset
|
|
115
|
+
* The source offset.
|
|
116
|
+
*
|
|
117
|
+
* @returns {number}
|
|
118
|
+
* The one-based source line.
|
|
119
|
+
*/
|
|
120
|
+
export function getLineNumber(sourceCode, offset) {
|
|
121
|
+
return sourceCode.getLocFromIndex(offset).line;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Return the offset at which an offset's source line starts.
|
|
126
|
+
*
|
|
127
|
+
* @param {object} sourceCode
|
|
128
|
+
* The Oxlint source code object.
|
|
129
|
+
* @param {number} offset
|
|
130
|
+
* The source offset.
|
|
131
|
+
*
|
|
132
|
+
* @returns {number}
|
|
133
|
+
* The line-start offset.
|
|
134
|
+
*/
|
|
135
|
+
export function getLineStart(sourceCode, offset) {
|
|
136
|
+
return sourceCode.lineStartIndices[getLineNumber(sourceCode, offset) - 1];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Return the whitespace before a source offset on its line.
|
|
141
|
+
*
|
|
142
|
+
* @param {object} sourceCode
|
|
143
|
+
* The Oxlint source code object.
|
|
144
|
+
* @param {number} offset
|
|
145
|
+
* The source offset.
|
|
146
|
+
*
|
|
147
|
+
* @returns {string|null}
|
|
148
|
+
* The line indentation, or null when code precedes the offset.
|
|
149
|
+
*/
|
|
150
|
+
export function getLineIndent(sourceCode, offset) {
|
|
151
|
+
// The offset at which the offset's line begins.
|
|
152
|
+
const lineStart = getLineStart(sourceCode, offset);
|
|
153
|
+
// The source text between the line start and the offset.
|
|
154
|
+
const prefix = sourceCode.text.slice(lineStart, offset);
|
|
155
|
+
|
|
156
|
+
return /^\s*$/.test(prefix) ? prefix : null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Return the source items immediately around a comment.
|
|
161
|
+
*
|
|
162
|
+
* @param {object} sourceCode
|
|
163
|
+
* The Oxlint source code object.
|
|
164
|
+
* @param {object} comment
|
|
165
|
+
* The comment token.
|
|
166
|
+
*
|
|
167
|
+
* @returns {object}
|
|
168
|
+
* The previous and next non-comment source items.
|
|
169
|
+
*/
|
|
170
|
+
export function getCommentNeighbours(sourceCode, comment) {
|
|
171
|
+
// Every token and comment in the file, in source order.
|
|
172
|
+
const sourceItems = sourceCode.tokensAndComments;
|
|
173
|
+
|
|
174
|
+
// The comment's position within sourceItems.
|
|
175
|
+
const commentIndex = sourceItems.findIndex(
|
|
176
|
+
(item) => item.range[0] === comment.range[0] && item.range[1] === comment.range[1],
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// The preceding non-comment source item, once found.
|
|
180
|
+
let previous = null;
|
|
181
|
+
// The following non-comment source item, once found.
|
|
182
|
+
let next = null;
|
|
183
|
+
|
|
184
|
+
for (let index = commentIndex - 1; index >= 0; index -= 1) {
|
|
185
|
+
if (!isComment(sourceItems[index])) {
|
|
186
|
+
previous = sourceItems[index];
|
|
187
|
+
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
for (let index = commentIndex + 1; index < sourceItems.length; index += 1) {
|
|
193
|
+
if (!isComment(sourceItems[index])) {
|
|
194
|
+
next = sourceItems[index];
|
|
195
|
+
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return { next, previous };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Return the comments that form one adjacent line-comment group.
|
|
205
|
+
*
|
|
206
|
+
* @param {object} sourceCode
|
|
207
|
+
* The Oxlint source code object.
|
|
208
|
+
*
|
|
209
|
+
* @returns {object[][]}
|
|
210
|
+
* Adjacent line-comment groups.
|
|
211
|
+
*/
|
|
212
|
+
export function getLineCommentGroups(sourceCode) {
|
|
213
|
+
// Every line comment in the file, in source order.
|
|
214
|
+
const comments = sourceCode.getAllComments().filter((comment) => comment.type === "Line");
|
|
215
|
+
// The adjacent comment groups, built up in place.
|
|
216
|
+
const groups = [];
|
|
217
|
+
|
|
218
|
+
for (const comment of comments) {
|
|
219
|
+
if (isDirectiveComment(comment)) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// The group currently being built, when there is one.
|
|
224
|
+
const group = groups.at(-1);
|
|
225
|
+
// The current group's last comment, when there is one.
|
|
226
|
+
const previousComment = group?.at(-1);
|
|
227
|
+
|
|
228
|
+
// The source text between the previous comment and this one.
|
|
229
|
+
const gap = previousComment
|
|
230
|
+
? sourceCode.text.slice(previousComment.range[1], comment.range[0])
|
|
231
|
+
: "";
|
|
232
|
+
|
|
233
|
+
// How many newlines separate this comment from the previous one.
|
|
234
|
+
const newlineCount = gap.match(/\r\n|\n|\r/g)?.length ?? 0;
|
|
235
|
+
|
|
236
|
+
if (group && newlineCount === 1 && /^[ \t]*\r?\n[ \t]*$/.test(gap)) {
|
|
237
|
+
group.push(comment);
|
|
238
|
+
} else {
|
|
239
|
+
groups.push([comment]);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return groups;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Return the newline sequence used by a source file.
|
|
248
|
+
*
|
|
249
|
+
* @param {string} source
|
|
250
|
+
* The source text.
|
|
251
|
+
*
|
|
252
|
+
* @returns {string}
|
|
253
|
+
* The source newline sequence.
|
|
254
|
+
*/
|
|
255
|
+
export function getNewline(source) {
|
|
256
|
+
return source.includes("\r\n") ? "\r\n" : "\n";
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Return whether a comment is a leading comment for the next source token.
|
|
261
|
+
*
|
|
262
|
+
* @param {object} sourceCode
|
|
263
|
+
* The Oxlint source code object.
|
|
264
|
+
* @param {object} comment
|
|
265
|
+
* The comment token.
|
|
266
|
+
* @param {object|null} previous
|
|
267
|
+
* The preceding non-comment source item.
|
|
268
|
+
*
|
|
269
|
+
* @returns {boolean}
|
|
270
|
+
* Whether the comment is on a line of its own before code.
|
|
271
|
+
*/
|
|
272
|
+
export function isLeadingComment(sourceCode, comment, previous) {
|
|
273
|
+
if (getLineIndent(sourceCode, comment.range[0]) === null) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return previous === null || comment.loc.start.line > previous.loc.end.line;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Return whether a line comment immediately documents a source node.
|
|
282
|
+
*
|
|
283
|
+
* @param {object} sourceCode
|
|
284
|
+
* The Oxlint source code object.
|
|
285
|
+
* @param {object} node
|
|
286
|
+
* The source node.
|
|
287
|
+
*
|
|
288
|
+
* @returns {boolean}
|
|
289
|
+
* Whether an ordinary line comment immediately precedes the node.
|
|
290
|
+
*/
|
|
291
|
+
export function hasImmediateLineComment(sourceCode, node) {
|
|
292
|
+
// Finds the closest preceding comment.
|
|
293
|
+
const comment = sourceCode
|
|
294
|
+
.getAllComments()
|
|
295
|
+
.findLast((candidate) => candidate.range[1] <= node.range[0]);
|
|
296
|
+
|
|
297
|
+
if (comment?.type !== "Line" || isDirectiveComment(comment)) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Checks the comments immediately around the node.
|
|
302
|
+
const { next, previous } = getCommentNeighbours(sourceCode, comment);
|
|
303
|
+
|
|
304
|
+
// Confirms there is no blank line before the node.
|
|
305
|
+
const gap = sourceCode.text.slice(comment.range[1], node.range[0]);
|
|
306
|
+
|
|
307
|
+
return (
|
|
308
|
+
next?.range[0] === node.range[0] &&
|
|
309
|
+
isLeadingComment(sourceCode, comment, previous) &&
|
|
310
|
+
/^\r?\n[ \t]*$/.test(gap)
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Return whether a block comment immediately documents a source node.
|
|
316
|
+
*
|
|
317
|
+
* @param {object} sourceCode
|
|
318
|
+
* The Oxlint source code object.
|
|
319
|
+
* @param {object} node
|
|
320
|
+
* The source node.
|
|
321
|
+
*
|
|
322
|
+
* @returns {boolean}
|
|
323
|
+
* Whether an ordinary block comment immediately precedes the node.
|
|
324
|
+
*/
|
|
325
|
+
export function hasImmediateBlockComment(sourceCode, node) {
|
|
326
|
+
// Finds the closest preceding comment.
|
|
327
|
+
const comment = sourceCode
|
|
328
|
+
.getAllComments()
|
|
329
|
+
.findLast((candidate) => candidate.range[1] <= node.range[0]);
|
|
330
|
+
|
|
331
|
+
if (comment?.type !== "Block" || isDirectiveComment(comment)) {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Checks the comments immediately around the node.
|
|
336
|
+
const { next, previous } = getCommentNeighbours(sourceCode, comment);
|
|
337
|
+
|
|
338
|
+
// Confirms there is no blank line before the node.
|
|
339
|
+
const gap = sourceCode.text.slice(comment.range[1], node.range[0]);
|
|
340
|
+
|
|
341
|
+
return (
|
|
342
|
+
next?.range[0] === node.range[0] &&
|
|
343
|
+
isLeadingComment(sourceCode, comment, previous) &&
|
|
344
|
+
/^\r?\n[ \t]*$/.test(gap)
|
|
345
|
+
);
|
|
346
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return whether a node is a call expression invoking the given function name.
|
|
3
|
+
*
|
|
4
|
+
* @param {object} node
|
|
5
|
+
* The node to inspect.
|
|
6
|
+
* @param {string} name
|
|
7
|
+
* The function name to match.
|
|
8
|
+
*
|
|
9
|
+
* @returns {boolean}
|
|
10
|
+
* Whether the node matches.
|
|
11
|
+
*/
|
|
12
|
+
export function isNamedCall(node, name) {
|
|
13
|
+
return (
|
|
14
|
+
node?.type === "CallExpression" &&
|
|
15
|
+
node.callee?.type === "Identifier" &&
|
|
16
|
+
node.callee.name === name
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Return an object argument from a call, when one is present.
|
|
22
|
+
*
|
|
23
|
+
* @param {object} callNode
|
|
24
|
+
* The call whose argument should be inspected.
|
|
25
|
+
* @param {number} argumentIndex
|
|
26
|
+
* The argument position to inspect.
|
|
27
|
+
*
|
|
28
|
+
* @returns {object|null}
|
|
29
|
+
* The object argument, or null when it is missing or not an object.
|
|
30
|
+
*/
|
|
31
|
+
export function getObjectArgument(callNode, argumentIndex) {
|
|
32
|
+
// The candidate object argument for runtime declarations.
|
|
33
|
+
const argument = callNode.arguments[argumentIndex];
|
|
34
|
+
|
|
35
|
+
return argument?.type === "ObjectExpression" ? argument : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Return the object properties that represent runtime declarations.
|
|
40
|
+
*
|
|
41
|
+
* @param {object} objectExpression
|
|
42
|
+
* The object expression to inspect.
|
|
43
|
+
*
|
|
44
|
+
* @returns {object[]}
|
|
45
|
+
* Its ordinary properties, excluding spread elements.
|
|
46
|
+
*/
|
|
47
|
+
export function getObjectProperties(objectExpression) {
|
|
48
|
+
return objectExpression.properties.filter((property) => property.type === "Property");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Return the name of an object property.
|
|
53
|
+
*
|
|
54
|
+
* @param {object} property
|
|
55
|
+
* The property to inspect.
|
|
56
|
+
*
|
|
57
|
+
* @returns {string|null}
|
|
58
|
+
* The property name, when it is a string or number.
|
|
59
|
+
*/
|
|
60
|
+
export function getPropertyName(property) {
|
|
61
|
+
if (property.key.type === "Identifier") {
|
|
62
|
+
return property.key.name;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (typeof property.key.value === "string" || typeof property.key.value === "number") {
|
|
66
|
+
return String(property.key.value);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrap words to a maximum line width.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} text
|
|
5
|
+
* The text to wrap.
|
|
6
|
+
* @param {number} width
|
|
7
|
+
* The maximum output width.
|
|
8
|
+
*
|
|
9
|
+
* @returns {string[]}
|
|
10
|
+
* Wrapped lines.
|
|
11
|
+
*/
|
|
12
|
+
export function wrapWords(text, width) {
|
|
13
|
+
// The text's individual words.
|
|
14
|
+
const words = text.trim().split(/\s+/).filter(Boolean);
|
|
15
|
+
// The wrapped lines, built up in place.
|
|
16
|
+
const lines = [];
|
|
17
|
+
|
|
18
|
+
// The line currently being filled.
|
|
19
|
+
let currentLine = "";
|
|
20
|
+
|
|
21
|
+
for (const word of words) {
|
|
22
|
+
if (word.length > width && currentLine === "") {
|
|
23
|
+
for (let index = 0; index < word.length; index += width) {
|
|
24
|
+
lines.push(word.slice(index, index + width));
|
|
25
|
+
}
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (currentLine === "") {
|
|
30
|
+
currentLine = word;
|
|
31
|
+
} else if (currentLine.length + word.length + 1 <= width) {
|
|
32
|
+
currentLine += ` ${word}`;
|
|
33
|
+
} else {
|
|
34
|
+
lines.push(currentLine);
|
|
35
|
+
currentLine = word;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (currentLine !== "") {
|
|
40
|
+
lines.push(currentLine);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return lines;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Capitalise a sentence and ensure it has terminal punctuation.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} text
|
|
50
|
+
* The sentence text.
|
|
51
|
+
*
|
|
52
|
+
* @returns {string}
|
|
53
|
+
* The corrected sentence text.
|
|
54
|
+
*/
|
|
55
|
+
export function formatSentence(text) {
|
|
56
|
+
return addTerminalPunctuation(capitaliseSentence(text));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Capitalise the first letter of sentence text.
|
|
61
|
+
*
|
|
62
|
+
* @param {string} text
|
|
63
|
+
* The sentence text.
|
|
64
|
+
*
|
|
65
|
+
* @returns {string}
|
|
66
|
+
* The capitalised sentence text.
|
|
67
|
+
*/
|
|
68
|
+
export function capitaliseSentence(text) {
|
|
69
|
+
// The sentence text, without leading or trailing whitespace.
|
|
70
|
+
const trimmedText = text.trim();
|
|
71
|
+
|
|
72
|
+
if (trimmedText === "" || trimmedText.startsWith("@")) {
|
|
73
|
+
return text;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// The index of the first letter character, ignoring leading punctuation.
|
|
77
|
+
const firstLetter = trimmedText.search(/\p{L}/u);
|
|
78
|
+
|
|
79
|
+
if (firstLetter < 0) {
|
|
80
|
+
return text;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// The leading word, starting from the first letter character.
|
|
84
|
+
const leadingWord = trimmedText.slice(firstLetter).match(/^\p{L}[\p{L}\p{N}]*/u)?.[0] ?? "";
|
|
85
|
+
|
|
86
|
+
// A camelCase word (lowercase start, later uppercase) is a code
|
|
87
|
+
// identifier and must keep its own casing rather than sentence casing.
|
|
88
|
+
if (/^\p{Ll}[\p{Ll}\p{N}]*\p{Lu}/u.test(leadingWord)) {
|
|
89
|
+
return text;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// The first letter character.
|
|
93
|
+
const letter = trimmedText[firstLetter];
|
|
94
|
+
// The text with its first letter capitalised.
|
|
95
|
+
const formattedText = `${trimmedText.slice(0, firstLetter)}${letter.toLocaleUpperCase()}${trimmedText.slice(firstLetter + 1)}`;
|
|
96
|
+
|
|
97
|
+
return text.replace(trimmedText, formattedText);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Add terminal punctuation to sentence text.
|
|
102
|
+
*
|
|
103
|
+
* @param {string} text
|
|
104
|
+
* The sentence text.
|
|
105
|
+
*
|
|
106
|
+
* @returns {string}
|
|
107
|
+
* The punctuated sentence text.
|
|
108
|
+
*/
|
|
109
|
+
export function addTerminalPunctuation(text) {
|
|
110
|
+
// The sentence text, without leading or trailing whitespace.
|
|
111
|
+
const trimmedText = text.trim();
|
|
112
|
+
|
|
113
|
+
if (trimmedText === "" || trimmedText.startsWith("@") || /[.!?]$/.test(trimmedText)) {
|
|
114
|
+
return text;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return text.replace(trimmedText, `${trimmedText}.`);
|
|
118
|
+
}
|
package/comments.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"jsPlugins": [
|
|
3
|
+
{
|
|
4
|
+
"name": "comments",
|
|
5
|
+
"specifier": "@lewishowles/lint-config/comments/plugin"
|
|
6
|
+
}
|
|
7
|
+
],
|
|
8
|
+
"rules": {
|
|
9
|
+
"comments/block-comments": "error",
|
|
10
|
+
"comments/class-documentation": "error",
|
|
11
|
+
"comments/configured-api-calls": "error",
|
|
12
|
+
"comments/function-documentation": "error",
|
|
13
|
+
"comments/jsdoc-tag-formatting": "error",
|
|
14
|
+
"comments/line-comments": "error",
|
|
15
|
+
"comments/max-line-length": "error",
|
|
16
|
+
"comments/placement": "error",
|
|
17
|
+
"comments/sentence-punctuation": "error",
|
|
18
|
+
"comments/variable-declarations": "error",
|
|
19
|
+
"comments/vue-component-documentation": "error",
|
|
20
|
+
"comments/vue-emit-documentation": "error",
|
|
21
|
+
"comments/vue-prop-documentation": "error"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lewishowles/lint-config",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Shared oxlint configuration for Lewis Howles projects",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"config",
|
|
@@ -19,27 +19,36 @@
|
|
|
19
19
|
"url": "git+https://github.com/lewishowles/lint-config.git"
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
|
+
"CHANGELOG.md",
|
|
22
23
|
"base.json",
|
|
24
|
+
"comments",
|
|
25
|
+
"comments.json",
|
|
23
26
|
"vue.json",
|
|
24
27
|
"README.md"
|
|
25
28
|
],
|
|
26
29
|
"type": "module",
|
|
27
30
|
"exports": {
|
|
28
31
|
"./base.json": "./base.json",
|
|
32
|
+
"./comments.json": "./comments.json",
|
|
33
|
+
"./comments/plugin": "./comments/plugin.js",
|
|
29
34
|
"./vue.json": "./vue.json"
|
|
30
35
|
},
|
|
31
36
|
"publishConfig": {
|
|
32
37
|
"access": "public"
|
|
33
38
|
},
|
|
34
39
|
"scripts": {
|
|
40
|
+
"lint": "vp check",
|
|
41
|
+
"lint:fix": "vp check --fix",
|
|
42
|
+
"prepare": "vp config --no-agent",
|
|
35
43
|
"publint": "publint"
|
|
36
44
|
},
|
|
37
45
|
"devDependencies": {
|
|
38
|
-
"publint": "^0.3.
|
|
46
|
+
"publint": "^0.3.22",
|
|
47
|
+
"vite-plus": "0.2.8"
|
|
39
48
|
},
|
|
40
49
|
"peerDependencies": {
|
|
41
50
|
"@stylistic/eslint-plugin": "*",
|
|
42
|
-
"vite-plus": "
|
|
51
|
+
"vite-plus": "0.2.8"
|
|
43
52
|
},
|
|
44
53
|
"engines": {
|
|
45
54
|
"node": ">=20"
|