@dereekb/util 13.11.3 → 13.11.4

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.
@@ -76,7 +76,11 @@
76
76
  if (((_implNode_id = implNode.id) === null || _implNode_id === void 0 ? void 0 : _implNode_id.type) !== 'Identifier') {
77
77
  return {
78
78
  jsdoc: null,
79
- orphanLineComments: []
79
+ orphanLineComments: [],
80
+ hasOverloads: false,
81
+ implLineComment: null,
82
+ implHasSurvivingAnnotation: false,
83
+ chainStartStatement: getStatementAnchor(implNode)
80
84
  };
81
85
  }
82
86
  var name = implNode.id.name;
@@ -97,10 +101,16 @@
97
101
  }
98
102
  }
99
103
  }
104
+ var hasOverloads = chainStartIdx >= 0 && implIdx >= 0 && chainStartIdx < implIdx;
100
105
  var firstJsdoc = null;
101
106
  var anyJsdocHasNoSideEffects = false;
107
+ var implJsdocHasNoSideEffects = false;
102
108
  var orphanLineComments = [];
103
- function processCommentsForStatement(stmt) {
109
+
110
+ // declaration is required (TS erases overload signatures, so only this annotation survives).
111
+ // Track it separately so callers don't accidentally remove it.
112
+ var implLineComment = null;
113
+ function processCommentsForStatement(stmt, isImplStatement) {
104
114
  var comments = sourceCode.getCommentsBefore(stmt) || [];
105
115
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
106
116
  try {
@@ -110,6 +120,9 @@
110
120
  var hasMarker = commentContainsNoSideEffects(comment.value);
111
121
  if (hasMarker) {
112
122
  anyJsdocHasNoSideEffects = true;
123
+ if (isImplStatement) {
124
+ implJsdocHasNoSideEffects = true;
125
+ }
113
126
  }
114
127
  // Auto-fix target: the first JSDoc in the chain (where the function's docs conventionally live).
115
128
  if (!firstJsdoc) {
@@ -120,7 +133,17 @@
120
133
  };
121
134
  }
122
135
  } else if (commentContainsNoSideEffects(comment.value)) {
123
- orphanLineComments.push(comment);
136
+ // Only the impl-leading annotation on an overloaded function is "required"; others are orphans.
137
+ // When multiple line/block comments stack above the impl, keep the closest one (last in source
138
+ // order) as the canonical impl annotation and treat the rest as orphans to consolidate.
139
+ if (isImplStatement && hasOverloads) {
140
+ if (implLineComment) {
141
+ orphanLineComments.push(implLineComment);
142
+ }
143
+ implLineComment = comment;
144
+ } else {
145
+ orphanLineComments.push(comment);
146
+ }
124
147
  }
125
148
  }
126
149
  } catch (err) {
@@ -140,11 +163,11 @@
140
163
  }
141
164
  if (chainStartIdx >= 0 && implIdx >= 0) {
142
165
  for(var i1 = chainStartIdx; i1 <= implIdx; i1 += 1){
143
- processCommentsForStatement(container.body[i1]);
166
+ processCommentsForStatement(container.body[i1], i1 === implIdx);
144
167
  }
145
168
  } else {
146
169
  // Fallback: no container body found; just look at comments before the implementation.
147
- processCommentsForStatement(implStmt);
170
+ processCommentsForStatement(implStmt, false);
148
171
  }
149
172
  // Report the first JSDoc as the canonical jsdoc, but reflect any-in-chain satisfaction
150
173
  // so callers don't re-annotate when the marker is already present elsewhere in the chain.
@@ -157,9 +180,18 @@
157
180
  hasNoSideEffects: anyJsdocHasNoSideEffects
158
181
  };
159
182
  }
183
+ // The implementation's emitted JS carries the marker when:
184
+ // - non-overloaded: the function's (only) JSDoc has the tag (it's directly attached to the impl), OR
185
+ // - overloaded: a line/block comment sits above the impl, OR the impl has its own tagged JSDoc.
186
+ var implHasSurvivingAnnotation = hasOverloads ? implLineComment !== null || implJsdocHasNoSideEffects : anyJsdocHasNoSideEffects;
187
+ var chainStartStatement = chainStartIdx >= 0 && container && Array.isArray(container.body) ? container.body[chainStartIdx] : implStmt;
160
188
  return {
161
189
  jsdoc: resolved,
162
- orphanLineComments: orphanLineComments
190
+ orphanLineComments: orphanLineComments,
191
+ hasOverloads: hasOverloads,
192
+ implLineComment: implLineComment,
193
+ implHasSurvivingAnnotation: implHasSurvivingAnnotation,
194
+ chainStartStatement: chainStartStatement
163
195
  };
164
196
  }
165
197
 
@@ -258,15 +290,7 @@ function _unsupported_iterable_to_array$1(o, minLen) {
258
290
  });
259
291
  return _to_consumable_array(DEFAULT_NAME_PATTERNS).concat(_to_consumable_array(additional));
260
292
  }
