eslint-plugin-jsdoc 48.5.2 → 48.6.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/src/jsdocUtils.js CHANGED
@@ -1321,7 +1321,7 @@ const parseClosureTemplateTag = (tag) => {
1321
1321
  * @param {{
1322
1322
  * contexts?: import('./iterateJsdoc.js').Context[]
1323
1323
  * }} settings
1324
- * @returns {string[]}
1324
+ * @returns {(string|import('./iterateJsdoc.js').ContextObject)[]}
1325
1325
  */
1326
1326
  const enforcedContexts = (context, defaultContexts, settings) => {
1327
1327
  const contexts = context.options[0]?.contexts || settings.contexts || (defaultContexts === true ? [
@@ -0,0 +1,384 @@
1
+ import iterateJsdoc from '../iterateJsdoc.js';
2
+ import {
3
+ getSettings,
4
+ } from '../iterateJsdoc.js';
5
+ import jsdocUtils from '../jsdocUtils.js';
6
+ import {
7
+ getNonJsdocComment,
8
+ getDecorator,
9
+ getReducedASTNode,
10
+ getFollowingComment,
11
+ } from '@es-joy/jsdoccomment';
12
+
13
+ /** @type {import('eslint').Rule.RuleModule} */
14
+ export default {
15
+ create (context) {
16
+ /**
17
+ * @typedef {import('eslint').AST.Token | import('estree').Comment | {
18
+ * type: import('eslint').AST.TokenType|"Line"|"Block"|"Shebang",
19
+ * range: [number, number],
20
+ * value: string
21
+ * }} Token
22
+ */
23
+
24
+ /**
25
+ * @callback AddComment
26
+ * @param {boolean|undefined} inlineCommentBlock
27
+ * @param {Token} comment
28
+ * @param {string} indent
29
+ * @param {number} lines
30
+ * @param {import('eslint').Rule.RuleFixer} fixer
31
+ */
32
+
33
+ /* c8 ignore next -- Fallback to deprecated method */
34
+ const {
35
+ sourceCode = context.getSourceCode(),
36
+ } = context;
37
+ const settings = getSettings(context);
38
+ if (!settings) {
39
+ return {};
40
+ }
41
+
42
+ const {
43
+ contexts = settings.contexts || [],
44
+ contextsAfter = /** @type {string[]} */ ([]),
45
+ contextsBeforeAndAfter = [
46
+ 'VariableDeclarator', 'TSPropertySignature', 'PropertyDefinition'
47
+ ],
48
+ enableFixer = true,
49
+ enforceJsdocLineStyle = 'multi',
50
+ lineOrBlockStyle = 'both',
51
+ allowedPrefixes = ['@ts-', 'istanbul ', 'c8 ', 'v8 ', 'eslint', 'prettier-']
52
+ } = context.options[0] ?? {};
53
+
54
+ let reportingNonJsdoc = false;
55
+
56
+ /**
57
+ * @param {string} messageId
58
+ * @param {import('estree').Comment|Token} comment
59
+ * @param {import('eslint').Rule.Node} node
60
+ * @param {import('eslint').Rule.ReportFixer} fixer
61
+ */
62
+ const report = (messageId, comment, node, fixer) => {
63
+ const loc = {
64
+ end: {
65
+ column: 0,
66
+ /* c8 ignore next 2 -- Guard */
67
+ // @ts-expect-error Ok
68
+ line: (comment.loc?.start?.line ?? 1),
69
+ },
70
+ start: {
71
+ column: 0,
72
+ /* c8 ignore next 2 -- Guard */
73
+ // @ts-expect-error Ok
74
+ line: (comment.loc?.start?.line ?? 1)
75
+ },
76
+ };
77
+
78
+ context.report({
79
+ fix: enableFixer ? fixer : null,
80
+ loc,
81
+ messageId,
82
+ node,
83
+ });
84
+ };
85
+
86
+ /**
87
+ * @param {import('eslint').Rule.Node} node
88
+ * @param {import('eslint').AST.Token | import('estree').Comment | {
89
+ * type: import('eslint').AST.TokenType|"Line"|"Block"|"Shebang",
90
+ * range: [number, number],
91
+ * value: string
92
+ * }} comment
93
+ * @param {AddComment} addComment
94
+ * @param {import('../iterateJsdoc.js').Context[]} ctxts
95
+ */
96
+ const getFixer = (node, comment, addComment, ctxts) => {
97
+ return /** @type {import('eslint').Rule.ReportFixer} */ (fixer) => {
98
+ // Default to one line break if the `minLines`/`maxLines` settings allow
99
+ const lines = settings.minLines === 0 && settings.maxLines >= 1 ? 1 : settings.minLines;
100
+ let baseNode =
101
+ /**
102
+ * @type {import('@typescript-eslint/types').TSESTree.Node|import('eslint').Rule.Node}
103
+ */ (
104
+ getReducedASTNode(node, sourceCode)
105
+ );
106
+
107
+ const decorator = getDecorator(
108
+ /** @type {import('eslint').Rule.Node} */
109
+ (baseNode)
110
+ );
111
+ if (decorator) {
112
+ baseNode = /** @type {import('@typescript-eslint/types').TSESTree.Decorator} */ (
113
+ decorator
114
+ );
115
+ }
116
+
117
+ const indent = jsdocUtils.getIndent({
118
+ text: sourceCode.getText(
119
+ /** @type {import('eslint').Rule.Node} */ (baseNode),
120
+ /** @type {import('eslint').AST.SourceLocation} */
121
+ (
122
+ /** @type {import('eslint').Rule.Node} */ (baseNode).loc
123
+ ).start.column,
124
+ ),
125
+ });
126
+
127
+ const {
128
+ inlineCommentBlock,
129
+ } =
130
+ /**
131
+ * @type {{
132
+ * context: string,
133
+ * inlineCommentBlock: boolean,
134
+ * minLineCount: import('../iterateJsdoc.js').Integer
135
+ * }[]}
136
+ */ (ctxts).find((contxt) => {
137
+ if (typeof contxt === 'string') {
138
+ return false;
139
+ }
140
+
141
+ const {
142
+ context: ctxt,
143
+ } = contxt;
144
+ return ctxt === node.type;
145
+ }) || {};
146
+
147
+ return addComment(inlineCommentBlock, comment, indent, lines, fixer);
148
+ };
149
+ };
150
+
151
+ /**
152
+ * @param {import('eslint').AST.Token | import('estree').Comment | {
153
+ * type: import('eslint').AST.TokenType|"Line"|"Block"|"Shebang",
154
+ * range: [number, number],
155
+ * value: string
156
+ * }} comment
157
+ * @param {import('eslint').Rule.Node} node
158
+ * @param {AddComment} addComment
159
+ * @param {import('../iterateJsdoc.js').Context[]} ctxts
160
+ */
161
+ const reportings = (comment, node, addComment, ctxts) => {
162
+ const fixer = getFixer(node, comment, addComment, ctxts);
163
+
164
+ if (comment.type === 'Block') {
165
+ if (lineOrBlockStyle === 'line') {
166
+ return;
167
+ }
168
+ report('blockCommentsJsdocStyle', comment, node, fixer);
169
+ return;
170
+ }
171
+
172
+ if (comment.type === 'Line') {
173
+ if (lineOrBlockStyle === 'block') {
174
+ return;
175
+ }
176
+ report('lineCommentsJsdocStyle', comment, node, fixer);
177
+ }
178
+ };
179
+
180
+ /**
181
+ * @type {import('../iterateJsdoc.js').CheckJsdoc}
182
+ */
183
+ const checkNonJsdoc = (_info, _handler, node) => {
184
+ const comment = getNonJsdocComment(sourceCode, node, settings);
185
+
186
+ if (
187
+ !comment ||
188
+ /** @type {string[]} */
189
+ (allowedPrefixes).some((prefix) => {
190
+ return comment.value.trimStart().startsWith(prefix);
191
+ })
192
+ ) {
193
+ return;
194
+ }
195
+
196
+ reportingNonJsdoc = true;
197
+
198
+ /** @type {AddComment} */
199
+ const addComment = (inlineCommentBlock, comment, indent, lines, fixer) => {
200
+ const insertion = (
201
+ inlineCommentBlock || enforceJsdocLineStyle === 'single'
202
+ ? `/** ${comment.value.trim()} `
203
+ : `/**\n${indent}*${comment.value.trimEnd()}\n${indent}`
204
+ ) +
205
+ `*/${'\n'.repeat((lines || 1) - 1)}`;
206
+
207
+ return fixer.replaceText(
208
+ /** @type {import('eslint').AST.Token} */
209
+ (comment),
210
+ insertion,
211
+ );
212
+ };
213
+
214
+ reportings(comment, node, addComment, contexts);
215
+ };
216
+
217
+ /**
218
+ * @param {import('eslint').Rule.Node} node
219
+ * @param {import('../iterateJsdoc.js').Context[]} ctxts
220
+ */
221
+ const checkNonJsdocAfter = (node, ctxts) => {
222
+ const comment = getFollowingComment(sourceCode, node);
223
+
224
+ if (
225
+ !comment ||
226
+ comment.value.startsWith('*') ||
227
+ /** @type {string[]} */
228
+ (allowedPrefixes).some((prefix) => {
229
+ return comment.value.trimStart().startsWith(prefix);
230
+ })
231
+ ) {
232
+ return;
233
+ }
234
+
235
+ /** @type {AddComment} */
236
+ const addComment = (inlineCommentBlock, comment, indent, lines, fixer) => {
237
+ const insertion = (
238
+ inlineCommentBlock || enforceJsdocLineStyle === 'single'
239
+ ? `/** ${comment.value.trim()} `
240
+ : `/**\n${indent}*${comment.value.trimEnd()}\n${indent}`
241
+ ) +
242
+ `*/${'\n'.repeat((lines || 1) - 1)}${lines ? `\n${indent.slice(1)}` : ' '}`;
243
+
244
+ return [fixer.remove(
245
+ /** @type {import('eslint').AST.Token} */
246
+ (comment)
247
+ ), fixer.insertTextBefore(
248
+ node.type === 'VariableDeclarator' ? node.parent : node,
249
+ insertion,
250
+ )];
251
+ };
252
+
253
+ reportings(comment, node, addComment, ctxts);
254
+ };
255
+
256
+ // Todo: add contexts to check after (and handle if want both before and after)
257
+ return {
258
+ ...jsdocUtils.getContextObject(
259
+ jsdocUtils.enforcedContexts(context, true, settings),
260
+ checkNonJsdoc,
261
+ ),
262
+ ...jsdocUtils.getContextObject(
263
+ contextsAfter,
264
+ (_info, _handler, node) => {
265
+ checkNonJsdocAfter(node, contextsAfter);
266
+ },
267
+ ),
268
+ ...jsdocUtils.getContextObject(
269
+ contextsBeforeAndAfter,
270
+ (_info, _handler, node) => {
271
+ checkNonJsdoc({}, null, node);
272
+ if (!reportingNonJsdoc) {
273
+ checkNonJsdocAfter(node, contextsBeforeAndAfter);
274
+ }
275
+ }
276
+ )
277
+ };
278
+ },
279
+ meta: {
280
+ fixable: 'code',
281
+
282
+ messages: {
283
+ blockCommentsJsdocStyle: 'Block comments should be JSDoc-style.',
284
+ lineCommentsJsdocStyle: 'Line comments should be JSDoc-style.',
285
+ },
286
+
287
+ docs: {
288
+ description: 'Converts non-JSDoc comments preceding or following nodes into JSDoc ones',
289
+ url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/convert-to-jsdoc-comments.md#repos-sticky-header',
290
+ },
291
+ schema: [
292
+ {
293
+ additionalProperties: false,
294
+ properties: {
295
+ allowedPrefixes: {
296
+ type: 'array',
297
+ items: {
298
+ type: 'string'
299
+ }
300
+ },
301
+ contexts: {
302
+ items: {
303
+ anyOf: [
304
+ {
305
+ type: 'string',
306
+ },
307
+ {
308
+ additionalProperties: false,
309
+ properties: {
310
+ context: {
311
+ type: 'string',
312
+ },
313
+ inlineCommentBlock: {
314
+ type: 'boolean',
315
+ },
316
+ },
317
+ type: 'object',
318
+ },
319
+ ],
320
+ },
321
+ type: 'array',
322
+ },
323
+ contextsAfter: {
324
+ items: {
325
+ anyOf: [
326
+ {
327
+ type: 'string',
328
+ },
329
+ {
330
+ additionalProperties: false,
331
+ properties: {
332
+ context: {
333
+ type: 'string',
334
+ },
335
+ inlineCommentBlock: {
336
+ type: 'boolean',
337
+ },
338
+ },
339
+ type: 'object',
340
+ },
341
+ ],
342
+ },
343
+ type: 'array',
344
+ },
345
+ contextsBeforeAndAfter: {
346
+ items: {
347
+ anyOf: [
348
+ {
349
+ type: 'string',
350
+ },
351
+ {
352
+ additionalProperties: false,
353
+ properties: {
354
+ context: {
355
+ type: 'string',
356
+ },
357
+ inlineCommentBlock: {
358
+ type: 'boolean',
359
+ },
360
+ },
361
+ type: 'object',
362
+ },
363
+ ],
364
+ },
365
+ type: 'array',
366
+ },
367
+ enableFixer: {
368
+ type: 'boolean'
369
+ },
370
+ enforceJsdocLineStyle: {
371
+ type: 'string',
372
+ enum: ['multi', 'single']
373
+ },
374
+ lineOrBlockStyle: {
375
+ type: 'string',
376
+ enum: ['block', 'line', 'both']
377
+ },
378
+ },
379
+ type: 'object',
380
+ },
381
+ ],
382
+ type: 'suggestion',
383
+ },
384
+ };
@@ -18,6 +18,11 @@ import {
18
18
  * }} RequireJsdocOpts
19
19
  */
20
20
 
21
+ /**
22
+ * @typedef {import('eslint').Rule.Node|
23
+ * import('@typescript-eslint/types').TSESTree.Node} ESLintOrTSNode
24
+ */
25
+
21
26
  /** @type {import('json-schema').JSONSchema4} */
22
27
  const OPTIONS_SCHEMA = {
23
28
  additionalProperties: false,
@@ -411,10 +416,13 @@ export default {
411
416
  const fix = /** @type {import('eslint').Rule.ReportFixer} */ (fixer) => {
412
417
  // Default to one line break if the `minLines`/`maxLines` settings allow
413
418
  const lines = settings.minLines === 0 && settings.maxLines >= 1 ? 1 : settings.minLines;
414
- /** @type {import('eslint').Rule.Node|import('@typescript-eslint/types').TSESTree.Decorator} */
419
+ /** @type {ESLintOrTSNode|import('@typescript-eslint/types').TSESTree.Decorator} */
415
420
  let baseNode = getReducedASTNode(node, sourceCode);
416
421
 
417
- const decorator = getDecorator(baseNode);
422
+ const decorator = getDecorator(
423
+ /** @type {import('eslint').Rule.Node} */
424
+ (baseNode)
425
+ );
418
426
  if (decorator) {
419
427
  baseNode = decorator;
420
428
  }