eslint-plugin-jsdoc 48.0.0 → 48.0.1

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.
Files changed (64) hide show
  1. package/package.json +1 -1
  2. package/src/WarnSettings.js +34 -0
  3. package/src/alignTransform.js +356 -0
  4. package/src/defaultTagOrder.js +168 -0
  5. package/src/exportParser.js +957 -0
  6. package/src/getDefaultTagStructureForMode.js +969 -0
  7. package/src/index.js +266 -0
  8. package/src/iterateJsdoc.js +2555 -0
  9. package/src/jsdocUtils.js +1693 -0
  10. package/src/rules/checkAccess.js +45 -0
  11. package/src/rules/checkAlignment.js +63 -0
  12. package/src/rules/checkExamples.js +594 -0
  13. package/src/rules/checkIndentation.js +75 -0
  14. package/src/rules/checkLineAlignment.js +364 -0
  15. package/src/rules/checkParamNames.js +404 -0
  16. package/src/rules/checkPropertyNames.js +152 -0
  17. package/src/rules/checkSyntax.js +30 -0
  18. package/src/rules/checkTagNames.js +314 -0
  19. package/src/rules/checkTypes.js +535 -0
  20. package/src/rules/checkValues.js +220 -0
  21. package/src/rules/emptyTags.js +88 -0
  22. package/src/rules/implementsOnClasses.js +64 -0
  23. package/src/rules/importsAsDependencies.js +131 -0
  24. package/src/rules/informativeDocs.js +182 -0
  25. package/src/rules/matchDescription.js +286 -0
  26. package/src/rules/matchName.js +147 -0
  27. package/src/rules/multilineBlocks.js +333 -0
  28. package/src/rules/noBadBlocks.js +109 -0
  29. package/src/rules/noBlankBlockDescriptions.js +69 -0
  30. package/src/rules/noBlankBlocks.js +53 -0
  31. package/src/rules/noDefaults.js +85 -0
  32. package/src/rules/noMissingSyntax.js +195 -0
  33. package/src/rules/noMultiAsterisks.js +134 -0
  34. package/src/rules/noRestrictedSyntax.js +91 -0
  35. package/src/rules/noTypes.js +73 -0
  36. package/src/rules/noUndefinedTypes.js +328 -0
  37. package/src/rules/requireAsteriskPrefix.js +189 -0
  38. package/src/rules/requireDescription.js +161 -0
  39. package/src/rules/requireDescriptionCompleteSentence.js +333 -0
  40. package/src/rules/requireExample.js +118 -0
  41. package/src/rules/requireFileOverview.js +154 -0
  42. package/src/rules/requireHyphenBeforeParamDescription.js +178 -0
  43. package/src/rules/requireJsdoc.js +629 -0
  44. package/src/rules/requireParam.js +592 -0
  45. package/src/rules/requireParamDescription.js +89 -0
  46. package/src/rules/requireParamName.js +55 -0
  47. package/src/rules/requireParamType.js +89 -0
  48. package/src/rules/requireProperty.js +48 -0
  49. package/src/rules/requirePropertyDescription.js +25 -0
  50. package/src/rules/requirePropertyName.js +25 -0
  51. package/src/rules/requirePropertyType.js +25 -0
  52. package/src/rules/requireReturns.js +238 -0
  53. package/src/rules/requireReturnsCheck.js +141 -0
  54. package/src/rules/requireReturnsDescription.js +59 -0
  55. package/src/rules/requireReturnsType.js +51 -0
  56. package/src/rules/requireThrows.js +111 -0
  57. package/src/rules/requireYields.js +216 -0
  58. package/src/rules/requireYieldsCheck.js +208 -0
  59. package/src/rules/sortTags.js +557 -0
  60. package/src/rules/tagLines.js +359 -0
  61. package/src/rules/textEscaping.js +146 -0
  62. package/src/rules/validTypes.js +368 -0
  63. package/src/tagNames.js +234 -0
  64. package/src/utils/hasReturnValue.js +549 -0