261
- /**
262
- * ESLint rule requiring the side-effect-free annotation inside the JSDoc of every
263
- * factory function — that is, declarations carrying the dbxUtilKind factory JSDoc tag,
264
- * and optionally functions matching factory name patterns.
265
- *
266
- * Auto-fix inserts the annotation as the last line of the JSDoc, removes any
267
- * redundant standalone-comment annotation between the JSDoc and the declaration,
268
- * and (when matched by name with no JSDoc) creates a minimal JSDoc carrying both tags.
269
- */ var utilRequireNoSideEffectsRule = {
293
+ var utilRequireNoSideEffectsRule = {
270
294
  meta: {
271
295
  type: 'suggestion',
272
296
  fixable: 'code',
@@ -276,7 +300,8 @@ function _unsupported_iterable_to_array$1(o, minLen) {
276
300
  },
277
301
  messages: {
278
302
  missingNoSideEffectsJsdoc: 'Factory function "{{name}}" is missing the `@__NO_SIDE_EFFECTS__` annotation in its JSDoc. Add it as the last tag inside the JSDoc block so esbuild can drop unused calls during tree-shaking.',
279
- missingJsdocForFactory: 'Factory-named function "{{name}}" has no JSDoc block. Add a JSDoc block containing `@__NO_SIDE_EFFECTS__` so esbuild can drop unused calls during tree-shaking.'
303
+ missingJsdocForFactory: 'Factory-named function "{{name}}" has no JSDoc block. Add a JSDoc block containing `@__NO_SIDE_EFFECTS__` so esbuild can drop unused calls during tree-shaking.',
304
+ missingImplAnnotationOverloaded: 'Overloaded factory function "{{name}}" needs `@__NO_SIDE_EFFECTS__` directly on its implementation — TypeScript erases overload signatures during emit, so the JSDoc tag on the first overload is dropped from the bundled JavaScript. Add a `// @__NO_SIDE_EFFECTS__` line comment immediately above the implementation declaration.'
280
305
  },
281
306
  schema: [
282
307
  {
@@ -319,19 +344,32 @@ function _unsupported_iterable_to_array$1(o, minLen) {
319
344
  if (!name) {
320
345
  return;
321
346
  }
322
- // Walks the overload chain (if any) so we read the JSDoc on the first overload
323
- // and any orphan annotations placed between overloads or above the implementation.
324
- var _findFunctionLeadingContext = findFunctionLeadingContext(sourceCode, node), jsdoc = _findFunctionLeadingContext.jsdoc, redundantLineComments = _findFunctionLeadingContext.orphanLineComments;
347
+ // Walks the overload chain (if any) so we read the JSDoc on the first overload,
348
+ // any orphan annotations placed between overloads, and the (preserved) impl-leading annotation.
349
+ var _findFunctionLeadingContext = findFunctionLeadingContext(sourceCode, node), jsdoc = _findFunctionLeadingContext.jsdoc, redundantLineComments = _findFunctionLeadingContext.orphanLineComments, hasOverloads = _findFunctionLeadingContext.hasOverloads, implLineComment = _findFunctionLeadingContext.implLineComment, implHasSurvivingAnnotation = _findFunctionLeadingContext.implHasSurvivingAnnotation, chainStartStatement = _findFunctionLeadingContext.chainStartStatement;
325
350
  var taggedAsFactory = (jsdoc === null || jsdoc === void 0 ? void 0 : (_jsdoc_text = jsdoc.text) === null || _jsdoc_text === void 0 ? void 0 : _jsdoc_text.includes(FACTORY_JSDOC_TAG)) === true;
326
351
  var matchedByName = !taggedAsFactory && namePatterns.length > 0 && nameMatchesFactoryPattern(name);
327
352
  if (!taggedAsFactory && !matchedByName) {
328
353
  return;
329
354
  }
330
- // Already annotated inside the JSDoc — passing.
331
- if (jsdoc === null || jsdoc === void 0 ? void 0 : jsdoc.hasNoSideEffects) {
355
+ // The bundled implementation already carries the marker — passing. This covers:
356
+ // - non-overloaded with the tag in the (only) JSDoc, AND
357
+
358
+ if (implHasSurvivingAnnotation) {
332
359
  return;
333
360
  }
334
- var messageId = jsdoc ? 'missingNoSideEffectsJsdoc' : 'missingJsdocForFactory';
361
+ // Choose the most specific message:
362
+ // - overloaded + JSDoc with the tag on first overload but no impl annotation → impl-specific.
363
+ // - has any JSDoc → JSDoc tag missing.
364
+ // - no JSDoc → JSDoc must be created.
365
+ var messageId;
366
+ if (hasOverloads && (jsdoc === null || jsdoc === void 0 ? void 0 : jsdoc.hasNoSideEffects)) {
367
+ messageId = 'missingImplAnnotationOverloaded';
368
+ } else if (jsdoc) {
369
+ messageId = 'missingNoSideEffectsJsdoc';
370
+ } else {
371
+ messageId = 'missingJsdocForFactory';
372
+ }
335
373
  context.report({
336
374
  node: node.id,
337
375
  messageId: messageId,
@@ -340,7 +378,10 @@ function _unsupported_iterable_to_array$1(o, minLen) {
340
378
  },
341
379
  fix: function fix(fixer) {
342
380
  var fixes = [];
343
- if (jsdoc) {
381
+ // Add the JSDoc tag (or create the JSDoc) so consumer-facing docs reflect the marker.
382
+ // Skipped when the existing JSDoc already carries the tag — only the impl-line comment
383
+ // would be missing in that case (handled below).
384
+ if (jsdoc && !jsdoc.hasNoSideEffects) {
344
385
  var jsdocText = jsdoc.text; // text excludes /* and */
345
386
  var jsdocStart = jsdoc.node.range[0];
346
387
  var jsdocEnd = jsdoc.node.range[1];
@@ -370,11 +411,10 @@ function _unsupported_iterable_to_array$1(o, minLen) {
370
411
  closingLineStart
371
412
  ], insertion));
372
413
  }
373
- } else {
374
- // No JSDoc — create one above the function declaration with matching indent.
375
- // Use the function's leading position. Account for `export` keyword: getCommentsBefore
376
- // attaches to the declaration including its modifiers, so node.range[0] is correct.
377
- var nodeStart = node.range[0];
414
+ } else if (!jsdoc) {
415
+ // No JSDoc anywhere — create one above the FIRST statement in the chain. For overloaded
416
+ // functions the canonical doc placement is on the first overload, not the implementation.
417
+ var nodeStart = chainStartStatement.range[0];
378
418
  var indent = getLineIndent(sourceText, nodeStart);
379
419
  var newJsdoc = "/**\n".concat(indent, " * @dbxUtilKind factory\n").concat(indent, " * ").concat(NO_SIDE_EFFECTS_TAG, "\n").concat(indent, " */\n").concat(indent);
380
420
  fixes.push(fixer.insertTextBeforeRange([
@@ -382,6 +422,18 @@ function _unsupported_iterable_to_array$1(o, minLen) {
382
422
  nodeStart
383
423
  ], newJsdoc));
384
424
  }
425
+
426
+ // implementation so the marker survives TypeScript's overload-signature erasure and reaches
427
+ // the bundled JavaScript. Skipped when one is already in place.
428
+ if (hasOverloads && !implLineComment) {
429
+ var implAnchor = getStatementAnchor(node);
430
+ var implStart = implAnchor.range[0];
431
+ var indent1 = getLineIndent(sourceText, implStart);
432
+ fixes.push(fixer.insertTextBeforeRange([
433
+ implStart,
434
+ implStart
435
+ ], "// ".concat(NO_SIDE_EFFECTS_TAG, "\n").concat(indent1)));
436
+ }
385
437
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
386
438
  try {
387
439
  // Remove any redundant adjacent annotation comments now that JSDoc carries the tag.
@@ -485,7 +537,8 @@ function _unsupported_iterable_to_array(o, minLen) {
485
537
  recommended: true
486
538
  },
487
539
  messages: {
488
- preferJsdocPlacement: 'Move the `@__NO_SIDE_EFFECTS__` annotation into the JSDoc block of "{{name}}" (as the last tag before the closing) instead of a separate line comment.'
540
+ preferJsdocPlacement: 'Move the `@__NO_SIDE_EFFECTS__` annotation into the JSDoc block of "{{name}}" (as the last tag before the closing) instead of a separate line comment.',
541
+ missingImplAnnotationOverloaded: '"{{name}}" carries `@__NO_SIDE_EFFECTS__` in its first-overload JSDoc but is overloaded — TypeScript erases overload signatures during emit, so the JSDoc tag is dropped from the bundled JavaScript. Add a `// @__NO_SIDE_EFFECTS__` line comment immediately above the implementation declaration.'
489
542
  },
490
543
  schema: []
491
544
  },
@@ -501,16 +554,32 @@ function _unsupported_iterable_to_array(o, minLen) {
501
554
  return;
502
555
  }
503
556
  var name = node.id.name;
504
- // Walks the overload chain (if any) so we find the JSDoc on the first overload
505
- // and any orphan annotations between overloads or above the implementation.
506
- var _findFunctionLeadingContext = findFunctionLeadingContext(sourceCode, node), jsdoc = _findFunctionLeadingContext.jsdoc, orphanLineComments = _findFunctionLeadingContext.orphanLineComments;
507
- // Only flag when both signals are present: a JSDoc to absorb the tag, AND an orphan annotation.
508
- if (!jsdoc || orphanLineComments.length === 0) {
557
+ // Walks the overload chain (if any) so we find the JSDoc on the first overload,
558
+ // any orphan annotations between overloads, and the (preserved) impl-leading annotation.
559
+ var _findFunctionLeadingContext = findFunctionLeadingContext(sourceCode, node), jsdoc = _findFunctionLeadingContext.jsdoc, orphanLineComments = _findFunctionLeadingContext.orphanLineComments, implLineComment = _findFunctionLeadingContext.implLineComment, hasOverloads = _findFunctionLeadingContext.hasOverloads, implHasSurvivingAnnotation = _findFunctionLeadingContext.implHasSurvivingAnnotation;
560
+ if (!jsdoc) {
561
+ return;
562
+ }
563
+ // Three reasons to fire:
564
+ // 1. There's an annotation form (orphan OR overload-impl line comment) and the JSDoc
565
+ // doesn't yet carry the tag — the JSDoc needs the tag added (for docs/tooling).
566
+ // 2. There are orphan annotations to consolidate, regardless of JSDoc tag state.
567
+ // 3. Function is overloaded, JSDoc carries the tag, but the implementation lacks any
568
+ // surviving annotation — TS erases overload signatures during emit, so the JSDoc tag
569
+
570
+ // above the impl so the bundler still sees the hint.
571
+ // The impl line comment on overloaded functions is REQUIRED for tree-shaking and is never
572
+ // removed — it is only counted as a signal that the JSDoc should also carry the tag.
573
+ var hasAnyAnnotationSource = orphanLineComments.length > 0 || implLineComment !== null;
574
+ var needsJsdocTag = !jsdoc.hasNoSideEffects && hasAnyAnnotationSource;
575
+ var needsOrphanRemoval = orphanLineComments.length > 0;
576
+ var needsImplLineCommentForOverload = jsdoc.hasNoSideEffects && hasOverloads && !implHasSurvivingAnnotation;
577
+ if (!needsJsdocTag && !needsOrphanRemoval && !needsImplLineCommentForOverload) {
509
578
  return;
510
579
  }
511
580
  context.report({
512
581
  node: node.id,
513
- messageId: 'preferJsdocPlacement',
582
+ messageId: needsImplLineCommentForOverload && !needsJsdocTag && !needsOrphanRemoval ? 'missingImplAnnotationOverloaded' : 'preferJsdocPlacement',
514
583
  data: {
515
584
  name: name
516
585
  },
@@ -520,8 +589,8 @@ function _unsupported_iterable_to_array(o, minLen) {
520
589
  var jsdocStart = jsdoc.node.range[0];
521
590
  var jsdocEnd = jsdoc.node.range[1];
522
591
  var jsdocIndent = getLineIndent(sourceText, jsdocStart);
523
- // Insert into JSDoc only if the tag isn't already there (preserve idempotency).
524
- if (!jsdoc.hasNoSideEffects) {
592
+ // Insert into JSDoc only if needed (preserve idempotency and skip when only orphans need removal).
593
+ if (needsJsdocTag) {
525
594
  if (!jsdocText.includes('\n')) {
526
595
  // Single-line JSDoc — expand to multi-line so the new tag has its own line.
527
596
  var bodyTrimmed = jsdocText.replace(/^\*\s*/, '').replace(/\s*$/, '');
@@ -544,6 +613,17 @@ function _unsupported_iterable_to_array(o, minLen) {
544
613
  ], insertion));
545
614
  }
546
615
  }
616
+ // Overloaded function with no surviving impl annotation — insert the bundler-required
617
+ // line comment directly above the implementation declaration.
618
+ if (needsImplLineCommentForOverload) {
619
+ var implAnchor = getStatementAnchor(node);
620
+ var implStart = implAnchor.range[0];
621
+ var indent = getLineIndent(sourceText, implStart);
622
+ fixes.push(fixer.insertTextBeforeRange([
623
+ implStart,
624
+ implStart
625
+ ], "// ".concat(NO_SIDE_EFFECTS_TAG, "\n").concat(indent)));
626
+ }
547
627
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
548
628
  try {
549
629
  // Remove the orphan line/block comment annotations.
@@ -74,7 +74,11 @@
74
74
  if (((_implNode_id = implNode.id) === null || _implNode_id === void 0 ? void 0 : _implNode_id.type) !== 'Identifier') {
75
75
  return {
76
76
  jsdoc: null,
77
- orphanLineComments: []
77
+ orphanLineComments: [],
78
+ hasOverloads: false,
79
+ implLineComment: null,
80
+ implHasSurvivingAnnotation: false,
81
+ chainStartStatement: getStatementAnchor(implNode)
78
82
  };
79
83
  }
80
84
  var name = implNode.id.name;
@@ -95,10 +99,16 @@
95
99
  }
96
100
  }
97
101
  }
102
+ var hasOverloads = chainStartIdx >= 0 && implIdx >= 0 && chainStartIdx < implIdx;
98
103
  var firstJsdoc = null;
99
104
  var anyJsdocHasNoSideEffects = false;
105
+ var implJsdocHasNoSideEffects = false;
100
106
  var orphanLineComments = [];
101
- function processCommentsForStatement(stmt) {
107
+
108
+ // declaration is required (TS erases overload signatures, so only this annotation survives).
109
+ // Track it separately so callers don't accidentally remove it.
110
+ var implLineComment = null;
111
+ function processCommentsForStatement(stmt, isImplStatement) {
102
112
  var comments = sourceCode.getCommentsBefore(stmt) || [];
103
113
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
104
114
  try {
@@ -108,6 +118,9 @@
108
118
  var hasMarker = commentContainsNoSideEffects(comment.value);
109
119
  if (hasMarker) {
110
120
  anyJsdocHasNoSideEffects = true;
121
+ if (isImplStatement) {
122
+ implJsdocHasNoSideEffects = true;
123
+ }
111
124
  }
112
125
  // Auto-fix target: the first JSDoc in the chain (where the function's docs conventionally live).
113
126
  if (!firstJsdoc) {
@@ -118,7 +131,17 @@
118
131
  };
119
132
  }
120
133
  } else if (commentContainsNoSideEffects(comment.value)) {
121
- orphanLineComments.push(comment);
134
+ // Only the impl-leading annotation on an overloaded function is "required"; others are orphans.
135
+ // When multiple line/block comments stack above the impl, keep the closest one (last in source
136
+ // order) as the canonical impl annotation and treat the rest as orphans to consolidate.
137
+ if (isImplStatement && hasOverloads) {
138
+ if (implLineComment) {
139
+ orphanLineComments.push(implLineComment);
140
+ }
141
+ implLineComment = comment;
142
+ } else {
143
+ orphanLineComments.push(comment);
144
+ }
122
145
  }
123
146
  }
124
147
  } catch (err) {
@@ -138,11 +161,11 @@
138
161
  }
139
162
  if (chainStartIdx >= 0 && implIdx >= 0) {
140
163
  for(var i1 = chainStartIdx; i1 <= implIdx; i1 += 1){
141
- processCommentsForStatement(container.body[i1]);
164
+ processCommentsForStatement(container.body[i1], i1 === implIdx);
142
165
  }
143
166
  } else {
144
167
  // Fallback: no container body found; just look at comments before the implementation.
145
- processCommentsForStatement(implStmt);
168
+ processCommentsForStatement(implStmt, false);
146
169
  }
147
170
  // Report the first JSDoc as the canonical jsdoc, but reflect any-in-chain satisfaction
148
171
  // so callers don't re-annotate when the marker is already present elsewhere in the chain.
@@ -155,9 +178,18 @@
155
178
  hasNoSideEffects: anyJsdocHasNoSideEffects
156
179
  };
157
180
  }
181
+ // The implementation's emitted JS carries the marker when:
182
+ // - non-overloaded: the function's (only) JSDoc has the tag (it's directly attached to the impl), OR
183
+ // - overloaded: a line/block comment sits above the impl, OR the impl has its own tagged JSDoc.
184
+ var implHasSurvivingAnnotation = hasOverloads ? implLineComment !== null || implJsdocHasNoSideEffects : anyJsdocHasNoSideEffects;
185
+ var chainStartStatement = chainStartIdx >= 0 && container && Array.isArray(container.body) ? container.body[chainStartIdx] : implStmt;
158
186
  return {
159
187
  jsdoc: resolved,
160
- orphanLineComments: orphanLineComments
188
+ orphanLineComments: orphanLineComments,
189
+ hasOverloads: hasOverloads,
190
+ implLineComment: implLineComment,
191
+ implHasSurvivingAnnotation: implHasSurvivingAnnotation,
192
+ chainStartStatement: chainStartStatement
161
193
  };
162
194
  }
163
195
 
@@ -256,15 +288,7 @@ function _unsupported_iterable_to_array$1(o, minLen) {
256
288
  });
257
289
  return _to_consumable_array(DEFAULT_NAME_PATTERNS).concat(_to_consumable_array(additional));
258
290
  }
259
- /**
260
- * ESLint rule requiring the side-effect-free annotation inside the JSDoc of every
261
- * factory function — that is, declarations carrying the dbxUtilKind factory JSDoc tag,
262
- * and optionally functions matching factory name patterns.
263
- *
264
- * Auto-fix inserts the annotation as the last line of the JSDoc, removes any
265
- * redundant standalone-comment annotation between the JSDoc and the declaration,
266
- * and (when matched by name with no JSDoc) creates a minimal JSDoc carrying both tags.
267
- */ var utilRequireNoSideEffectsRule = {
291
+ var utilRequireNoSideEffectsRule = {
268
292
  meta: {
269
293
  type: 'suggestion',
270
294
  fixable: 'code',
@@ -274,7 +298,8 @@ function _unsupported_iterable_to_array$1(o, minLen) {
274
298
  },
275
299
  messages: {
276
300
  missingNoSideEffectsJsdoc: 'Factory function "{{name}}" is missing the `@__NO_SIDE_EFFECTS__` annotation in its JSDoc. Add it as the last tag inside the JSDoc block so esbuild can drop unused calls during tree-shaking.',
277
- missingJsdocForFactory: 'Factory-named function "{{name}}" has no JSDoc block. Add a JSDoc block containing `@__NO_SIDE_EFFECTS__` so esbuild can drop unused calls during tree-shaking.'
301
+ missingJsdocForFactory: 'Factory-named function "{{name}}" has no JSDoc block. Add a JSDoc block containing `@__NO_SIDE_EFFECTS__` so esbuild can drop unused calls during tree-shaking.',
302
+ missingImplAnnotationOverloaded: 'Overloaded factory function "{{name}}" needs `@__NO_SIDE_EFFECTS__` directly on its implementation — TypeScript erases overload signatures during emit, so the JSDoc tag on the first overload is dropped from the bundled JavaScript. Add a `// @__NO_SIDE_EFFECTS__` line comment immediately above the implementation declaration.'
278
303
  },
279
304
  schema: [
280
305
  {
@@ -317,19 +342,32 @@ function _unsupported_iterable_to_array$1(o, minLen) {
317
342
  if (!name) {
318
343
  return;
319
344
  }
320
- // Walks the overload chain (if any) so we read the JSDoc on the first overload
321
- // and any orphan annotations placed between overloads or above the implementation.
322
- var _findFunctionLeadingContext = findFunctionLeadingContext(sourceCode, node), jsdoc = _findFunctionLeadingContext.jsdoc, redundantLineComments = _findFunctionLeadingContext.orphanLineComments;
345
+ // Walks the overload chain (if any) so we read the JSDoc on the first overload,
346
+ // any orphan annotations placed between overloads, and the (preserved) impl-leading annotation.
347
+ var _findFunctionLeadingContext = findFunctionLeadingContext(sourceCode, node), jsdoc = _findFunctionLeadingContext.jsdoc, redundantLineComments = _findFunctionLeadingContext.orphanLineComments, hasOverloads = _findFunctionLeadingContext.hasOverloads, implLineComment = _findFunctionLeadingContext.implLineComment, implHasSurvivingAnnotation = _findFunctionLeadingContext.implHasSurvivingAnnotation, chainStartStatement = _findFunctionLeadingContext.chainStartStatement;
323
348
  var taggedAsFactory = (jsdoc === null || jsdoc === void 0 ? void 0 : (_jsdoc_text = jsdoc.text) === null || _jsdoc_text === void 0 ? void 0 : _jsdoc_text.includes(FACTORY_JSDOC_TAG)) === true;
324
349
  var matchedByName = !taggedAsFactory && namePatterns.length > 0 && nameMatchesFactoryPattern(name);
325
350
  if (!taggedAsFactory && !matchedByName) {
326
351
  return;
327
352
  }
328
- // Already annotated inside the JSDoc — passing.
329
- if (jsdoc === null || jsdoc === void 0 ? void 0 : jsdoc.hasNoSideEffects) {
353
+ // The bundled implementation already carries the marker — passing. This covers:
354
+ // - non-overloaded with the tag in the (only) JSDoc, AND
355
+
356
+ if (implHasSurvivingAnnotation) {
330
357
  return;
331
358
  }
332
- var messageId = jsdoc ? 'missingNoSideEffectsJsdoc' : 'missingJsdocForFactory';
359
+ // Choose the most specific message:
360
+ // - overloaded + JSDoc with the tag on first overload but no impl annotation → impl-specific.
361
+ // - has any JSDoc → JSDoc tag missing.
362
+ // - no JSDoc → JSDoc must be created.
363
+ var messageId;
364
+ if (hasOverloads && (jsdoc === null || jsdoc === void 0 ? void 0 : jsdoc.hasNoSideEffects)) {
365
+ messageId = 'missingImplAnnotationOverloaded';
366
+ } else if (jsdoc) {
367
+ messageId = 'missingNoSideEffectsJsdoc';
368
+ } else {
369
+ messageId = 'missingJsdocForFactory';
370
+ }
333
371
  context.report({
334
372
  node: node.id,
335
373
  messageId: messageId,
@@ -338,7 +376,10 @@ function _unsupported_iterable_to_array$1(o, minLen) {
338
376
  },
339
377
  fix: function fix(fixer) {
340
378
  var fixes = [];
341
- if (jsdoc) {
379
+ // Add the JSDoc tag (or create the JSDoc) so consumer-facing docs reflect the marker.
380
+ // Skipped when the existing JSDoc already carries the tag — only the impl-line comment
381
+ // would be missing in that case (handled below).
382
+ if (jsdoc && !jsdoc.hasNoSideEffects) {
342
383
  var jsdocText = jsdoc.text; // text excludes /* and */
343
384
  var jsdocStart = jsdoc.node.range[0];
344
385
  var jsdocEnd = jsdoc.node.range[1];
@@ -368,11 +409,10 @@ function _unsupported_iterable_to_array$1(o, minLen) {
368
409
  closingLineStart
369
410
  ], insertion));
370
411
  }
371
- } else {
372
- // No JSDoc — create one above the function declaration with matching indent.
373
- // Use the function's leading position. Account for `export` keyword: getCommentsBefore
374
- // attaches to the declaration including its modifiers, so node.range[0] is correct.
375
- var nodeStart = node.range[0];
412
+ } else if (!jsdoc) {
413
+ // No JSDoc anywhere — create one above the FIRST statement in the chain. For overloaded
414
+ // functions the canonical doc placement is on the first overload, not the implementation.
415
+ var nodeStart = chainStartStatement.range[0];
376
416
  var indent = getLineIndent(sourceText, nodeStart);
377
417
  var newJsdoc = "/**\n".concat(indent, " * @dbxUtilKind factory\n").concat(indent, " * ").concat(NO_SIDE_EFFECTS_TAG, "\n").concat(indent, " */\n").concat(indent);
378
418
  fixes.push(fixer.insertTextBeforeRange([
@@ -380,6 +420,18 @@ function _unsupported_iterable_to_array$1(o, minLen) {
380
420
  nodeStart
381
421
  ], newJsdoc));
382
422
  }
423
+
424
+ // implementation so the marker survives TypeScript's overload-signature erasure and reaches
425
+ // the bundled JavaScript. Skipped when one is already in place.
426
+ if (hasOverloads && !implLineComment) {
427
+ var implAnchor = getStatementAnchor(node);
428
+ var implStart = implAnchor.range[0];
429
+ var indent1 = getLineIndent(sourceText, implStart);
430
+ fixes.push(fixer.insertTextBeforeRange([
431
+ implStart,
432
+ implStart
433
+ ], "// ".concat(NO_SIDE_EFFECTS_TAG, "\n").concat(indent1)));
434
+ }
383
435
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
384
436
  try {
385
437
  // Remove any redundant adjacent annotation comments now that JSDoc carries the tag.
@@ -483,7 +535,8 @@ function _unsupported_iterable_to_array(o, minLen) {
483
535
  recommended: true
484
536
  },
485
537
  messages: {
486
- preferJsdocPlacement: 'Move the `@__NO_SIDE_EFFECTS__` annotation into the JSDoc block of "{{name}}" (as the last tag before the closing) instead of a separate line comment.'
538
+ preferJsdocPlacement: 'Move the `@__NO_SIDE_EFFECTS__` annotation into the JSDoc block of "{{name}}" (as the last tag before the closing) instead of a separate line comment.',
539
+ missingImplAnnotationOverloaded: '"{{name}}" carries `@__NO_SIDE_EFFECTS__` in its first-overload JSDoc but is overloaded — TypeScript erases overload signatures during emit, so the JSDoc tag is dropped from the bundled JavaScript. Add a `// @__NO_SIDE_EFFECTS__` line comment immediately above the implementation declaration.'
487
540
  },
488
541
  schema: []
489
542
  },
@@ -499,16 +552,32 @@ function _unsupported_iterable_to_array(o, minLen) {
499
552
  return;
500
553
  }
501
554
  var name = node.id.name;
502
- // Walks the overload chain (if any) so we find the JSDoc on the first overload
503
- // and any orphan annotations between overloads or above the implementation.
504
- var _findFunctionLeadingContext = findFunctionLeadingContext(sourceCode, node), jsdoc = _findFunctionLeadingContext.jsdoc, orphanLineComments = _findFunctionLeadingContext.orphanLineComments;
505
- // Only flag when both signals are present: a JSDoc to absorb the tag, AND an orphan annotation.
506
- if (!jsdoc || orphanLineComments.length === 0) {
555
+ // Walks the overload chain (if any) so we find the JSDoc on the first overload,
556
+ // any orphan annotations between overloads, and the (preserved) impl-leading annotation.
557
+ var _findFunctionLeadingContext = findFunctionLeadingContext(sourceCode, node), jsdoc = _findFunctionLeadingContext.jsdoc, orphanLineComments = _findFunctionLeadingContext.orphanLineComments, implLineComment = _findFunctionLeadingContext.implLineComment, hasOverloads = _findFunctionLeadingContext.hasOverloads, implHasSurvivingAnnotation = _findFunctionLeadingContext.implHasSurvivingAnnotation;
558
+ if (!jsdoc) {
559
+ return;
560
+ }
561
+ // Three reasons to fire:
562
+ // 1. There's an annotation form (orphan OR overload-impl line comment) and the JSDoc
563
+ // doesn't yet carry the tag — the JSDoc needs the tag added (for docs/tooling).
564
+ // 2. There are orphan annotations to consolidate, regardless of JSDoc tag state.
565
+ // 3. Function is overloaded, JSDoc carries the tag, but the implementation lacks any
566
+ // surviving annotation — TS erases overload signatures during emit, so the JSDoc tag
567
+
568
+ // above the impl so the bundler still sees the hint.
569
+ // The impl line comment on overloaded functions is REQUIRED for tree-shaking and is never
570
+ // removed — it is only counted as a signal that the JSDoc should also carry the tag.
571
+ var hasAnyAnnotationSource = orphanLineComments.length > 0 || implLineComment !== null;
572
+ var needsJsdocTag = !jsdoc.hasNoSideEffects && hasAnyAnnotationSource;
573
+ var needsOrphanRemoval = orphanLineComments.length > 0;
574
+ var needsImplLineCommentForOverload = jsdoc.hasNoSideEffects && hasOverloads && !implHasSurvivingAnnotation;
575
+ if (!needsJsdocTag && !needsOrphanRemoval && !needsImplLineCommentForOverload) {
507
576
  return;
508
577
  }
509
578
  context.report({
510
579
  node: node.id,
511
- messageId: 'preferJsdocPlacement',
580
+ messageId: needsImplLineCommentForOverload && !needsJsdocTag && !needsOrphanRemoval ? 'missingImplAnnotationOverloaded' : 'preferJsdocPlacement',
512
581
  data: {
513
582
  name: name
514
583
  },
@@ -518,8 +587,8 @@ function _unsupported_iterable_to_array(o, minLen) {
518
587
  var jsdocStart = jsdoc.node.range[0];
519
588
  var jsdocEnd = jsdoc.node.range[1];
520
589
  var jsdocIndent = getLineIndent(sourceText, jsdocStart);
521
- // Insert into JSDoc only if the tag isn't already there (preserve idempotency).
522
- if (!jsdoc.hasNoSideEffects) {
590
+ // Insert into JSDoc only if needed (preserve idempotency and skip when only orphans need removal).
591
+ if (needsJsdocTag) {
523
592
  if (!jsdocText.includes('\n')) {
524
593
  // Single-line JSDoc — expand to multi-line so the new tag has its own line.
525
594
  var bodyTrimmed = jsdocText.replace(/^\*\s*/, '').replace(/\s*$/, '');
@@ -542,6 +611,17 @@ function _unsupported_iterable_to_array(o, minLen) {
542
611
  ], insertion));
543
612
  }
544
613
  }
614
+ // Overloaded function with no surviving impl annotation — insert the bundler-required
615
+ // line comment directly above the implementation declaration.
616
+ if (needsImplLineCommentForOverload) {
617
+ var implAnchor = getStatementAnchor(node);
618
+ var implStart = implAnchor.range[0];
619
+ var indent = getLineIndent(sourceText, implStart);
620
+ fixes.push(fixer.insertTextBeforeRange([
621
+ implStart,
622
+ implStart
623
+ ], "// ".concat(NO_SIDE_EFFECTS_TAG, "\n").concat(indent)));
624
+ }
545
625
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
546
626
  try {
547
627
  // Remove the orphan line/block comment annotations.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dereekb/util/eslint",
3
- "version": "13.11.3",
3
+ "version": "13.11.4",
4
4
  "peerDependencies": {
5
5
  "@typescript-eslint/utils": "8.59.0"
6
6
  },
@@ -10,7 +10,45 @@ export interface JsdocCommentInfo {
10
10
  }
11
11
  export interface FunctionLeadingContext {
12
12
  readonly jsdoc: JsdocCommentInfo | null;
13
+ /**
14
+ * Adjacent `@__NO_SIDE_EFFECTS__` line/block comments that should be migrated into the JSDoc
15
+ * and removed. Excludes the implementation-leading annotation on overloaded functions, which
16
+ * is tracked separately by {@link implLineComment} because it must be preserved.
17
+ */
13
18
  readonly orphanLineComments: readonly AstNode[];
19
+ /**
20
+ * True when this implementation is preceded by sibling overload signatures (`TSDeclareFunction`
21
+ * statements with the same name).
22
+ */
23
+ readonly hasOverloads: boolean;
24
+ /**
25
+ * The `@__NO_SIDE_EFFECTS__` line/block comment immediately above the implementation
26
+ * declaration when the function has overloads. TypeScript erases overload signatures during
27
+ * emit, so JSDoc placed only on the first overload is dropped from the bundled JS — the
28
+ * line comment directly above the implementation is what survives and reaches the bundler.
29
+ *
30
+ * `null` when the function is single-signature (the line comment is then a removable orphan)
31
+ * or when no such comment is present.
32
+ */
33
+ readonly implLineComment: AstNode | null;
34
+ /**
35
+ * True when the implementation will carry the `@__NO_SIDE_EFFECTS__` annotation in the emitted
36
+ * JavaScript:
37
+ *
38
+ * - Single-signature: true when the (only) JSDoc carries the tag.
39
+ * - Overloaded: true when a line/block comment with the tag sits immediately above the
40
+ * implementation, OR the implementation has its own JSDoc carrying the tag.
41
+ *
42
+ * This is the signal upstream packages need so esbuild/rollup can tree-shake unused calls.
43
+ */
44
+ readonly implHasSurvivingAnnotation: boolean;
45
+ /**
46
+ * The first statement in the overload chain (i.e. the first overload's export wrapper, or the
47
+ * implementation itself when there are no overloads). Use this as the insertion anchor when
48
+ * creating a brand-new JSDoc for the function, since docs conventionally live on the first
49
+ * overload rather than the implementation.
50
+ */
51
+ readonly chainStartStatement: AstNode;
14
52
  }
15
53
  /**
16
54
  * Returns true if the given comment text contains the @__NO_SIDE_EFFECTS__ marker.
@@ -27,6 +65,15 @@ export declare function commentContainsNoSideEffects(text: string): boolean;
27
65
  * @returns The whitespace prefix (spaces/tabs) of that line.
28
66
  */
29
67
  export declare function getLineIndent(sourceText: string, offset: number): string;
68
+ /**
69
+ * Returns the outermost statement node for a FunctionDeclaration — its `ExportNamedDeclaration`
70
+ * or `ExportDefaultDeclaration` parent if exported, otherwise the declaration itself. This is the
71
+ * node ESLint attaches leading comments to.
72
+ *
73
+ * @param node - The FunctionDeclaration AST node.
74
+ * @returns The statement node ESLint attaches leading comments to.
75
+ */
76
+ export declare function getStatementAnchor(node: AstNode): AstNode;
30
77
  /**
31
78
  * Walks backward from the implementation FunctionDeclaration through any overload signatures
32
79
  * with the same name, collecting: