1- import { GenMapping , maybeAddSegment , setIgnore , setSourceContent } from '@jridgewell/gen-mapping' ;
2- import { traceSegment , decodedMappings } from '@jridgewell/trace-mapping' ;
1+ import {
2+ GenMapping ,
3+ maybeAddSegment ,
4+ setIgnore ,
5+ setSourceContent ,
6+ setRangeSegment ,
7+ } from '@jridgewell/gen-mapping' ;
8+ import {
9+ traceSegment ,
10+ traceSegmentsInRange ,
11+ isRange ,
12+ decodedMappings ,
13+ decodedRangeMappings ,
14+ } from '@jridgewell/trace-mapping' ;
315
416import type { TraceMap } from '@jridgewell/trace-mapping' ;
517
@@ -10,6 +22,8 @@ export type SourceMapSegmentObject = {
1022 source : string ;
1123 content : string | null ;
1224 ignore : boolean ;
25+ isRangeMapping : boolean ;
26+ rangeMappingOffset : { line : number ; column : number } ;
1327} ;
1428
1529export type OriginalSource = {
@@ -40,8 +54,19 @@ function SegmentObject(
4054 name : string ,
4155 content : string | null ,
4256 ignore : boolean ,
57+ isRangeMapping ?: boolean ,
58+ rangeMappingOffset ?: { line : number ; column : number } ,
4359) : SourceMapSegmentObject {
44- return { source, line, column, name, content, ignore } ;
60+ return {
61+ source,
62+ line,
63+ column,
64+ name,
65+ content,
66+ ignore,
67+ isRangeMapping : isRangeMapping || false ,
68+ rangeMappingOffset : rangeMappingOffset || { line : 0 , column : 0 } ,
69+ } ;
4570}
4671
4772function Source (
@@ -105,36 +130,113 @@ export function traceMappings(tree: MapSource): GenMapping {
105130 const { sources : rootSources , map } = tree ;
106131 const rootNames = map . names ;
107132 const rootMappings = decodedMappings ( map ) ;
133+ const rootRangeMappings = decodedRangeMappings ( map ) || [ ] ;
134+
135+ // Find the next segment either in the current line or in
136+ // the next line if we're at the end and there are further lines.
137+ function nextSegment ( line : number , index : number ) {
138+ let current = index + 1 ;
139+
140+ while ( line < rootMappings . length ) {
141+ if ( current < rootMappings [ line ] . length ) {
142+ return { line, segment : rootMappings [ line ] [ current ] } ;
143+ } else {
144+ line ++ ;
145+ current = 0 ;
146+ }
147+ }
148+
149+ return null ;
150+ }
108151
109152 for ( let i = 0 ; i < rootMappings . length ; i ++ ) {
110153 const segments = rootMappings [ i ] ;
154+ const rangeMappings = rootRangeMappings [ i ] || [ ] ;
111155
112156 for ( let j = 0 ; j < segments . length ; j ++ ) {
113157 const segment = segments [ j ] ;
158+ const isRangeMapping = rangeMappings . includes ( j ) ;
114159 const genCol = segment [ 0 ] ;
115- let traced : SourceMapSegmentObject | null = SOURCELESS_MAPPING ;
116160
117- // 1-length segments only move the current generated column, there's no source information
118- // to gather from it.
119- if ( segment . length !== 1 ) {
161+ if ( segment . length === 1 || ! isRangeMapping ) {
162+ let tracedSegment : SourceMapSegmentObject | null = SOURCELESS_MAPPING ;
163+
164+ // 1-length segments only move the current generated column, there's no source information
165+ // to gather from it.
166+ if ( segment . length !== 1 ) {
167+ const source = rootSources [ segment [ 1 ] ] ;
168+
169+ tracedSegment = originalPositionFor (
170+ source ,
171+ segment [ 2 ] ,
172+ segment [ 3 ] ,
173+ segment . length === 5 ? rootNames [ segment [ 4 ] ] : '' ,
174+ ) ;
175+
176+ // If the trace is invalid, then the trace ran into a sourcemap that doesn't contain a
177+ // respective segment into an original source.
178+ if ( tracedSegment === null ) continue ;
179+ }
180+
181+ const { column, line, name, content, source, ignore } = tracedSegment ! ;
182+
183+ maybeAddSegment ( gen , i , genCol , source , line , column , name ) ;
184+ if ( source && content != null ) setSourceContent ( gen , source , content ) ;
185+ if ( ignore ) setIgnore ( gen , source , true ) ;
186+ } else {
187+ // isRangeMapping
120188 const source = rootSources [ segment [ 1 ] ] ;
121- traced = originalPositionFor (
189+
190+ // Find end segment, if none exists it's an invalid range mapping and
191+ // we will skip it.
192+ const next = nextSegment ( i , j ) ;
193+ if ( next === null ) continue ;
194+ const { line : nextSegmentLine , segment : endSegment } = next ;
195+ const rangeLineOffset = nextSegmentLine - i ;
196+ const rangeColumnOffset = endSegment [ 0 ] - segment [ 0 ] ;
197+ const endLine = segment [ 2 ] + rangeLineOffset ;
198+ const endColumn = segment [ 2 ] === endLine ? segment [ 3 ] + rangeColumnOffset : endSegment [ 0 ] ;
199+
200+ const tracedSegments = originalPositionsForRange (
122201 source ,
123202 segment [ 2 ] ,
124203 segment [ 3 ] ,
125204 segment . length === 5 ? rootNames [ segment [ 4 ] ] : '' ,
205+ endLine ,
206+ endColumn ,
207+ false ,
126208 ) ;
127209
128- // If the trace is invalid, then the trace ran into a sourcemap that doesn't contain a
129- // respective segment into an original source.
130- if ( traced == null ) continue ;
131- }
210+ if ( tracedSegments . length === 0 ) continue ;
132211
133- const { column, line, name, content, source, ignore } = traced ;
212+ for ( const tracedSegment of tracedSegments ) {
213+ const {
214+ column,
215+ line,
216+ name,
217+ content,
218+ source,
219+ ignore,
220+ isRangeMapping,
221+ rangeMappingOffset,
222+ } = tracedSegment ;
134223
135- maybeAddSegment ( gen , i , genCol , source , line , column , name ) ;
136- if ( source && content != null ) setSourceContent ( gen , source , content ) ;
137- if ( ignore ) setIgnore ( gen , source , true ) ;
224+ // The range mapping offset is the amount that we need to offset the
225+ // generated line/column from the root. We have to return this up
226+ // because originalPositionsForRange can't increment it.
227+ const genLine = i + rangeMappingOffset . line ;
228+ // If the traced segment isn't on the same line as the range start,
229+ // genCol is irrelevant
230+ const genColumn =
231+ rangeMappingOffset . line === 0
232+ ? genCol + rangeMappingOffset . column
233+ : rangeMappingOffset . column ;
234+ maybeAddSegment ( gen , genLine , genColumn , source , line , column , name , null ) ;
235+ setRangeSegment ( gen , genLine , genColumn , isRangeMapping ) ;
236+ if ( source && content != null ) setSourceContent ( gen , source , content ) ;
237+ if ( ignore ) setIgnore ( gen , source , true ) ;
238+ }
239+ }
138240 }
139241 }
140242
@@ -159,14 +261,177 @@ export function originalPositionFor(
159261
160262 // If we couldn't find a segment, then this doesn't exist in the sourcemap.
161263 if ( segment == null ) return null ;
264+ const maybeRange = isRange ( source . map , segment ) ;
265+ let startLine = 0 ; // FIXME this should be the line of the segment
266+ if ( maybeRange ) {
267+ startLine = maybeRange . line ;
268+ }
269+
162270 // 1-length segments only move the current generated column, there's no source information
163271 // to gather from it.
164272 if ( segment . length === 1 ) return SOURCELESS_MAPPING ;
165273
274+ // If the child is a range mapping, we need to offset the next lookup point by the
275+ // offset of the parent mapping into the range.
276+ let rangeMappingOffset = { line : 0 , column : 0 } ;
277+ if ( maybeRange ) {
278+ if ( startLine === line ) rangeMappingOffset = { line : 0 , column : column - segment [ 0 ] } ;
279+ else rangeMappingOffset = { line : line - startLine , column : 0 } ;
280+ }
281+
166282 return originalPositionFor (
167283 source . sources [ segment [ 1 ] ] ,
168- segment [ 2 ] ,
169- segment [ 3 ] ,
284+ segment [ 2 ] + rangeMappingOffset . line ,
285+ segment [ 3 ] + rangeMappingOffset . column ,
170286 segment . length === 5 ? source . map . names [ segment [ 4 ] ] : name ,
171287 ) ;
172288}
289+
290+ function originalPositionsForRange (
291+ source : Sources ,
292+ line : number ,
293+ column : number ,
294+ name : string ,
295+ endLine : number ,
296+ endColumn : number ,
297+ emitEndPoint : boolean ,
298+ ) : SourceMapSegmentObject [ ] {
299+ // If this is the bottom node then we just return the current range.
300+ if ( source . map === null ) {
301+ return [
302+ SegmentObject ( source . source , line , column , '' , source . content , source . ignore , true ) ,
303+ // The end point isn't always emitted, because the end point may be
304+ // a separate mapping that will be processed & translated too. We only emit this
305+ // if we need to make up a mapping because we split or clamped a range.
306+ ...( emitEndPoint
307+ ? [
308+ SegmentObject (
309+ source . source ,
310+ endLine ,
311+ endColumn ,
312+ '' ,
313+ source . content ,
314+ source . ignore ,
315+ false ,
316+ { line : endLine - line , column : endColumn - column } ,
317+ ) ,
318+ ]
319+ : [ ] ) ,
320+ ] ;
321+ }
322+
323+ // We additional trace the start position of the range to because we may need to
324+ // intersect with a range that starts before the given position, or map the start
325+ // of the range to it if there aren't any exact hits.
326+ const initialSegment = traceSegment ( source . map , line , column ) ;
327+ const segments = traceSegmentsInRange ( source . map , line , column , endLine , endColumn ) ;
328+
329+ // If tracing the start of the range hits a mapping that isn't in the segmenObjects list,
330+ // add it to the list to process first.
331+ if ( initialSegment !== null && ( segments . length === 0 || initialSegment !== segments [ 0 ] ) ) {
332+ segments . splice ( 0 , 0 , initialSegment ) ;
333+ }
334+
335+ const originalPositions = [ ] ;
336+ for ( const segment of segments ) {
337+ if ( segment == null ) continue ;
338+
339+ let startLine = 0 ; // FIXME this should be the line of the segment
340+ let childEndLine , endSegment ;
341+ const maybeRange = isRange ( source . map , segment ) ;
342+ if ( maybeRange ) {
343+ startLine = maybeRange . line ;
344+ childEndLine = maybeRange . endLine ;
345+ endSegment = maybeRange . endSegment ;
346+ }
347+
348+ // At the very beginning of a range, the child position might be behind
349+ // the start of the range. In that case we clamp the offset to 0.
350+ const rangeOffsetLine = startLine - line ;
351+ const rangeOffsetColumn = rangeOffsetLine === 0 ? Math . max ( 0 , segment [ 0 ] - column ) : segment [ 0 ] ;
352+ const rangeOffset = { line : rangeOffsetLine , column : rangeOffsetColumn } ;
353+
354+ // Sourceless mappings just have the offset added and we skip the recursive
355+ // step because there's no source to process.
356+ if ( segment . length === 1 ) {
357+ const mapping = SOURCELESS_MAPPING ;
358+ mapping . rangeMappingOffset = rangeOffset ;
359+ originalPositions . push ( mapping ) ;
360+ continue ;
361+ }
362+
363+ if ( ! maybeRange ) {
364+ const position = originalPositionFor (
365+ source . sources [ segment [ 1 ] ] ,
366+ segment [ 2 ] ,
367+ segment [ 3 ] ,
368+ segment . length === 5 ? source . map . names [ segment [ 4 ] ] : name ,
369+ ) ;
370+ if ( position !== null ) {
371+ position . rangeMappingOffset . line += rangeOffset . line ;
372+ position . rangeMappingOffset . column += rangeOffset . column ;
373+ originalPositions . push ( position ) ;
374+ }
375+ } else {
376+ // Compute the intersection of the child and parent ranges.
377+ //
378+ // line,column endLine,endColumn
379+ // Parent range |-----------------------------|
380+ // Child range |------------------------|
381+ // startLine,segment[0] childEndLine,endSegment[0]
382+ // Child mapped |------------------------|
383+ // segment[2],segment[3] endSegment[2],endSegment[3]
384+ //
385+ // For example, if segment[0] < column as in this diagram, we
386+ // need to clamp the start point to column, which maps to
387+ // segment[3] + (column - segment[0]). The end point needs to
388+ // be clamped if endSegment[3] > endColumn.
389+ const clampedStartLine = Math . max ( line , startLine ) ;
390+ const clampedStartColumn = Math . max ( column , segment [ 0 ] ) ;
391+ const clampedEndLine = Math . min ( endLine , childEndLine ! ) ;
392+ const clampedEndColumn = Math . min ( endColumn , endSegment ! [ 0 ] ) ;
393+
394+ const originalStartLine = segment [ 2 ] + ( clampedStartLine - startLine ) ;
395+ let originalStartColumn ;
396+ if ( startLine == line ) {
397+ originalStartColumn = segment [ 3 ] + ( clampedStartColumn - segment [ 0 ] ) ;
398+ } else if ( startLine > line ) {
399+ originalStartColumn = segment [ 3 ] ;
400+ } else {
401+ originalStartColumn = column ;
402+ }
403+
404+ const originalEndLine = originalStartLine + ( clampedEndLine - clampedStartLine ) ;
405+ // When the range ends on the same line, the end column is calculated from the
406+ // segment distance because the range is exclusive of the end segment.
407+ // If the range ends on a different line, we end on the end segment generated
408+ // column.
409+ const originalEndColumn =
410+ originalStartLine == originalEndLine
411+ ? originalStartColumn + ( clampedEndColumn - clampedStartColumn )
412+ : clampedEndColumn ;
413+
414+ const positions = originalPositionsForRange (
415+ source . sources [ segment [ 1 ] ] ,
416+ originalStartLine ,
417+ originalStartColumn ,
418+ segment . length === 5 ? source . map . names [ segment [ 4 ] ] : name ,
419+ originalEndLine ,
420+ originalEndColumn ,
421+ // If the range had to be clamped then we need to emit a new mapping
422+ // for the end, as no existing explicit mapping will exist at that point.
423+ // Otherwise, we should be able to rely on the original end mapping being
424+ // translated appropriately.
425+ clampedEndLine !== childEndLine || clampedEndColumn !== endSegment ! [ 0 ] ,
426+ ) ;
427+
428+ for ( const position of positions ) {
429+ position . rangeMappingOffset . line += rangeOffset . line ;
430+ position . rangeMappingOffset . column += rangeOffset . column ;
431+ }
432+ originalPositions . push ( ...positions ) ;
433+ }
434+ }
435+
436+ return originalPositions ;
437+ }
0 commit comments