eslint-plugin-jsdoc 65.2.0 → 65.2.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.
@@ -180,6 +180,8 @@ export type Utils = BasicUtils & {
180
180
  addLine: AddLine;
181
181
  addLines: AddLines;
182
182
  makeMultiline: MakeMultiline;
183
+ moveClosingDelimiterToOwnLine: () => boolean;
184
+ newLineEnd: string;
183
185
  flattenRoots: import("./jsdocUtils.js").FlattenRoots;
184
186
  getFunctionParameterNames: GetFunctionParameterNames;
185
187
  hasParams: HasParams;
package/package.json CHANGED
@@ -163,5 +163,5 @@
163
163
  "test-cov": "TIMING=1 c8 --reporter text pnpm run test-no-cov",
164
164
  "test-index": "pnpm run test-no-cov test/rules/index.js"
165
165
  },
166
- "version": "65.2.0"
166
+ "version": "65.2.1"
167
167
  }
@@ -520,6 +520,8 @@ import esquery from 'esquery';
520
520
  * addLine: AddLine,
521
521
  * addLines: AddLines,
522
522
  * makeMultiline: MakeMultiline,
523
+ * moveClosingDelimiterToOwnLine: () => boolean,
524
+ * newLineEnd: string,
523
525
  * flattenRoots: import('./jsdocUtils.js').FlattenRoots,
524
526
  * getFunctionParameterNames: GetFunctionParameterNames,
525
527
  * hasParams: HasParams,
@@ -1188,30 +1190,150 @@ const getUtils = (
1188
1190
  // correct information will be available)
1189
1191
  };
1190
1192
 
