@@ -184,71 +184,101 @@ export function removeSourceMappingURL(code: string): string {
184184}
185185
186186/**
187- * Finds, resolves, and loads the input sourcemap referenced in the code's trailing
188- * sourceMappingURL comment, if present. Supports inline base64 data URIs, local absolute
189- * file URLs, and relative/absolute filesystem paths.
187+ * Extracts the base64 payload from an inline sourcemap data URI line and verifies
188+ * that only trailing whitespace follows the payload.
189+ *
190+ * @returns The base64 payload string if valid and trailing, or `undefined` otherwise.
190191 */
191- export function loadInputSourceMap ( filename : string , code : string ) : EncodedSourceMap | undefined {
192- // Locate the last sourceMappingURL comment using lastIndexOf to avoid scanning
193- // the entire file with a regular expression (significant for large files).
194- const lastSourceMapIndex = code . lastIndexOf ( '//# sourceMappingURL=' ) ;
195- if ( lastSourceMapIndex === - 1 ) {
192+ function extractTrailingBase64Payload ( urlLine : string ) : string | undefined {
193+ if ( ! urlLine . startsWith ( 'data:application/json;' ) ) {
196194 return undefined ;
197195 }
198196
199- const urlLine = code . slice ( lastSourceMapIndex + 21 ) ;
200-
201- // Inline base64-encoded sourcemaps can be extremely large (up to megabytes).
202- // Parse them without regular expressions to avoid heavy backtracking and allocations.
203- if ( urlLine . startsWith ( 'data:application/json;' ) ) {
204- const base64StartIndex = urlLine . indexOf ( 'base64,' ) ;
205- if ( base64StartIndex === - 1 ) {
206- return undefined ;
207- }
197+ const base64StartIndex = urlLine . indexOf ( 'base64,' ) ;
198+ if ( base64StartIndex === - 1 ) {
199+ return undefined ;
200+ }
208201
209- const payloadStart = base64StartIndex + 7 ;
210- let payloadEnd = urlLine . length ;
211- // Find the first trailing whitespace character that marks the end of the base64 payload.
212- for ( let i = payloadStart ; i < urlLine . length ; i ++ ) {
213- const char = urlLine [ i ] ;
214- if ( char === ' ' || char === '\r' || char === '\n' || char === '\t' ) {
215- payloadEnd = i ;
216- break ;
217- }
202+ const payloadStart = base64StartIndex + 7 ;
203+ let payloadEnd = urlLine . length ;
204+ // Find the first trailing whitespace character that marks the end of the base64 payload.
205+ for ( let i = payloadStart ; i < urlLine . length ; i ++ ) {
206+ const char = urlLine [ i ] ;
207+ if ( char === ' ' || char === '\r' || char === '\n' || char === '\t' ) {
208+ payloadEnd = i ;
209+ break ;
218210 }
211+ }
219212
220- // Verify that everything after the base64 payload is trailing whitespace
221- // to ensure this is a valid trailing sourceMappingURL comment at the end of the file.
222- for ( let i = payloadEnd ; i < urlLine . length ; i ++ ) {
223- const char = urlLine [ i ] ;
224- if ( char !== ' ' && char !== '\r' && char !== '\n' && char !== '\t' ) {
225- return undefined ;
226- }
213+ // Verify that everything after the base64 payload is trailing whitespace
214+ // to ensure this is a valid trailing sourceMappingURL comment at the end of the file.
215+ for ( let i = payloadEnd ; i < urlLine . length ; i ++ ) {
216+ const char = urlLine [ i ] ;
217+ if ( char !== ' ' && char !== '\r' && char !== '\n' && char !== '\t' ) {
218+ return undefined ;
227219 }
220+ }
228221
229- try {
230- // Extract the base64 payload and decode it directly into binary memory.
231- const base64Content = urlLine . slice ( payloadStart , payloadEnd ) ;
222+ return urlLine . slice ( payloadStart , payloadEnd ) ;
223+ }
232224
233- return JSON . parse ( Buffer . from ( base64Content , 'base64' ) . toString ( 'utf-8' ) ) as EncodedSourceMap ;
234- } catch {
235- return undefined ;
236- }
225+ /**
226+ * Extracts the URL from an external sourcemap comment line and verifies
227+ * that only trailing whitespace follows the URL.
228+ *
229+ * @returns The URL string if valid and trailing, or `undefined` otherwise.
230+ */
231+ function extractTrailingUrl ( urlLine : string ) : string | undefined {
232+ if ( urlLine . startsWith ( 'data:' ) ) {
233+ return undefined ;
237234 }
238235
239- // Non-inline sourcemap comments (always small, typically < 200 characters).
240- const urlMatch = / ^ ( [ ^ \r \n \s ] + ) / . exec ( urlLine ) ;
236+ const urlMatch = / ^ ( [ ^ \r \n \s ' " ` ] + ) / . exec ( urlLine ) ;
241237 if ( ! urlMatch ) {
242238 return undefined ;
243239 }
244240
245- const url = urlMatch [ 1 ] ;
246- const remaining = urlLine . slice ( url . length ) ;
247- // Verify there is only whitespace after the URL to the end of the file.
241+ const remaining = urlLine . slice ( urlMatch [ 1 ] . length ) ;
248242 if ( ! / ^ \s * $ / . test ( remaining ) ) {
249243 return undefined ;
250244 }
251245
246+ return urlMatch [ 1 ] ;
247+ }
248+
249+ /**
250+ * Checks whether a `//# sourceMappingURL=` URL line snippet represents a valid trailing comment at the end of the file.
251+ */
252+ export function isTrailingSourceMapComment ( urlLine : string ) : boolean {
253+ return (
254+ extractTrailingBase64Payload ( urlLine ) !== undefined || extractTrailingUrl ( urlLine ) !== undefined
255+ ) ;
256+ }
257+
258+ /**
259+ * Resolves and loads the input sourcemap referenced in a `//# sourceMappingURL=` URL line snippet.
260+ * Supports inline base64 data URIs, local absolute file URLs, and relative/absolute filesystem paths.
261+ */
262+ export function loadInputSourceMapFromUrl (
263+ filename : string ,
264+ urlLine : string ,
265+ ) : EncodedSourceMap | undefined {
266+ // Inline base64-encoded sourcemaps can be extremely large (up to megabytes).
267+ // Parse them without regular expressions to avoid heavy backtracking and allocations.
268+ const base64Payload = extractTrailingBase64Payload ( urlLine ) ;
269+ if ( base64Payload !== undefined ) {
270+ try {
271+ return JSON . parse ( Buffer . from ( base64Payload , 'base64' ) . toString ( 'utf-8' ) ) as EncodedSourceMap ;
272+ } catch {
273+ return undefined ;
274+ }
275+ }
276+
277+ const url = extractTrailingUrl ( urlLine ) ;
278+ if ( ! url ) {
279+ return undefined ;
280+ }
281+
252282 if ( url . startsWith ( 'file://' ) ) {
253283 // Local absolute file URL scheme.
254284 try {
@@ -269,3 +299,31 @@ export function loadInputSourceMap(filename: string, code: string): EncodedSourc
269299
270300 return undefined ;
271301}
302+
303+ /**
304+ * Finds, resolves, and loads the input sourcemap referenced in the code's trailing
305+ * sourceMappingURL comment, if present. Supports inline base64 data URIs, local absolute
306+ * file URLs, and relative/absolute filesystem paths.
307+ */
308+ export function loadInputSourceMap ( filename : string , code : string ) : EncodedSourceMap | undefined {
309+ // Locate the last sourceMappingURL comment using lastIndexOf to avoid scanning
310+ // the entire file with a regular expression (significant for large files).
311+ const lastSourceMapIndex = code . lastIndexOf ( '//# sourceMappingURL=' ) ;
312+ if ( lastSourceMapIndex === - 1 ) {
313+ return undefined ;
314+ }
315+
316+ if ( lastSourceMapIndex > 0 ) {
317+ // Skip any preceding horizontal whitespace (spaces/tabs) to find the start of the line.
318+ let prevIdx = lastSourceMapIndex - 1 ;
319+ while ( prevIdx >= 0 && ( code [ prevIdx ] === ' ' || code [ prevIdx ] === '\t' ) ) {
320+ prevIdx -- ;
321+ }
322+ // Ensure the comment starts at the beginning of a line, preventing false positives within code or strings.
323+ if ( prevIdx >= 0 && code [ prevIdx ] !== '\n' && code [ prevIdx ] !== '\r' ) {
324+ return undefined ;
325+ }
326+ }
327+
328+ return loadInputSourceMapFromUrl ( filename , code . slice ( lastSourceMapIndex + 21 ) ) ;
329+ }
0 commit comments