@dereekb/util 13.11.3 → 13.11.5
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/eslint/index.cjs.js +117 -37
- package/eslint/index.esm.js +117 -37
- package/eslint/package.json +1 -1
- package/eslint/src/lib/comments.d.ts +47 -0
- package/eslint/src/lib/prefer-no-side-effects-in-jsdoc.rule.d.ts +8 -0
- package/eslint/src/lib/require-no-side-effects.rule.d.ts +19 -6
- package/fetch/package.json +2 -2
- package/index.cjs.js +219 -1
- package/index.esm.js +219 -2
- package/package.json +1 -1
- package/src/lib/date/date.d.ts +224 -1
- package/test/package.json +2 -2
package/eslint/index.cjs.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
331
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
375
|
-
//
|
|
376
|
-
|
|
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
|
-
//
|
|
506
|
-
var _findFunctionLeadingContext = findFunctionLeadingContext(sourceCode, node), jsdoc = _findFunctionLeadingContext.jsdoc, orphanLineComments = _findFunctionLeadingContext.orphanLineComments;
|
|
507
|
-
|
|
508
|
-
|
|
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
|
|
524
|
-
if (
|
|
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.
|
package/eslint/index.esm.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
329
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
373
|
-
//
|
|
374
|
-
|
|
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
|
-
//
|
|
504
|
-
var _findFunctionLeadingContext = findFunctionLeadingContext(sourceCode, node), jsdoc = _findFunctionLeadingContext.jsdoc, orphanLineComments = _findFunctionLeadingContext.orphanLineComments;
|
|
505
|
-
|
|
506
|
-
|
|
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
|
|
522
|
-
if (
|
|
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.
|
package/eslint/package.json
CHANGED
|
@@ -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:
|