pkgprn 0.5.1 → 0.5.2

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/strip-comments.js CHANGED
@@ -1,36 +1,11 @@
1
1
  import { decode, encode } from '@jridgewell/sourcemap-codec';
2
2
 
3
- /**
4
- * @typedef {'jsdoc' | 'license' | 'regular'} CommentType
5
- */
6
-
7
- /**
8
- * @typedef {Object} CommentRange
9
- * @property {number} start - Start index in source (inclusive)
10
- * @property {number} end - End index in source (exclusive)
11
- * @property {CommentType} type - Classification of the comment
12
- */
13
-
14
- /**
15
- * @typedef {Object} StripResult
16
- * @property {string} result - The stripped source text
17
- * @property {Int32Array | null} lineMap - Maps 0-based original line → 0-based new line (-1 if removed). null when nothing changed.
18
- */
19
-
20
3
  const jsExtensions = ['.js', '.mjs', '.cjs'];
21
4
 
22
- /**
23
- * Check if a file path has a JS extension that may contain comments.
24
- * @param {string} file
25
- * @returns {boolean}
26
- */
27
5
  export function isStrippableFile(file) {
28
6
  return jsExtensions.some(ext => file.endsWith(ext));
29
7
  }
30
8
 
31
- /**
32
- * Keywords after which a `/` token begins a regex literal rather than division.
33
- */
34
9
  const regexPrecedingKeywords = new Set([
35
10
  'return',
36
11
  'throw',
@@ -51,28 +26,17 @@ const regexPrecedingKeywords = new Set([
51
26
  'else',
52
27
  ]);
53
28
 
54
- /**
55
- * Classify a block comment based on its content.
56
- * Priority: license > jsdoc > regular.
57
- *
58
- * @param {string} source - Full source text
59
- * @param {number} start - Start index of the comment (at `/`)
60
- * @param {number} end - End index of the comment (after `*​/`)
61
- * @returns {CommentType}
62
- */
63
29
  function classifyBlockComment(source, start, end) {
64
- // License: starts with /*! or contains @license or @preserve
30
+
65
31
  if (source[start + 2] === '!') {
66
32
  return 'license';
67
33
  }
68
34
 
69
- // Check for @license or @preserve inside the comment body
70
35
  const body = source.slice(start + 2, end - 2);
71
36
  if (body.includes('@license') || body.includes('@preserve')) {
72
37
  return 'license';
73
38
  }
74
39
 
75
- // JSDoc: starts with /** (and is not the degenerate /**/ which is length 4)
76
40
  if (source[start + 2] === '*' && end - start > 4) {
77
41
  return 'jsdoc';
78
42
  }
@@ -80,113 +44,80 @@ function classifyBlockComment(source, start, end) {
80
44
  return 'regular';
81
45
  }
82
46
 
83
- /**
84
- * Scan source code and return an array of comment ranges with their types.
85
- * Correctly handles:
86
- * - Single and double quoted strings (with escapes)
87
- * - Template literals (with nested `${…}` expressions, arbitrarily deep)
88
- * - Regular expression literals (with character classes `[…]`)
89
- * - Hashbang lines (`#!/…`)
90
- * - Single-line comments (`// …`)
91
- * - Block comments (`/* … *​/`)
92
- *
93
- * @param {string} source
94
- * @returns {CommentRange[]}
95
- */
96
47
  export function scanComments(source) {
97
- /** @type {CommentRange[]} */
48
+
98
49
  const comments = [];
99
50
  const len = source.length;
100
51
  let i = 0;
101
52
 
102
- // Stack for template literal nesting.
103
- // Each entry holds the brace depth inside a `${…}` expression.
104
- // When the stack is non-empty the main loop is inside a template expression.
105
- /** @type {number[]} */
106
53
  const templateStack = [];
107
54
 
108
- // For regex-vs-division disambiguation we track whether the last
109
- // *significant* (non-whitespace, non-comment) token could be the end
110
- // of an expression. If it could, `/` is the division operator;
111
- // otherwise `/` starts a regex literal.
112
55
  let exprEnd = false;
113
56
 
114
- // --- Hashbang ----------------------------------------------------------
115
57
  if (len >= 2 && source[0] === '#' && source[1] === '!') {
116
- // Skip the entire hashbang line — it is never a comment.
58
+
117
59
  while (i < len && source[i] !== '\n') i++;
118
- // exprEnd stays false (hashbang is like the start of the file)
60
+
119
61
  }
120
62
 
121
63
  while (i < len) {
122
64
  const ch = source.charCodeAt(i);
123
65
 
124
- // ---- whitespace (skip, preserve exprEnd) --------------------------
125
- // space, tab, newline, carriage return, vertical tab, form feed,
126
- // BOM / NBSP (0xFEFF, 0x00A0) – we keep it simple: anything ≤ 0x20
127
- // plus the two common Unicode whitespace chars.
128
66
  if (ch <= 0x20 || ch === 0xfeff || ch === 0xa0) {
129
67
  i++;
130
68
  continue;
131
69
  }
132
70
 
133
- // ---- single-line comment ------------------------------------------
134
- if (ch === 0x2f /* / */ && i + 1 < len && source.charCodeAt(i + 1) === 0x2f /* / */) {
71
+ if (ch === 0x2f && i + 1 < len && source.charCodeAt(i + 1) === 0x2f ) {
135
72
  const start = i;
136
73
  i += 2;
137
- while (i < len && source.charCodeAt(i) !== 0x0a /* \n */) i++;
74
+ while (i < len && source.charCodeAt(i) !== 0x0a ) i++;
138
75
  comments.push({ start, end: i, type: 'regular' });
139
- // exprEnd unchanged (comments are transparent)
76
+
140
77
  continue;
141
78
  }
142
79
 
143
- // ---- block comment ------------------------------------------------
144
- if (ch === 0x2f /* / */ && i + 1 < len && source.charCodeAt(i + 1) === 0x2a /* * */) {
80
+ if (ch === 0x2f && i + 1 < len && source.charCodeAt(i + 1) === 0x2a ) {
145
81
  const start = i;
146
82
  i += 2;
147
- while (i < len && !((source.charCodeAt(i) === 0x2a /* * */ && i + 1 < len && source.charCodeAt(i + 1) === 0x2f) /* / */)) {
83
+ while (i < len && !((source.charCodeAt(i) === 0x2a && i + 1 < len && source.charCodeAt(i + 1) === 0x2f) )) {
148
84
  i++;
149
85
  }
150
- if (i < len) i += 2; // skip closing */
86
+ if (i < len) i += 2;
151
87
  comments.push({ start, end: i, type: classifyBlockComment(source, start, i) });
152
- // exprEnd unchanged
88
+
153
89
  continue;
154
90
  }
155
91
 
156
- // ---- regex literal ------------------------------------------------
157
- if (ch === 0x2f /* / */ && !exprEnd) {
92
+ if (ch === 0x2f && !exprEnd) {
158
93
  i = skipRegex(source, i, len);
159
- exprEnd = true; // a regex is a value
94
+ exprEnd = true;
160
95
  continue;
161
96
  }
162
97
 
163
- // ---- single-quoted string ----------------------------------------
164
- if (ch === 0x27 /* ' */) {
98
+ if (ch === 0x27 ) {
165
99
  i = skipSingleString(source, i, len);
166
100
  exprEnd = true;
167
101
  continue;
168
102
  }
169
103
 
170
- // ---- double-quoted string ----------------------------------------
171
- if (ch === 0x22 /* " */) {
104
+ if (ch === 0x22 ) {
172
105
  i = skipDoubleString(source, i, len);
173
106
  exprEnd = true;
174
107
  continue;
175
108
  }
176
109
 
177
- // ---- template literal --------------------------------------------
178
- if (ch === 0x60 /* ` */) {
110
+ if (ch === 0x60 ) {
179
111
  i = scanTemplateTail(source, i + 1, len, templateStack, comments);
180
112
  exprEnd = true;
181
113
  continue;
182
114
  }
183
115
 
184
- // ---- closing brace: may end a template expression ----------------
185
- if (ch === 0x7d /* } */) {
116
+ if (ch === 0x7d ) {
186
117
  if (templateStack.length > 0) {
187
118
  const depth = templateStack[templateStack.length - 1];
188
119
  if (depth === 0) {
189
- // Returning from a template expression back to the template body.
120
+
190
121
  templateStack.pop();
191
122
  i = scanTemplateTail(source, i + 1, len, templateStack, comments);
192
123
  exprEnd = true;
@@ -195,17 +126,12 @@ export function scanComments(source) {
195
126
  templateStack[templateStack.length - 1] = depth - 1;
196
127
  }
197
128
  i++;
198
- // After `}` we conservatively assume regex can follow.
199
- // This is correct for block statements, if/for/while bodies,
200
- // class bodies, etc. For the rare `({}) / x` pattern it would
201
- // misidentify division as regex, but that is harmless for
202
- // comment detection (we just skip over the "regex" body).
129
+
203
130
  exprEnd = false;
204
131
  continue;
205
132
  }
206
133
 
207
- // ---- opening brace -----------------------------------------------
208
- if (ch === 0x7b /* { */) {
134
+ if (ch === 0x7b ) {
209
135
  if (templateStack.length > 0) {
210
136
  templateStack[templateStack.length - 1]++;
211
137
  }
@@ -214,7 +140,6 @@ export function scanComments(source) {
214
140
  continue;
215
141
  }
216
142
 
217
- // ---- identifier / keyword / number --------------------------------
218
143
  if (isIdentStart(ch) || isDigit(ch)) {
219
144
  const wordStart = i;
220
145
  i++;
@@ -224,21 +149,18 @@ export function scanComments(source) {
224
149
  continue;
225
150
  }
226
151
 
227
- // ---- ++ and -- ----------------------------------------------------
228
- if ((ch === 0x2b /* + */ || ch === 0x2d) /* - */ && i + 1 < len && source.charCodeAt(i + 1) === ch) {
152
+ if ((ch === 0x2b || ch === 0x2d) && i + 1 < len && source.charCodeAt(i + 1) === ch) {
229
153
  i += 2;
230
- exprEnd = true; // `x++` / `x--` end an expression
154
+ exprEnd = true;
231
155
  continue;
232
156
  }
233
157
 
234
- // ---- closing brackets ) ] ----------------------------------------
235
- if (ch === 0x29 /* ) */ || ch === 0x5d /* ] */) {
158
+ if (ch === 0x29 || ch === 0x5d ) {
236
159
  i++;
237
160
  exprEnd = true;
238
161
  continue;
239
162
  }
240
163
 
241
- // ---- everything else: operators, punctuation ----------------------
242
164
  i++;
243
165
  exprEnd = false;
244
166
  }
@@ -246,266 +168,168 @@ export function scanComments(source) {
246
168
  return comments;
247
169
  }
248
170
 
249
- // ---------------------------------------------------------------------------
250
- // Character classification helpers
251
- // ---------------------------------------------------------------------------
252
-
253
- /**
254
- * @param {number} ch - char code
255
- * @returns {boolean}
256
- */
257
171
  function isDigit(ch) {
258
- return ch >= 0x30 && ch <= 0x39; // 0-9
172
+ return ch >= 0x30 && ch <= 0x39;
259
173
  }
260
174
 
261
- /**
262
- * @param {number} ch - char code
263
- * @returns {boolean}
264
- */
265
175
  function isIdentStart(ch) {
266
176
  return (
267
- (ch >= 0x41 && ch <= 0x5a) || // A-Z
268
- (ch >= 0x61 && ch <= 0x7a) || // a-z
269
- ch === 0x5f || // _
270
- ch === 0x24 || // $
271
- ch === 0x5c || // \ (unicode escape in identifier)
272
- ch > 0x7f // non-ASCII (simplified – covers all Unicode ID_Start)
177
+ (ch >= 0x41 && ch <= 0x5a) ||
178
+ (ch >= 0x61 && ch <= 0x7a) ||
179
+ ch === 0x5f ||
180
+ ch === 0x24 ||
181
+ ch === 0x5c ||
182
+ ch > 0x7f
273
183
  );
274
184
  }
275
185
 
276
- /**
277
- * @param {number} ch - char code
278
- * @returns {boolean}
279
- */
280
186
  function isIdentPart(ch) {
281
187
  return isIdentStart(ch) || isDigit(ch);
282
188
  }
283
189
 
284
- // ---------------------------------------------------------------------------
285
- // Skip helpers — each returns the new index *after* the construct.
286
- // ---------------------------------------------------------------------------
287
-
288
- /**
289
- * Skip a single-quoted string starting at index `i` (which points at the
290
- * opening `'`). Returns the index after the closing `'`.
291
- * @param {string} s
292
- * @param {number} i
293
- * @param {number} len
294
- * @returns {number}
295
- */
296
190
  function skipSingleString(s, i, len) {
297
- i++; // skip opening '
191
+ i++;
298
192
  while (i < len) {
299
193
  const ch = s.charCodeAt(i);
300
- if (ch === 0x27 /* ' */) {
194
+ if (ch === 0x27 ) {
301
195
  i++;
302
196
  break;
303
197
  }
304
- if (ch === 0x5c /* \ */) {
198
+ if (ch === 0x5c ) {
305
199
  i += 2;
306
200
  continue;
307
- } // escape
308
- if (ch === 0x0a /* \n */ || ch === 0x0d /* \r */) break; // unterminated
201
+ }
202
+ if (ch === 0x0a || ch === 0x0d ) break;
309
203
  i++;
310
204
  }
311
205
  return i;
312
206
  }
313
207
 
314
- /**
315
- * Skip a double-quoted string starting at index `i` (which points at the
316
- * opening `"`). Returns the index after the closing `"`.
317
- * @param {string} s
318
- * @param {number} i
319
- * @param {number} len
320
- * @returns {number}
321
- */
322
208
  function skipDoubleString(s, i, len) {
323
- i++; // skip opening "
209
+ i++;
324
210
  while (i < len) {
325
211
  const ch = s.charCodeAt(i);
326
- if (ch === 0x22 /* " */) {
212
+ if (ch === 0x22 ) {
327
213
  i++;
328
214
  break;
329
215
  }
330
- if (ch === 0x5c /* \ */) {
216
+ if (ch === 0x5c ) {
331
217
  i += 2;
332
218
  continue;
333
219
  }
334
- if (ch === 0x0a || ch === 0x0d) break; // unterminated
220
+ if (ch === 0x0a || ch === 0x0d) break;
335
221
  i++;
336
222
  }
337
223
  return i;
338
224
  }
339
225
 
340
- /**
341
- * Skip a regex literal starting at index `i` (which points at the opening `/`).
342
- * Handles character classes `[…]` and escape sequences.
343
- * Returns the index after the closing `/` and any flags.
344
- * @param {string} s
345
- * @param {number} i
346
- * @param {number} len
347
- * @returns {number}
348
- */
349
226
  function skipRegex(s, i, len) {
350
- i++; // skip opening /
227
+ i++;
351
228
  while (i < len) {
352
229
  const ch = s.charCodeAt(i);
353
- if (ch === 0x5c /* \ */) {
354
- i += 2; // skip escaped char
230
+ if (ch === 0x5c ) {
231
+ i += 2;
355
232
  continue;
356
233
  }
357
- if (ch === 0x5b /* [ */) {
358
- // character class — `]` inside does not end the regex
234
+ if (ch === 0x5b ) {
235
+
359
236
  i++;
360
237
  while (i < len) {
361
238
  const cc = s.charCodeAt(i);
362
- if (cc === 0x5c /* \ */) {
239
+ if (cc === 0x5c ) {
363
240
  i += 2;
364
241
  continue;
365
242
  }
366
- if (cc === 0x5d /* ] */) {
243
+ if (cc === 0x5d ) {
367
244
  i++;
368
245
  break;
369
246
  }
370
- if (cc === 0x0a || cc === 0x0d) break; // safety: unterminated
247
+ if (cc === 0x0a || cc === 0x0d) break;
371
248
  i++;
372
249
  }
373
250
  continue;
374
251
  }
375
- if (ch === 0x2f /* / */) {
376
- i++; // skip closing /
377
- // consume flags: [a-z] (dgimsvy…)
252
+ if (ch === 0x2f ) {
253
+ i++;
254
+
378
255
  while (i < len && isRegexFlag(s.charCodeAt(i))) i++;
379
256
  break;
380
257
  }
381
- if (ch === 0x0a || ch === 0x0d) break; // unterminated on this line
258
+ if (ch === 0x0a || ch === 0x0d) break;
382
259
  i++;
383
260
  }
384
261
  return i;
385
262
  }
386
263
 
387
- /**
388
- * @param {number} ch
389
- * @returns {boolean}
390
- */
391
264
  function isRegexFlag(ch) {
392
- return ch >= 0x61 && ch <= 0x7a; // a-z
265
+ return ch >= 0x61 && ch <= 0x7a;
393
266
  }
394
267
 
395
- /**
396
- * Scan the body of a template literal starting *after* the opening `` ` ``
397
- * (or after the `}` that closes a template expression).
398
- *
399
- * If we hit `${`, we push onto `templateStack` and return to the main loop
400
- * so that the expression is parsed as normal code (which may contain
401
- * comments, nested templates, etc.).
402
- *
403
- * If we hit the closing `` ` ``, we return and the template is done.
404
- *
405
- * @param {string} s
406
- * @param {number} i - index right after the `` ` `` or `}`
407
- * @param {number} len
408
- * @param {number[]} templateStack
409
- * @param {CommentRange[]} comments - passed through so inner comments are recorded
410
- * @returns {number} new index
411
- */
412
268
  function scanTemplateTail(s, i, len, templateStack, comments) {
413
- void comments; // comments only found inside ${} which returns to main loop
269
+ void comments;
414
270
  while (i < len) {
415
271
  const ch = s.charCodeAt(i);
416
- if (ch === 0x5c /* \ */) {
417
- i += 2; // skip escape sequence
272
+ if (ch === 0x5c ) {
273
+ i += 2;
418
274
  continue;
419
275
  }
420
- if (ch === 0x60 /* ` */) {
421
- i++; // closing backtick
276
+ if (ch === 0x60 ) {
277
+ i++;
422
278
  return i;
423
279
  }
424
- if (ch === 0x24 /* $ */ && i + 1 < len && s.charCodeAt(i + 1) === 0x7b /* { */) {
425
- i += 2; // skip ${
426
- templateStack.push(0); // push new brace depth for this expression
427
- return i; // return to main loop for expression parsing
280
+ if (ch === 0x24 && i + 1 < len && s.charCodeAt(i + 1) === 0x7b ) {
281
+ i += 2;
282
+ templateStack.push(0);
283
+ return i;
428
284
  }
429
285
  i++;
430
286
  }
431
287
  return i;
432
288
  }
433
289
 
434
- // ---------------------------------------------------------------------------
435
- // Public API
436
- // ---------------------------------------------------------------------------
437
-
438
- /**
439
- * Parse the `--strip-comments` flag value into a `Set` of comment types.
440
- *
441
- * - `'all'` or `true` → `{'jsdoc', 'license', 'regular'}`
442
- * - `'jsdoc,regular'` → `{'jsdoc', 'regular'}`
443
- *
444
- * @param {string | true} value
445
- * @returns {Set<CommentType>}
446
- */
447
290
  export function parseCommentTypes(value) {
448
291
  if (value === true || value === 'all') {
449
- return new Set(/** @type {CommentType[]} */ (['jsdoc', 'license', 'regular']));
292
+ return new Set( (['jsdoc', 'license', 'regular']));
450
293
  }
451
294
 
452
- const valid = /** @type {CommentType[]} */ (['jsdoc', 'license', 'regular']);
295
+ const valid = (['jsdoc', 'license', 'regular']);
453
296
  const parts = String(value)
454
297
  .split(',')
455
298
  .map(s => s.trim())
456
299
  .filter(Boolean);
457
300
 
458
- /** @type {Set<CommentType>} */
459
301
  const result = new Set();
460
302
 
461
303
  for (const part of parts) {
462
304
  if (part === 'all') {
463
305
  return new Set(valid);
464
306
  }
465
- if (!valid.includes(/** @type {CommentType} */ (part))) {
307
+ if (!valid.includes( (part))) {
466
308
  throw new Error(`unknown comment type "${part}" (expected: ${valid.join(', ')}, all)`);
467
309
  }
468
- result.add(/** @type {CommentType} */ (part));
310
+ result.add( (part));
469
311
  }
470
312
 
471
313
  if (result.size === 0) {
472
- return new Set(valid); // fallback to all
314
+ return new Set(valid);
473
315
  }
474
316
 
475
317
  return result;
476
318
  }
477
319
 
478
- /**
479
- * Strip comments from `source` whose type is in `typesToStrip`.
480
- *
481
- * @param {string} source
482
- * @param {Set<CommentType>} typesToStrip
483
- * @returns {string}
484
- */
485
320
  export function stripComments(source, typesToStrip) {
486
321
  return stripCommentsWithLineMap(source, typesToStrip).result;
487
322
  }
488
323
 
489
- /**
490
- * Strip comments and return both the stripped source and a line map that
491
- * tracks where each original line ended up in the output.
492
- *
493
- * @param {string} source
494
- * @param {Set<CommentType>} typesToStrip
495
- * @returns {StripResult}
496
- */
497
324
  export function stripCommentsWithLineMap(source, typesToStrip) {
498
325
  const comments = scanComments(source);
499
326
 
500
327
  if (comments.length === 0) return { result: source, lineMap: null };
501
328
 
502
- // Filter to only the comments we want to remove.
503
329
  const toRemove = comments.filter(c => typesToStrip.has(c.type));
504
330
 
505
331
  if (toRemove.length === 0) return { result: source, lineMap: null };
506
332
 
507
- // Build output by copying non-removed ranges.
508
- /** @type {string[]} */
509
333
  const parts = [];
510
334
  let pos = 0;
511
335
 
@@ -522,58 +346,36 @@ export function stripCommentsWithLineMap(source, typesToStrip) {
522
346
 
523
347
  let intermediate = parts.join('');
524
348
 
525
- // --- Build original-line → intermediate-line mapping -------------------
526
- // For every original offset, compute how many bytes were removed before it.
527
- // Then convert original line-start offsets to intermediate offsets and
528
- // derive intermediate line numbers.
529
-
530
349
  const origLines = source.split('\n');
531
350
  const origLineCount = origLines.length;
532
351
 
533
- // Build sorted prefix-sum of removed byte counts for fast lookup.
534
- // removedBefore(offset) = total chars removed in ranges fully before offset.
535
- // We also detect if an offset falls inside a removed range.
536
-
537
- /**
538
- * Translate an original offset to an intermediate offset.
539
- * Returns -1 if the offset is inside a removed range.
540
- * @param {number} offset
541
- * @returns {number}
542
- */
543
352
  function translateOffset(offset) {
544
353
  let removed = 0;
545
354
  for (const { start, end } of toRemove) {
546
355
  if (offset < start) break;
547
- if (offset < end) return -1; // inside removed range
356
+ if (offset < end) return -1;
548
357
  removed += end - start;
549
358
  }
550
359
  return offset - removed;
551
360
  }
552
361
 
553
- // For each original line, figure out which intermediate line it maps to.
554
- // An original line maps to -1 if its entire non-whitespace content was
555
- // inside removed ranges (i.e. the line becomes blank/whitespace-only).
556
362
  const intermediateText = intermediate;
557
363
  const intermediateLineStarts = buildLineStarts(intermediateText);
558
364
 
559
- /** @type {Int32Array} */
560
365
  const origToIntermediate = new Int32Array(origLineCount).fill(-1);
561
366
  let origOffset = 0;
562
367
  for (let oi = 0; oi < origLineCount; oi++) {
563
368
  const lineLen = origLines[oi].length;
564
- // Check if any content on this line survives.
565
- // We try the line-start offset; if it's inside a removed range
566
- // the whole beginning is gone, but content may survive later.
567
- // The most reliable way: translate the offset of each non-WS char.
369
+
568
370
  let survived = false;
569
371
  for (let ci = 0; ci < lineLen; ci++) {
570
372
  const ch = source.charCodeAt(origOffset + ci);
571
- // skip whitespace chars — they don't count as surviving content
373
+
572
374
  if (ch === 0x20 || ch === 0x09 || ch === 0x0d) continue;
573
375
  const mapped = translateOffset(origOffset + ci);
574
376
  if (mapped !== -1) {
575
377
  survived = true;
576
- // Convert intermediate offset to intermediate line number.
378
+
577
379
  origToIntermediate[oi] = offsetToLine(intermediateLineStarts, mapped);
578
380
  break;
579
381
  }
@@ -581,18 +383,13 @@ export function stripCommentsWithLineMap(source, typesToStrip) {
581
383
  if (!survived) {
582
384
  origToIntermediate[oi] = -1;
583
385
  }
584
- origOffset += lineLen + 1; // +1 for the '\n' (split removed it)
386
+ origOffset += lineLen + 1;
585
387
  }
586
388
 
587
- // --- Apply cleanup (same logic as before) ------------------------------
588
-
589
- // Trim trailing whitespace from every line.
590
389
  intermediate = intermediate.replace(/[ \t]+$/gm, '');
591
390
 
592
- // Collapse 3+ consecutive newlines into 2 newlines.
593
391
  intermediate = intermediate.replace(/\n{3,}/g, '\n\n');
594
392
 
595
- // Remove leading blank lines (preserve hashbang).
596
393
  if (intermediate.startsWith('#!')) {
597
394
  const hashbangEnd = intermediate.indexOf('\n');
598
395
  if (hashbangEnd !== -1) {
@@ -604,23 +401,17 @@ export function stripCommentsWithLineMap(source, typesToStrip) {
604
401
  intermediate = intermediate.replace(/^\n+/, '');
605
402
  }
606
403
 
607
- // Ensure the file ends with exactly one newline (if it originally did).
608
404
  if (source.endsWith('\n') && intermediate.length > 0) {
609
405
  intermediate = intermediate.replace(/\n*$/, '\n');
610
406
  }
611
407
 
612
408
  const result = intermediate;
613
409
 
614
- // --- Build intermediate-line → final-line mapping ----------------------
615
- // The cleanup may have removed/collapsed lines from the intermediateText.
616
- // We line up intermediateText lines with final lines by content matching.
617
410
  const finalLines = result.split('\n');
618
411
  const intLines = intermediateText.split('\n');
619
412
 
620
- // Trim trailing WS from intermediate lines to match what cleanup did.
621
413
  const intLinesTrimmed = intLines.map(l => l.replace(/[ \t]+$/, ''));
622
414
 
623
- /** @type {Int32Array} */
624
415
  const intermediateToFinal = new Int32Array(intLines.length).fill(-1);
625
416
  let fi = 0;
626
417
  for (let ii = 0; ii < intLinesTrimmed.length && fi < finalLines.length; ii++) {
@@ -628,11 +419,9 @@ export function stripCommentsWithLineMap(source, typesToStrip) {
628
419
  intermediateToFinal[ii] = fi;
629
420
  fi++;
630
421
  }
631
- // else: this intermediate line was removed by cleanup → stays -1
422
+
632
423
  }
633
424
 
634
- // --- Compose: original → intermediate → final --------------------------
635
- /** @type {Int32Array} */
636
425
  const lineMap = new Int32Array(origLineCount).fill(-1);
637
426
  for (let oi = 0; oi < origLineCount; oi++) {
638
427
  const il = origToIntermediate[oi];
@@ -644,14 +433,8 @@ export function stripCommentsWithLineMap(source, typesToStrip) {
644
433
  return { result, lineMap };
645
434
  }
646
435
 
647
- /**
648
- * Build an array of line-start offsets for the given text.
649
- * `result[i]` is the char offset where line `i` begins (0-based lines).
650
- * @param {string} text
651
- * @returns {number[]}
652
- */
653
436
  function buildLineStarts(text) {
654
- /** @type {number[]} */
437
+
655
438
  const starts = [0];
656
439
  for (let i = 0; i < text.length; i++) {
657
440
  if (text.charCodeAt(i) === 0x0a) {
@@ -661,14 +444,8 @@ function buildLineStarts(text) {
661
444
  return starts;
662
445
  }
663
446
 
664
- /**
665
- * Given sorted line-start offsets, find which line a char offset falls on.
666
- * @param {number[]} lineStarts
667
- * @param {number} offset
668
- * @returns {number} 0-based line number
669
- */
670
447
  function offsetToLine(lineStarts, offset) {
671
- // Binary search for the last lineStart <= offset.
448
+
672
449
  let lo = 0;
673
450
  let hi = lineStarts.length - 1;
674
451
  while (lo < hi) {
@@ -682,43 +459,31 @@ function offsetToLine(lineStarts, offset) {
682
459
  return lo;
683
460
  }
684
461
 
685
- /**
686
- * Adjust a parsed sourcemap (v3) whose `sources` reference a file that had
687
- * comments stripped. Updates the original-line numbers in `mappings` for
688
- * segments that point at the given source index.
689
- *
690
- * Segments whose original line maps to -1 (i.e. the line was removed) are
691
- * dropped from the output.
692
- *
693
- * @param {{ version: number, mappings: string, sources?: string[], names?: string[], [k: string]: unknown }} map - Parsed sourcemap object (mutated in place).
694
- * @param {number} sourceIndex - Index in `map.sources` of the stripped file.
695
- * @param {Int32Array} lineMap - 0-based original line → 0-based new line (-1 if removed).
696
- */
697
462
  export function adjustSourcemapLineMappings(map, sourceIndex, lineMap) {
698
463
  if (map.version !== 3 || typeof map.mappings !== 'string') return;
699
464
 
700
465
  const decoded = decode(map.mappings);
701
466
 
702
467
  for (const line of decoded) {
703
- // Walk backwards so we can splice without index issues.
468
+
704
469
  for (let si = line.length - 1; si >= 0; si--) {
705
470
  const seg = line[si];
706
- // Segments with < 4 fields have no source mapping.
471
+
707
472
  if (seg.length < 4) continue;
708
- const seg4 = /** @type {[number, number, number, number, ...number[]]} */ (seg);
709
- // Only adjust segments pointing at the stripped source file.
473
+ const seg4 = (seg);
474
+
710
475
  if (seg4[1] !== sourceIndex) continue;
711
476
 
712
- const origLine = seg4[2]; // 0-based
477
+ const origLine = seg4[2];
713
478
  if (origLine < 0 || origLine >= lineMap.length) {
714
- // Out of range — drop it.
479
+
715
480
  line.splice(si, 1);
716
481
  continue;
717
482
  }
718
483
 
719
484
  const newLine = lineMap[origLine];
720
485
  if (newLine === -1) {
721
- // The line was removed — drop this segment.
486
+
722
487
  line.splice(si, 1);
723
488
  continue;
724
489
  }