1191
- /** @type {AddTag} */
1192
- utils.addTag = (
1193
- targetTagName,
1194
- number = (jsdoc.tags[jsdoc.tags.length - 1]?.source[0]?.number ?? jsdoc.source.findIndex(({
1193
+ // `comment-parser` keeps the `\r` of a CRLF line break in `lineEnd`
1194
+ const newLineEnd = sourceCode.text.includes('\r\n') ? '\r' : '';
1195
+ utils.newLineEnd = newLineEnd;
1196
+
1197
+ // From last to first, as printed on a line
1198
+ const contentKeys = /** @type {const} */ ([
1199
+ 'description',
1200
+ 'postName',
1201
+ 'name',
1202
+ 'postType',
1203
+ 'type',
1204
+ 'postTag',
1205
+ 'tag',
1206
+ ]);
1207
+
1208
+ /**
1209
+ * @param {import('comment-parser').Tokens} lineTokens
1210
+ * @returns {boolean}
1211
+ */
1212
+ const hasContent = (lineTokens) => {
1213
+ return contentKeys.some((key) => {
1214
+ return lineTokens[key];
1215
+ });
1216
+ };
1217
+
1218
+ /**
1219
+ * Moves a closing delimiter which follows content on the same line to a
1220
+ * line of its own.
1221
+ * @returns {boolean} Whether a change was made
1222
+ */
1223
+ utils.moveClosingDelimiterToOwnLine = () => {
1224
+ const closingIndex = jsdoc.source.length - 1;
1225
+ const {
1226
+ tokens: closingTokens,
1227
+ } = jsdoc.source[closingIndex];
1228
+
1229
+ if (!closingTokens.end || !hasContent(closingTokens)) {
1230
+ return false;
1231
+ }
1232
+
1233
+ const {
1234
+ end,
1235
+ lineEnd,
1236
+ } = closingTokens;
1237
+
1238
+ // Tags may hold their own copies of the line (see `rewireSpecs`)
1239
+ const {
1240
+ number: closingNumber,
1241
+ } = jsdoc.source[closingIndex];
1242
+ const lines = [
1243
+ jsdoc.source[closingIndex],
1244
+ ...jsdoc.tags.flatMap(({
1245
+ source,
1246
+ }) => {
1247
+ return source.filter(({
1248
+ number,
1249
+ }) => {
1250
+ return number === closingNumber;
1251
+ });
1252
+ }),
1253
+ ];
1254
+
1255
+ for (const {
1256
+ tokens: lineTokens,
1257
+ } of lines) {
1258
+ lineTokens.end = '';
1259
+ lineTokens.lineEnd = newLineEnd;
1260
+
1261
+ // Strip the whitespace which preceded the closing delimiter
1262
+ const lastKey = /** @type {NonNullable<typeof contentKeys[number]>} */ (
1263
+ contentKeys.find((key) => {
1264
+ return lineTokens[key];
1265
+ })
1266
+ );
1267
+ lineTokens[lastKey] = lineTokens[lastKey].trimEnd();
1268
+ }
1269
+
1270
+ utils.addLine(closingIndex + 1, {
1271
+ end,
1272
+ lineEnd,
1273
+ start: indent + ' ',
1274
+ });
1275
+
1276
+ return true;
1277
+ };
1278
+
1279
+ /**
1280
+ * Prepares the block for, and gets, the index at which a new tag belongs:
1281
+ * after the last line of the last tag (including its continuation lines),
1282
+ * or at the end of a description if there are no tags, and in any case
1283
+ * before the closing delimiter, which is moved to a line of its own
1284
+ * (mutating `jsdoc.source`) if need be.
1285
+ * @returns {Integer}
1286
+ */
1287
+ const prepareTagInsertionIndex = () => {
1288
+ const closingIndex = jsdoc.source.length - 1;
1289
+
1290
+ if (utils.moveClosingDelimiterToOwnLine()) {
1291
+ return closingIndex + 1;
1292
+ }
1293
+
1294
+ if (!jsdoc.source.some(({
1195
1295
  tokens: {
1196
1296
  tag,
1197
1297
  },
1198
1298
  }) => {
1199
1299
  return tag;
1200
- }) - 1) + 1,
1300
+ })) {
1301
+ return closingIndex;
1302
+ }
1303
+
1304
+ // Blank lines before the closing delimiter do not belong to the tag
1305
+ return jsdoc.source.findLastIndex(({
1306
+ tokens: lineTokens,
1307
+ }, idx) => {
1308
+ return idx < closingIndex && hasContent(lineTokens);
1309
+ }) + 1;
1310
+ };
1311
+
1312
+ /** @type {AddTag} */
1313
+ utils.addTag = (
1314
+ targetTagName,
1315
+ number,
1201
1316
  tokens = {},
1202
1317
  ) => {
1203
- jsdoc.source.splice(number, 0, {
1204
- number,
1318
+ if (number === undefined && jsdoc.source.length === 1) {
1319
+ utils.makeMultiline();
1320
+ }
1321
+
1322
+ const insertionIndex = number ?? prepareTagInsertionIndex();
1323
+
1324
+ jsdoc.source.splice(insertionIndex, 0, {
1325
+ number: insertionIndex,
1205
1326
  source: '',
1206
1327
  tokens: seedTokens({
1207
1328
  delimiter: '*',
1329
+ lineEnd: newLineEnd,
1208
1330
  postDelimiter: ' ',
1209
1331
  start: indent + ' ',
1210
1332
  tag: `@${targetTagName}`,
1211
1333
  ...tokens,
1212
1334
  }),
1213
1335
  });
1214
- for (const src of jsdoc.source.slice(number + 1)) {
1336
+ for (const src of jsdoc.source.slice(insertionIndex + 1)) {
1215
1337
  src.number++;
1216
1338
  }
1217
1339
  };
@@ -1376,6 +1498,7 @@ const getUtils = (
1376
1498
  }
1377
1499
 
1378
1500
  utils.emptyTokens(tokens);
1501
+ tokens.lineEnd = newLineEnd;
1379
1502
 
1380
1503
  utils.addLine(1, {
1381
1504
  delimiter: '*',
@@ -1383,6 +1506,7 @@ const getUtils = (
1383
1506
  // If a description were present, it may have whitespace attached
1384
1507
  // due to being at the end of the single line
1385
1508
  description: description.trimEnd(),
1509
+ lineEnd: newLineEnd,
1386
1510
  name,
1387
1511
  postDelimiter,
1388
1512
  postName,
@@ -22,6 +22,52 @@ const getSimpleParameterName = (parameter) => {
22
22
  return parameter.name;
23
23
  };
24
24
 
25
+ const contentKeys = /** @type {const} */ ([
26
+ 'description',
27
+ 'postName',
28
+ 'name',
29
+ 'postType',
30
+ 'type',
31
+ 'postTag',
32
+ 'tag',
33
+ ]);
34
+
35
+ /**
36
+ * @param {import('comment-parser').Tokens} tokens
37
+ * @returns {boolean}
38
+ */
39
+ const hasContent = (tokens) => {
40
+ return contentKeys.some((key) => {
41
+ return tokens[key];
42
+ });
43
+ };
44
+
45
+ /**
46
+ * Copies tokens, omitting any closing delimiter (and the whitespace
47
+ * preceding it).
48
+ * @param {import('comment-parser').Tokens} tokens
49
+ * @param {string} newLineEnd
50
+ * @returns {import('comment-parser').Tokens}
51
+ */
52
+ const withoutClosing = (tokens, newLineEnd) => {
53
+ const copy = {
54
+ ...tokens,
55
+ end: '',
56
+ };
57
+ if (tokens.end) {
58
+ copy.lineEnd = newLineEnd;
59
+
60
+ const lastKey = contentKeys.find((key) => {
61
+ return copy[key];
62
+ });
63
+ if (lastKey) {
64
+ copy[lastKey] = copy[lastKey].trimEnd();
65
+ }
66
+ }
67
+
68
+ return copy;
69
+ };
70
+
25
71
  /**
26
72
  * @param {import('../iterateJsdoc.js').Integer} firstChangedTagIndex
27
73
  * @param {import('comment-parser').Spec[]} orderedTags
@@ -33,6 +79,9 @@ const makeParamOrderFix = (
33
79
  firstChangedTagIndex, orderedTags, jsdoc, utils,
34
80
  ) => {
35
81
  return () => {
82
+ // Otherwise, the closing delimiter would be copied along with the last tag
83
+ utils.moveClosingDelimiterToOwnLine();
84
+
36
85
  const itemsToMoveRange = [
37
86
  ...Array.from({
38
87
  length: jsdoc.tags.length - firstChangedTagIndex,
@@ -62,25 +111,25 @@ const makeParamOrderFix = (
62
111
  for (const index of itemsToMoveRange) {
63
112
  const changedTag = changedTags[index];
64
113
 
114
+ const [
115
+ firstLine,
116
+ ...otherLines
117
+ ] = changedTag.source;
118
+
65
119
  utils.addTag(
66
120
  changedTag.tag,
67
121
  extraTagCount + initialOffset + index,
68
- {
69
- ...changedTag.source[0].tokens,
70
- end: '',
71
- },
122
+ withoutClosing(firstLine.tokens, utils.newLineEnd),
72
123
  );
73
124
 
74
125
  for (const {
75
126
  tokens,
76
- } of changedTag.source.slice(1)) {
77
- if (!tokens.end) {
127
+ } of otherLines) {
128
+ // A closing delimiter alone on its line is not part of the tag
129
+ if (!tokens.end || hasContent(tokens)) {
78
130
  utils.addLine(
79
131
  extraTagCount + initialOffset + index + 1,
80
- {
81
- ...tokens,
82
- end: '',
83
- },
132
+ withoutClosing(tokens, utils.newLineEnd),
84
133
  );
85
134
  extraTagCount++;
86
135
  }