@ -1,39 +1,45 @@
import { encode } from "sourcemap-codec" ;
type MappingSegment = [ number ] | [ number , number , number , number ] | [ number , number , number , number , number ]
type MappingSegment =
| [ number ]
| [ number , number , number , number ]
| [ number , number , number , number , number ] ;
type SourceMappings = {
sources : string [ ] ;
names : string [ ] ;
mappings : MappingSegment [ ] [ ] ;
}
} ;
type SourceLocation = {
line : number ;
column : number ;
}
} ;
function get_end_location ( s : string ) : SourceLocation {
const parts = s . split ( '\n' ) ;
const parts = s . split ( "\n" ) ;
return {
line : parts.length - 1 ,
column : parts [ parts . length - 1 ] . length - 1
column : parts [ parts . length - 1 ] . length - 1 ,
} ;
}
export function offset_source_location ( offset : SourceLocation , map : SourceMappings ) : SourceMappings {
const new_mappings = map . mappings . map ( line = > line . map ( seg = > {
export function offset_source_location (
offset : SourceLocation ,
map : SourceMappings
) : SourceMappings {
const new_mappings = map . mappings . map ( ( line ) = >
line . map ( ( seg ) = > {
if ( seg . length < 3 ) return seg ;
const new_seg = seg . slice ( ) as MappingSegment ;
new_seg [ 2 ] = new_seg [ 2 ] + offset . line ;
return new_seg ;
} ) ) ;
} )
) ;
// first line has column altered
// column changed in first line
if ( new_mappings . length > 0 ) {
new_mappings [ 0 ] = new_mappings [ 0 ] . map ( seg = > {
new_mappings [ 0 ] = new_mappings [ 0 ] . map ( ( seg ) = > {
if ( seg . length < 4 ) return seg ;
const newSeg = seg . slice ( ) as MappingSegment ;
newSeg [ 3 ] = newSeg [ 3 ] + offset . column ;
@ -43,13 +49,14 @@ export function offset_source_location(offset: SourceLocation, map: SourceMappin
return {
sources : map.sources ,
mappings : new_mappings
mappings : new_mappings ,
} as SourceMappings ;
}
function merge_tables < T > ( original : T [ ] , extended : T [ ] ) : { table : T [ ] ; new_idx : number [ ] } {
function merge_tables < T > (
original : T [ ] ,
extended : T [ ]
) : { table : T [ ] ; new_idx : number [ ] } {
const table = original . slice ( ) ;
const new_idx = [ ] ;
for ( let j = 0 ; j < original . length ; j ++ ) {
@ -65,13 +72,11 @@ function merge_tables<T>( original: T[], extended: T[]): { table: T[]; new_idx:
return { table , new_idx } ;
}
export class GeneratedStringWithMap {
readonly generated : string ;
readonly map : SourceMappings ;
constructor ( generated : string , map : SourceMappings ) {
constructor ( generated : string , map : SourceMappings ) {
this . generated = generated ;
this . map = map ;
}
@ -81,7 +86,7 @@ export class GeneratedStringWithMap {
version : 3 ,
sources : this.map.sources ,
names : [ ] ,
mappings : encode ( this . map . mappings as any )
mappings : encode ( this . map . mappings as any ) ,
} ;
}
@ -90,17 +95,33 @@ export class GeneratedStringWithMap {
if ( this . generated . length == 0 ) return other ;
if ( other . generated . length == 0 ) return this ;
//combine sources
const { table : new_sources , new_idx : other_source_idx } = merge_tables ( this . map . sources , other . map . sources ) ;
const { table : new_names , new_idx : other_name_idx } = merge_tables ( this . map . names , other . map . names ) ;
//update source and name references in segments
const other_mappings = other . map . mappings . map ( line = > line . map ( seg = > {
//to reduce allocations, we only return a new segment if a value has changed
// combine sources
const {
table : new_sources ,
new_idx : other_source_idx
} = merge_tables (
this . map . sources ,
other . map . sources
) ;
const {
table : new_names ,
new_idx : other_name_idx
} = merge_tables (
this . map . names ,
other . map . names
) ;
// update source refs and name refs in segments
const other_mappings = other . map . mappings . map ( ( line ) = >
line . map ( ( seg ) = > {
// to reduce allocations,
// we only return a new segment if a value has changed
if (
( seg . length > 1 && other_source_idx [ seg [ 1 ] ] != seg [ 1 ] ) // has source idx that has been updated
|| ( seg . length == 5 && other_name_idx [ seg [ 4 ] ] != seg [ 4 ] ) ) // has name idx that has been updated
{
// new source idx
( seg . length > 1 && other_source_idx [ seg [ 1 ] ] != seg [ 1 ] ) ||
// new name idx
( seg . length == 5 && other_name_idx [ seg [ 4 ] ] != seg [ 4 ] )
) {
const new_seg = seg . slice ( ) as MappingSegment ;
new_seg [ 1 ] = other_source_idx [ seg [ 1 ] ] ;
if ( seg . length == 5 ) {
@ -110,44 +131,58 @@ export class GeneratedStringWithMap {
} else {
return seg ;
}
} ) ) ;
} )
) ;
// combine the mappings
// combine the mappings
let new_mappings = this . map . mappings . slice ( ) ;
//shift the first line of the second mapping by the number of columns in the last line of the first
// shift the first line of the second mapping
// by the number of columns in the last line of the first mapping
const end = get_end_location ( this . generated ) ;
const col_offset = end . column + 1 ;
const first_line = other_mappings . length == 0 ? [ ] : other_mappings [ 0 ] . map ( seg = > {
const first_line =
other_mappings . length == 0
? [ ]
: other_mappings [ 0 ] . map ( ( seg ) = > {
const new_seg = seg . slice ( ) as MappingSegment ;
new_seg [ 0 ] = seg [ 0 ] + col_offset ;
return new_seg ;
} ) ;
new_mappings [ new_mappings . length - 1 ] = new_mappings [ new_mappings . length - 1 ] . concat ( first_line ) ;
new_mappings [ new_mappings . length - 1 ] = new_mappings [
new_mappings . length - 1
] . concat ( first_line ) ;
//the rest don't need modification and can just be appended
new_mappings = new_mappings . concat ( other_mappings . slice ( 1 ) as MappingSegment [ ] [ ] ) ;
// the rest don't need modification and can just be appended
new_mappings = new_mappings . concat (
other_mappings . slice ( 1 ) as MappingSegment [ ] [ ]
) ;
return new GeneratedStringWithMap ( this . generated + other . generated , {
return new GeneratedStringWithMap (
this . generated + other . generated , {
sources : new_sources ,
names : new_names ,
mappings : new_mappings
mappings : new_mappings ,
} ) ;
}
static from_generated ( generated : string , map? : SourceMappings ) : GeneratedStringWithMap {
static from_generated (
generated : string ,
map? : SourceMappings
) : GeneratedStringWithMap {
if ( map ) return new GeneratedStringWithMap ( generated , map ) ;
const replacement_map : SourceMappings = {
names : [ ] ,
sources : [ ] ,
mappings : [ ]
mappings : [ ] ,
} ;
if ( generated . length == 0 ) return new GeneratedStringWithMap ( generated , replacement_map ) ;
if ( generated . length == 0 )
return new GeneratedStringWithMap ( generated , replacement_map ) ;
// we generate a mapping where the source was overwritten by the generated
// we generate a mapping
// where the source was overwritten by the generated
const end = get_end_location ( generated ) ;
for ( let i = 0 ; i <= end . line ; i ++ ) {
replacement_map . mappings . push ( [ ] ) ; // unmapped line
@ -156,25 +191,36 @@ export class GeneratedStringWithMap {
return new GeneratedStringWithMap ( generated , replacement_map ) ;
}
static from_source ( source_file : string , source : string , offset_in_source? : SourceLocation ) : GeneratedStringWithMap {
static from_source (
source_file : string ,
source : string ,
offset_in_source? : SourceLocation
) : GeneratedStringWithMap {
const offset = offset_in_source || { line : 0 , column : 0 } ;
const map : SourceMappings = {
names : [ ] ,
sources : [ source_file ] ,
mappings : [ ]
sources : [ source_file ] ,
mappings : [ ] ,
} ;
if ( source . length == 0 ) return new GeneratedStringWithMap ( source , map ) ;
// we create a high resolution identity map here, we know that it will eventually be
// merged with svelte's map, at which stage the resolution will decrease.
const lines = source . split ( '\n' ) ;
// we create a high resolution identity map here,
// we know that it will eventually be merged with svelte's map,
// at which stage the resolution will decrease.
const lines = source . split ( "\n" ) ;
let pos = 0 ;
const identity_map = lines . map ( ( line , line_idx ) = > {
const segs = line . split ( /([^\d\w\s]|\s+)/g ) . filter ( x = > x !== "" ) . map ( s = > {
const seg : MappingSegment = [ pos , 0 , offset . line + line_idx , pos + ( line_idx == 0 ? offset.column : 0 ) ] ;
const segs = line
. split ( /([^\d\w\s]|\s+)/g )
. filter ( ( x ) = > x !== "" )
. map ( ( s ) = > {
const seg : MappingSegment = [
pos ,
0 ,
offset . line + line_idx ,
pos + ( line_idx == 0 ? offset.column : 0 ) ,
] ;
pos = pos + s . length ;
return seg ;
} ) ;