@@ -0,0 +1,359 @@
1
+ import iterateJsdoc from '../iterateJsdoc.js';
2
+
3
+ export default iterateJsdoc(({
4
+ context,
5
+ jsdoc,
6
+ utils,
7
+ }) => {
8
+ const [
9
+ alwaysNever = 'never',
10
+ {
11
+ count = 1,
12
+ endLines = 0,
13
+ startLines = 0,
14
+ applyToEndTag = true,
15
+ tags = {},
16
+ } = {},
17
+ ] = context.options;
18
+
19
+ // eslint-disable-next-line complexity -- Temporary
20
+ jsdoc.tags.some((tg, tagIdx) => {
21
+ let lastTag;
22
+
23
+ /**
24
+ * @type {null|import('../iterateJsdoc.js').Integer}
25
+ */
26
+ let lastEmpty = null;
27
+
28
+ /**
29
+ * @type {null|import('../iterateJsdoc.js').Integer}
30
+ */
31
+ let reportIndex = null;
32
+ let emptyLinesCount = 0;
33
+ for (const [
34
+ idx,
35
+ {
36
+ tokens: {
37
+ tag,
38
+ name,
39
+ type,
40
+ description,
41
+ end,
42
+ },
43
+ },
44
+ ] of tg.source.entries()) {
45
+ // May be text after a line break within a tag description
46
+ if (description) {
47
+ reportIndex = null;
48
+ }
49
+
50
+ if (lastTag && [
51
+ 'any', 'always',
52
+ ].includes(tags[lastTag.slice(1)]?.lines)) {
53
+ continue;
54
+ }
55
+
56
+ const empty = !tag && !name && !type && !description;
57
+ if (
58
+ empty && !end &&
59
+ (alwaysNever === 'never' ||
60
+ lastTag && tags[lastTag.slice(1)]?.lines === 'never'
61
+ )
62
+ ) {
63
+ reportIndex = idx;
64
+
65
+ continue;
66
+ }
67
+
68
+ if (!end) {
69
+ if (empty) {
70
+ emptyLinesCount++;
71
+ } else {
72
+ emptyLinesCount = 0;
73
+ }
74
+
75
+ lastEmpty = empty ? idx : null;
76
+ }
77
+
78
+ lastTag = tag;
79
+ }
80
+
81
+ if (
82
+ typeof endLines === 'number' &&
83
+ lastEmpty !== null && tagIdx === jsdoc.tags.length - 1
84
+ ) {
85
+ const lineDiff = endLines - emptyLinesCount;
86
+
87
+ if (lineDiff < 0) {
88
+ const fixer = () => {
89
+ utils.removeTag(tagIdx, {
90
+ tagSourceOffset: /** @type {import('../iterateJsdoc.js').Integer} */ (
91
+ lastEmpty
92
+ ) + lineDiff + 1,
93
+ });
94
+ };
95
+
96
+ utils.reportJSDoc(
97
+ `Expected ${endLines} trailing lines`,
98
+ {
99
+ line: tg.source[lastEmpty].number + lineDiff + 1,
100
+ },
101
+ fixer,
102
+ );
103
+ } else if (lineDiff > 0) {
104
+ const fixer = () => {
105
+ utils.addLines(
106
+ tagIdx,
107
+ /** @type {import('../iterateJsdoc.js').Integer} */ (lastEmpty),
108
+ endLines - emptyLinesCount,
109
+ );
110
+ };
111
+
112
+ utils.reportJSDoc(
113
+ `Expected ${endLines} trailing lines`,
114
+ {
115
+ line: tg.source[lastEmpty].number,
116
+ },
117
+ fixer,
118
+ );
119
+ }
120
+
121
+ return true;
122
+ }
123
+
124
+ if (reportIndex !== null) {
125
+ const fixer = () => {
126
+ utils.removeTag(tagIdx, {
127
+ tagSourceOffset: /** @type {import('../iterateJsdoc.js').Integer} */ (
128
+ reportIndex
129
+ ),
130
+ });
131
+ };
132
+
133
+ utils.reportJSDoc(
134
+ 'Expected no lines between tags',
135
+ {
136
+ line: tg.source[0].number + 1,
137
+ },
138
+ fixer,
139
+ );
140
+
141
+ return true;
142
+ }
143
+
144
+ return false;
145
+ });
146
+
147
+ (applyToEndTag ? jsdoc.tags : jsdoc.tags.slice(0, -1)).some((tg, tagIdx) => {
148
+ /**
149
+ * @type {{
150
+ * idx: import('../iterateJsdoc.js').Integer,
151
+ * number: import('../iterateJsdoc.js').Integer
152
+ * }[]}
153
+ */
154
+ const lines = [];
155
+
156
+ let currentTag;
157
+ let tagSourceIdx = 0;
158
+ for (const [
159
+ idx,
160
+ {
161
+ number,
162
+ tokens: {
163
+ tag,
164
+ name,
165
+ type,
166
+ description,
167
+ end,
168
+ },
169
+ },
170
+ ] of tg.source.entries()) {
171
+ if (description) {
172
+ lines.splice(0, lines.length);
173
+ tagSourceIdx = idx;
174
+ }
175
+
176
+ if (tag) {
177
+ currentTag = tag;
178
+ }
179
+
180
+ if (!tag && !name && !type && !description && !end) {
181
+ lines.push({
182
+ idx,
183
+ number,
184
+ });
185
+ }
186
+ }
187
+
188
+ const currentTg = currentTag && tags[currentTag.slice(1)];
189
+ const tagCount = currentTg?.count;
190
+
191
+ const defaultAlways = alwaysNever === 'always' && currentTg?.lines !== 'never' &&
192
+ currentTg?.lines !== 'any' && lines.length < count;
193
+
194
+ let overrideAlways;
195
+ let fixCount = count;
196
+ if (!defaultAlways) {
197
+ fixCount = typeof tagCount === 'number' ? tagCount : count;
198
+ overrideAlways = currentTg?.lines === 'always' &&
199
+ lines.length < fixCount;
200
+ }
201
+
202
+ if (defaultAlways || overrideAlways) {
203
+ const fixer = () => {
204
+ utils.addLines(tagIdx, lines[lines.length - 1]?.idx || tagSourceIdx + 1, fixCount - lines.length);
205
+ };
206
+
207
+ const line = lines[lines.length - 1]?.number || tg.source[tagSourceIdx].number;
208
+ utils.reportJSDoc(
209
+ `Expected ${fixCount} line${fixCount === 1 ? '' : 's'} between tags but found ${lines.length}`,
210
+ {
211
+ line,
212
+ },
213
+ fixer,
214
+ );
215
+
216
+ return true;
217
+ }
218
+
219
+ return false;
220
+ });
221
+
222
+ if (typeof startLines === 'number') {
223
+ if (!jsdoc.tags.length) {
224
+ return;
225
+ }
226
+
227
+ const {
228
+ description,
229
+ lastDescriptionLine,
230
+ } = utils.getDescription();
231
+ if (!(/\S/u).test(description)) {
232
+ return;
233
+ }
234
+
235
+ const trailingLines = description.match(/\n+$/u)?.[0]?.length;
236
+ const trailingDiff = (trailingLines ?? 0) - startLines;
237
+ if (trailingDiff > 0) {
238
+ utils.reportJSDoc(
239
+ `Expected only ${startLines} line after block description`,
240
+ {
241
+ line: lastDescriptionLine - trailingDiff,
242
+ },
243
+ () => {
244
+ utils.setBlockDescription((info, seedTokens, descLines) => {
245
+ return descLines.slice(0, -trailingDiff).map((desc) => {
246
+ return {
247
+ number: 0,
248
+ source: '',
249
+ tokens: seedTokens({
250
+ ...info,
251
+ description: desc,
252
+ postDelimiter: desc.trim() ? info.postDelimiter : '',
253
+ }),
254
+ };
255
+ });
256
+ });
257
+ },
258
+ );
259
+ } else if (trailingDiff < 0) {
260
+ utils.reportJSDoc(
261
+ `Expected ${startLines} lines after block description`,
262
+ {
263
+ line: lastDescriptionLine,
264
+ },
265
+ () => {
266
+ utils.setBlockDescription((info, seedTokens, descLines) => {
267
+ return [
268
+ ...descLines,
269
+ ...Array.from({
270
+ length: -trailingDiff,
271
+ }, () => {
272
+ return '';
273
+ }),
274
+ ].map((desc) => {
275
+ return {
276
+ number: 0,
277
+ source: '',
278
+ tokens: seedTokens({
279
+ ...info,
280
+ description: desc,
281
+ postDelimiter: desc.trim() ? info.postDelimiter : '',
282
+ }),
283
+ };
284
+ });
285
+ });
286
+ },
287
+ );
288
+ }
289
+ }
290
+ }, {
291
+ iterateAllJsdocs: true,
292
+ meta: {
293
+ docs: {
294
+ description: 'Enforces lines (or no lines) between tags.',
295
+ url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/tag-lines.md#repos-sticky-header',
296
+ },
297
+ fixable: 'code',
298
+ schema: [
299
+ {
300
+ enum: [
301
+ 'always', 'any', 'never',
302
+ ],
303
+ type: 'string',
304
+ },
305
+ {
306
+ additionalProperties: false,
307
+ properties: {
308
+ applyToEndTag: {
309
+ type: 'boolean',
310
+ },
311
+ count: {
312
+ type: 'integer',
313
+ },
314
+ endLines: {
315
+ anyOf: [
316
+ {
317
+ type: 'integer',
318
+ },
319
+ {
320
+ type: 'null',
321
+ },
322
+ ],
323
+ },
324
+ startLines: {
325
+ anyOf: [
326
+ {
327
+ type: 'integer',
328
+ },
329
+ {
330
+ type: 'null',
331
+ },
332
+ ],
333
+ },
334
+ tags: {
335
+ patternProperties: {
336
+ '.*': {
337
+ additionalProperties: false,
338
+ properties: {
339
+ count: {
340
+ type: 'integer',
341
+ },
342
+ lines: {
343
+ enum: [
344
+ 'always', 'never', 'any',
345
+ ],
346
+ type: 'string',
347
+ },
348
+ },
349
+ },
350
+ },
351
+ type: 'object',
352
+ },
353
+ },
354
+ type: 'object',
355
+ },
356
+ ],
357
+ type: 'suggestion',
358
+ },
359
+ });
@@ -0,0 +1,146 @@
1
+ import iterateJsdoc from '../iterateJsdoc.js';
2
+
3
+ // We could disallow raw gt, quot, and apos, but allow for parity; but we do
4
+ // not allow hex or decimal character references
5
+ const htmlRegex = /(<|&(?!(?:amp|lt|gt|quot|apos);))(?=\S)/u;
6
+ const markdownRegex = /(?<!\\)(`+)([^`]+)\1(?!`)/u;
7
+
8
+ /**
9
+ * @param {string} desc
10
+ * @returns {string}
11
+ */
12
+ const htmlReplacer = (desc) => {
13
+ return desc.replaceAll(new RegExp(htmlRegex, 'gu'), (_) => {
14
+ if (_ === '<') {
15
+ return '&lt;';
16
+ }
17
+
18
+ return '&amp;';
19
+ });
20
+ };
21
+
22
+ /**
23
+ * @param {string} desc
24
+ * @returns {string}
25
+ */
26
+ const markdownReplacer = (desc) => {
27
+ return desc.replaceAll(new RegExp(markdownRegex, 'gu'), (_, backticks, encapsed) => {
28
+ const bookend = '`'.repeat(backticks.length);
29
+ return `\\${bookend}${encapsed}${bookend}`;
30
+ });
31
+ };
32
+
33
+ export default iterateJsdoc(({
34
+ context,
35
+ jsdoc,
36
+ utils,
37
+ }) => {
38
+ const {
39
+ escapeHTML,
40
+ escapeMarkdown,
41
+ } = context.options[0] || {};
42
+
43
+ if (!escapeHTML && !escapeMarkdown) {
44
+ context.report({
45
+ loc: {
46
+ end: {
47
+ column: 1,
48
+ line: 1,
49
+ },
50
+ start: {
51
+ column: 1,
52
+ line: 1,
53
+ },
54
+ },
55
+ message: 'You must include either `escapeHTML` or `escapeMarkdown`',
56
+ });
57
+ return;
58
+ }
59
+
60
+ const {
61
+ descriptions,
62
+ } = utils.getDescription();
63
+
64
+ if (escapeHTML) {
65
+ if (descriptions.some((desc) => {
66
+ return htmlRegex.test(desc);
67
+ })) {
68
+ const line = utils.setDescriptionLines(htmlRegex, htmlReplacer);
69
+ utils.reportJSDoc('You have unescaped HTML characters < or &', {
70
+ line,
71
+ }, () => {}, true);
72
+ return;
73
+ }
74
+
75
+ for (const tag of jsdoc.tags) {
76
+ if (/** @type {string[]} */ (
77
+ utils.getTagDescription(tag, true)
78
+ ).some((desc) => {
79
+ return htmlRegex.test(desc);
80
+ })) {
81
+ const line = utils.setTagDescription(tag, htmlRegex, htmlReplacer) +
82
+ tag.source[0].number;
83
+ utils.reportJSDoc('You have unescaped HTML characters < or & in a tag', {
84
+ line,
85
+ }, () => {}, true);
86
+ }
87
+ }
88
+
89
+ return;
90
+ }
91
+
92
+ if (descriptions.some((desc) => {
93
+ return markdownRegex.test(desc);
94
+ })) {
95
+ const line = utils.setDescriptionLines(markdownRegex, markdownReplacer);
96
+ utils.reportJSDoc('You have unescaped Markdown backtick sequences', {
97
+ line,
98
+ }, () => {}, true);
99
+ return;
100
+ }
101
+
102
+ for (const tag of jsdoc.tags) {
103
+ if (/** @type {string[]} */ (
104
+ utils.getTagDescription(tag, true)
105
+ ).some((desc) => {
106
+ return markdownRegex.test(desc);
107
+ })) {
108
+ const line = utils.setTagDescription(
109
+ tag, markdownRegex, markdownReplacer,
110
+ ) + tag.source[0].number;
111
+ utils.reportJSDoc(
112
+ 'You have unescaped Markdown backtick sequences in a tag',
113
+ {
114
+ line,
115
+ },
116
+ () => {},
117
+ true,
118
+ );
119
+ }
120
+ }
121
+ }, {
122
+ iterateAllJsdocs: true,
123
+ meta: {
124
+ docs: {
125
+ description: '',
126
+ url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/text-escaping.md#repos-sticky-header',
127
+ },
128
+ fixable: 'code',
129
+ schema: [
130
+ {
131
+ additionalProperties: false,
132
+ properties: {
133
+ // Option properties here (or remove the object)
134
+ escapeHTML: {
135
+ type: 'boolean',
136
+ },
137
+ escapeMarkdown: {
138
+ type: 'boolean',
139
+ },
140
+ },
141
+ type: 'object',
142
+ },
143
+ ],
144
+ type: 'suggestion',
145
+ },
146
+ });