@@ -50,21 +50,27 @@ export class SourceFile {
50
50
* Render the raw source map generated from the flattened mappings.
51
51
*/
52
52
renderFlattenedSourceMap ( ) : RawSourceMap {
53
- const sources : SourceFile [ ] = [ ] ;
54
- const names : string [ ] = [ ] ;
55
-
53
+ const sources = new IndexedMap < string , string > ( ) ;
54
+ const names = new IndexedSet < string > ( ) ;
56
55
const mappings : SourceMapMappings = [ ] ;
56
+ const sourcePathDir = this . fs . dirname ( this . sourcePath ) ;
57
+ // Computing the relative path can be expensive, and we are likely to have the same path for
58
+ // many (if not all!) mappings.
59
+ const relativeSourcePathCache =
60
+ new Cache < string , string > ( input => this . fs . relative ( sourcePathDir , input ) ) ;
57
61
58
62
for ( const mapping of this . flattenedMappings ) {
59
- const sourceIndex = findIndexOrAdd ( sources , mapping . originalSource ) ;
63
+ const sourceIndex = sources . set (
64
+ relativeSourcePathCache . get ( mapping . originalSource . sourcePath ) ,
65
+ mapping . originalSource . contents ) ;
60
66
const mappingArray : SourceMapSegment = [
61
67
mapping . generatedSegment . column ,
62
68
sourceIndex ,
63
69
mapping . originalSegment . line ,
64
70
mapping . originalSegment . column ,
65
71
] ;
66
72
if ( mapping . name !== undefined ) {
67
- const nameIndex = findIndexOrAdd ( names , mapping . name ) ;
73
+ const nameIndex = names . add ( mapping . name ) ;
68
74
mappingArray . push ( nameIndex ) ;
69
75
}
70
76
@@ -77,14 +83,13 @@ export class SourceFile {
77
83
mappings [ line ] . push ( mappingArray ) ;
78
84
}
79
85
80
- const sourcePathDir = this . fs . dirname ( this . sourcePath ) ;
81
86
const sourceMap : RawSourceMap = {
82
87
version : 3 ,
83
88
file : this . fs . relative ( sourcePathDir , this . sourcePath ) ,
84
- sources : sources . map ( sf => this . fs . relative ( sourcePathDir , sf . sourcePath ) ) ,
85
- names,
89
+ sources : sources . keys ,
90
+ names : names . values ,
86
91
mappings : encode ( mappings ) ,
87
- sourcesContent : sources . map ( sf => sf . contents ) ,
92
+ sourcesContent : sources . values ,
88
93
} ;
89
94
return sourceMap ;
90
95
}
@@ -259,23 +264,6 @@ export interface Mapping {
259
264
readonly name ?: string ;
260
265
}
261
266
262
- /**
263
- * Find the index of `item` in the `items` array.
264
- * If it is not found, then push `item` to the end of the array and return its new index.
265
- *
266
- * @param items the collection in which to look for `item`.
267
- * @param item the item to look for.
268
- * @returns the index of the `item` in the `items` array.
269
- */
270
- function findIndexOrAdd < T > ( items : T [ ] , item : T ) : number {
271
- const itemIndex = items . indexOf ( item ) ;
272
- if ( itemIndex > - 1 ) {
273
- return itemIndex ;
274
- } else {
275
- items . push ( item ) ;
276
- return items . length - 1 ;
277
- }
278
- }
279
267
280
268
281
269
/**
@@ -448,3 +436,96 @@ export function computeStartOfLinePositions(str: string) {
448
436
function computeLineLengths ( str : string ) : number [ ] {
449
437
return ( str . split ( / \n / ) ) . map ( s => s . length ) ;
450
438
}
439
+
440
+ /**
441
+ * A collection of mappings between `keys` and `values` stored in the order in which the keys are
442
+ * first seen.
443
+ *
444
+ * The difference between this and a standard `Map` is that when you add a key-value pair the index
445
+ * of the `key` is returned.
446
+ */
447
+ class IndexedMap < K , V > {
448
+ private map = new Map < K , number > ( ) ;
449
+
450
+ /**
451
+ * An array of keys added to this map.
452
+ *
453
+ * This array is guaranteed to be in the order of the first time the key was added to the map.
454
+ */
455
+ readonly keys : K [ ] = [ ] ;
456
+
457
+ /**
458
+ * An array of values added to this map.
459
+ *
460
+ * This array is guaranteed to be in the order of the first time the associated key was added to
461
+ * the map.
462
+ */
463
+ readonly values : V [ ] = [ ] ;
464
+
465
+ /**
466
+ * Associate the `value` with the `key` and return the index of the key in the collection.
467
+ *
468
+ * If the `key` already exists then the `value` is not set and the index of that `key` is
469
+ * returned; otherwise the `key` and `value` are stored and the index of the new `key` is
470
+ * returned.
471
+ *
472
+ * @param key the key to associated with the `value`.
473
+ * @param value the value to associated with the `key`.
474
+ * @returns the index of the `key` in the `keys` array.
475
+ */
476
+ set ( key : K , value : V ) : number {
477
+ if ( this . map . has ( key ) ) {
478
+ return this . map . get ( key ) ! ;
479
+ }
480
+ const index = this . values . push ( value ) - 1 ;
481
+ this . keys . push ( key ) ;
482
+ this . map . set ( key , index ) ;
483
+ return index ;
484
+ }
485
+ }
486
+
487
+ /**
488
+ * A collection of `values` stored in the order in which they were added.
489
+ *
490
+ * The difference between this and a standard `Set` is that when you add a value the index of that
491
+ * item is returned.
492
+ */
493
+ class IndexedSet < V > {
494
+ private map = new Map < V , number > ( ) ;
495
+
496
+ /**
497
+ * An array of values added to this set.
498
+ * This array is guaranteed to be in the order of the first time the value was added to the set.
499
+ */
500
+ readonly values : V [ ] = [ ] ;
501
+
502
+ /**
503
+ * Add the `value` to the `values` array, if it doesn't already exist; returning the index of the
504
+ * `value` in the `values` array.
505
+ *
506
+ * If the `value` already exists then the index of that `value` is returned, otherwise the new
507
+ * `value` is stored and the new index returned.
508
+ *
509
+ * @param value the value to add to the set.
510
+ * @returns the index of the `value` in the `values` array.
511
+ */
512
+ add ( value : V ) : number {
513
+ if ( this . map . has ( value ) ) {
514
+ return this . map . get ( value ) ! ;
515
+ }
516
+ const index = this . values . push ( value ) - 1 ;
517
+ this . map . set ( value , index ) ;
518
+ return index ;
519
+ }
520
+ }
521
+
522
+ class Cache < Input , Cached > {
523
+ private map = new Map < Input , Cached > ( ) ;
524
+ constructor ( private computeFn : ( input : Input ) => Cached ) { }
525
+ get ( input : Input ) : Cached {
526
+ if ( ! this . map . has ( input ) ) {
527
+ this . map . set ( input , this . computeFn ( input ) ) ;
528
+ }
529
+ return this . map . get ( input ) ! ;
530
+ }
531
+ }
0 commit comments