• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts {
2    export interface SourceMapGeneratorOptions {
3        extendedDiagnostics?: boolean;
4    }
5
6    export function createSourceMapGenerator(host: EmitHost, file: string, sourceRoot: string, sourcesDirectoryPath: string, generatorOptions: SourceMapGeneratorOptions): SourceMapGenerator {
7        const { enter, exit } = generatorOptions.extendedDiagnostics
8            ? performance.createTimer("Source Map", "beforeSourcemap", "afterSourcemap")
9            : performance.nullTimer;
10
11        // Current source map file and its index in the sources list
12        const rawSources: string[] = [];
13        const sources: string[] = [];
14        const sourceToSourceIndexMap = new Map<string, number>();
15        let sourcesContent: (string | null)[] | undefined;
16
17        const names: string[] = [];
18        let nameToNameIndexMap: ESMap<string, number> | undefined;
19        let mappings = "";
20
21        // Last recorded and encoded mappings
22        let lastGeneratedLine = 0;
23        let lastGeneratedCharacter = 0;
24        let lastSourceIndex = 0;
25        let lastSourceLine = 0;
26        let lastSourceCharacter = 0;
27        let lastNameIndex = 0;
28        let hasLast = false;
29
30        let pendingGeneratedLine = 0;
31        let pendingGeneratedCharacter = 0;
32        let pendingSourceIndex = 0;
33        let pendingSourceLine = 0;
34        let pendingSourceCharacter = 0;
35        let pendingNameIndex = 0;
36        let hasPending = false;
37        let hasPendingSource = false;
38        let hasPendingName = false;
39
40        return {
41            getSources: () => rawSources,
42            addSource,
43            setSourceContent,
44            addName,
45            addMapping,
46            appendSourceMap,
47            toJSON,
48            toString: () => JSON.stringify(toJSON())
49        };
50
51        function addSource(fileName: string) {
52            enter();
53            const source = getRelativePathToDirectoryOrUrl(sourcesDirectoryPath,
54                fileName,
55                host.getCurrentDirectory(),
56                host.getCanonicalFileName,
57                /*isAbsolutePathAnUrl*/ true);
58
59            let sourceIndex = sourceToSourceIndexMap.get(source);
60            if (sourceIndex === undefined) {
61                sourceIndex = sources.length;
62                sources.push(source);
63                rawSources.push(fileName);
64                sourceToSourceIndexMap.set(source, sourceIndex);
65            }
66            exit();
67            return sourceIndex;
68        }
69
70        /* eslint-disable boolean-trivia, no-null/no-null */
71        function setSourceContent(sourceIndex: number, content: string | null) {
72            enter();
73            if (content !== null) {
74                if (!sourcesContent) sourcesContent = [];
75                while (sourcesContent.length < sourceIndex) {
76                    sourcesContent.push(null);
77                }
78                sourcesContent[sourceIndex] = content;
79            }
80            exit();
81        }
82        /* eslint-enable boolean-trivia, no-null/no-null */
83
84        function addName(name: string) {
85            enter();
86            if (!nameToNameIndexMap) nameToNameIndexMap = new Map();
87            let nameIndex = nameToNameIndexMap.get(name);
88            if (nameIndex === undefined) {
89                nameIndex = names.length;
90                names.push(name);
91                nameToNameIndexMap.set(name, nameIndex);
92            }
93            exit();
94            return nameIndex;
95        }
96
97        function isNewGeneratedPosition(generatedLine: number, generatedCharacter: number) {
98            return !hasPending
99                || pendingGeneratedLine !== generatedLine
100                || pendingGeneratedCharacter !== generatedCharacter;
101        }
102
103        function isBacktrackingSourcePosition(sourceIndex: number | undefined, sourceLine: number | undefined, sourceCharacter: number | undefined) {
104            return sourceIndex !== undefined
105                && sourceLine !== undefined
106                && sourceCharacter !== undefined
107                && pendingSourceIndex === sourceIndex
108                && (pendingSourceLine > sourceLine
109                    || pendingSourceLine === sourceLine && pendingSourceCharacter > sourceCharacter);
110        }
111
112        function addMapping(generatedLine: number, generatedCharacter: number, sourceIndex?: number, sourceLine?: number, sourceCharacter?: number, nameIndex?: number) {
113            Debug.assert(generatedLine >= pendingGeneratedLine, "generatedLine cannot backtrack");
114            Debug.assert(generatedCharacter >= 0, "generatedCharacter cannot be negative");
115            Debug.assert(sourceIndex === undefined || sourceIndex >= 0, "sourceIndex cannot be negative");
116            Debug.assert(sourceLine === undefined || sourceLine >= 0, "sourceLine cannot be negative");
117            Debug.assert(sourceCharacter === undefined || sourceCharacter >= 0, "sourceCharacter cannot be negative");
118            enter();
119            // If this location wasn't recorded or the location in source is going backwards, record the mapping
120            if (isNewGeneratedPosition(generatedLine, generatedCharacter) ||
121                isBacktrackingSourcePosition(sourceIndex, sourceLine, sourceCharacter)) {
122                commitPendingMapping();
123                pendingGeneratedLine = generatedLine;
124                pendingGeneratedCharacter = generatedCharacter;
125                hasPendingSource = false;
126                hasPendingName = false;
127                hasPending = true;
128            }
129
130            if (sourceIndex !== undefined && sourceLine !== undefined && sourceCharacter !== undefined) {
131                pendingSourceIndex = sourceIndex;
132                pendingSourceLine = sourceLine;
133                pendingSourceCharacter = sourceCharacter;
134                hasPendingSource = true;
135                if (nameIndex !== undefined) {
136                    pendingNameIndex = nameIndex;
137                    hasPendingName = true;
138                }
139            }
140            exit();
141        }
142
143        function appendSourceMap(generatedLine: number, generatedCharacter: number, map: RawSourceMap, sourceMapPath: string, start?: LineAndCharacter, end?: LineAndCharacter) {
144            Debug.assert(generatedLine >= pendingGeneratedLine, "generatedLine cannot backtrack");
145            Debug.assert(generatedCharacter >= 0, "generatedCharacter cannot be negative");
146            enter();
147            // First, decode the old component sourcemap
148            const sourceIndexToNewSourceIndexMap: number[] = [];
149            let nameIndexToNewNameIndexMap: number[] | undefined;
150            const mappingIterator = decodeMappings(map.mappings);
151            for (let iterResult = mappingIterator.next(); !iterResult.done; iterResult = mappingIterator.next()) {
152                const raw = iterResult.value;
153                if (end && (
154                    raw.generatedLine > end.line ||
155                    (raw.generatedLine === end.line && raw.generatedCharacter > end.character))) {
156                    break;
157                }
158
159                if (start && (
160                    raw.generatedLine < start.line ||
161                    (start.line === raw.generatedLine && raw.generatedCharacter < start.character))) {
162                    continue;
163                }
164                // Then reencode all the updated mappings into the overall map
165                let newSourceIndex: number | undefined;
166                let newSourceLine: number | undefined;
167                let newSourceCharacter: number | undefined;
168                let newNameIndex: number | undefined;
169                if (raw.sourceIndex !== undefined) {
170                    newSourceIndex = sourceIndexToNewSourceIndexMap[raw.sourceIndex];
171                    if (newSourceIndex === undefined) {
172                        // Apply offsets to each position and fixup source entries
173                        const rawPath = map.sources[raw.sourceIndex];
174                        const relativePath = map.sourceRoot ? combinePaths(map.sourceRoot, rawPath) : rawPath;
175                        const combinedPath = combinePaths(getDirectoryPath(sourceMapPath), relativePath);
176                        sourceIndexToNewSourceIndexMap[raw.sourceIndex] = newSourceIndex = addSource(combinedPath);
177                        if (map.sourcesContent && typeof map.sourcesContent[raw.sourceIndex] === "string") {
178                            setSourceContent(newSourceIndex, map.sourcesContent[raw.sourceIndex]);
179                        }
180                    }
181
182                    newSourceLine = raw.sourceLine;
183                    newSourceCharacter = raw.sourceCharacter;
184                    if (map.names && raw.nameIndex !== undefined) {
185                        if (!nameIndexToNewNameIndexMap) nameIndexToNewNameIndexMap = [];
186                        newNameIndex = nameIndexToNewNameIndexMap[raw.nameIndex];
187                        if (newNameIndex === undefined) {
188                            nameIndexToNewNameIndexMap[raw.nameIndex] = newNameIndex = addName(map.names[raw.nameIndex]);
189                        }
190                    }
191                }
192
193                const rawGeneratedLine = raw.generatedLine - (start ? start.line : 0);
194                const newGeneratedLine = rawGeneratedLine + generatedLine;
195                const rawGeneratedCharacter = start && start.line === raw.generatedLine ? raw.generatedCharacter - start.character : raw.generatedCharacter;
196                const newGeneratedCharacter = rawGeneratedLine === 0 ? rawGeneratedCharacter + generatedCharacter : rawGeneratedCharacter;
197                addMapping(newGeneratedLine, newGeneratedCharacter, newSourceIndex, newSourceLine, newSourceCharacter, newNameIndex);
198            }
199            exit();
200        }
201
202        function shouldCommitMapping() {
203            return !hasLast
204                || lastGeneratedLine !== pendingGeneratedLine
205                || lastGeneratedCharacter !== pendingGeneratedCharacter
206                || lastSourceIndex !== pendingSourceIndex
207                || lastSourceLine !== pendingSourceLine
208                || lastSourceCharacter !== pendingSourceCharacter
209                || lastNameIndex !== pendingNameIndex;
210        }
211
212        function commitPendingMapping() {
213            if (!hasPending || !shouldCommitMapping()) {
214                return;
215            }
216
217            enter();
218
219            // Line/Comma delimiters
220            if (lastGeneratedLine < pendingGeneratedLine) {
221                // Emit line delimiters
222                do {
223                    mappings += ";";
224                    lastGeneratedLine++;
225                    lastGeneratedCharacter = 0;
226                }
227                while (lastGeneratedLine < pendingGeneratedLine);
228            }
229            else {
230                Debug.assertEqual(lastGeneratedLine, pendingGeneratedLine, "generatedLine cannot backtrack");
231                // Emit comma to separate the entry
232                if (hasLast) {
233                    mappings += ",";
234                }
235            }
236
237            // 1. Relative generated character
238            mappings += base64VLQFormatEncode(pendingGeneratedCharacter - lastGeneratedCharacter);
239            lastGeneratedCharacter = pendingGeneratedCharacter;
240
241            if (hasPendingSource) {
242                // 2. Relative sourceIndex
243                mappings += base64VLQFormatEncode(pendingSourceIndex - lastSourceIndex);
244                lastSourceIndex = pendingSourceIndex;
245
246                // 3. Relative source line
247                mappings += base64VLQFormatEncode(pendingSourceLine - lastSourceLine);
248                lastSourceLine = pendingSourceLine;
249
250                // 4. Relative source character
251                mappings += base64VLQFormatEncode(pendingSourceCharacter - lastSourceCharacter);
252                lastSourceCharacter = pendingSourceCharacter;
253
254                if (hasPendingName) {
255                    // 5. Relative nameIndex
256                    mappings += base64VLQFormatEncode(pendingNameIndex - lastNameIndex);
257                    lastNameIndex = pendingNameIndex;
258                }
259            }
260
261            hasLast = true;
262            exit();
263        }
264
265        function toJSON(): RawSourceMap {
266            commitPendingMapping();
267            return {
268                version: 3,
269                file,
270                sourceRoot,
271                sources,
272                names,
273                mappings,
274                sourcesContent,
275            };
276        }
277    }
278
279    // Sometimes tools can see the following line as a source mapping url comment, so we mangle it a bit (the [M])
280    const sourceMapCommentRegExp = /^\/\/[@#] source[M]appingURL=(.+)\s*$/;
281    const whitespaceOrMapCommentRegExp = /^\s*(\/\/[@#] .*)?$/;
282
283    /* @internal */
284    export interface LineInfo {
285        getLineCount(): number;
286        getLineText(line: number): string;
287    }
288
289    /* @internal */
290    export function getLineInfo(text: string, lineStarts: readonly number[]): LineInfo {
291        return {
292            getLineCount: () => lineStarts.length,
293            getLineText: line => text.substring(lineStarts[line], lineStarts[line + 1])
294        };
295    }
296
297    /**
298     * Tries to find the sourceMappingURL comment at the end of a file.
299     */
300    /* @internal */
301    export function tryGetSourceMappingURL(lineInfo: LineInfo) {
302        for (let index = lineInfo.getLineCount() - 1; index >= 0; index--) {
303            const line = lineInfo.getLineText(index);
304            const comment = sourceMapCommentRegExp.exec(line);
305            if (comment) {
306                return comment[1];
307            }
308            // If we see a non-whitespace/map comment-like line, break, to avoid scanning up the entire file
309            else if (!line.match(whitespaceOrMapCommentRegExp)) {
310                break;
311            }
312        }
313    }
314
315    /* eslint-disable no-null/no-null */
316    /* @internal */
317    function isStringOrNull(x: any) {
318        return typeof x === "string" || x === null;
319    }
320
321    /* @internal */
322    export function isRawSourceMap(x: any): x is RawSourceMap {
323        return x !== null
324            && typeof x === "object"
325            && x.version === 3
326            && typeof x.file === "string"
327            && typeof x.mappings === "string"
328            && isArray(x.sources) && every(x.sources, isString)
329            && (x.sourceRoot === undefined || x.sourceRoot === null || typeof x.sourceRoot === "string")
330            && (x.sourcesContent === undefined || x.sourcesContent === null || isArray(x.sourcesContent) && every(x.sourcesContent, isStringOrNull))
331            && (x.names === undefined || x.names === null || isArray(x.names) && every(x.names, isString));
332    }
333    /* eslint-enable no-null/no-null */
334
335    /* @internal */
336    export function tryParseRawSourceMap(text: string) {
337        try {
338            const parsed = JSON.parse(text);
339            if (isRawSourceMap(parsed)) {
340                return parsed;
341            }
342        }
343        catch {
344            // empty
345        }
346
347        return undefined;
348    }
349
350    /* @internal */
351    export interface MappingsDecoder extends Iterator<Mapping> {
352        readonly pos: number;
353        readonly error: string | undefined;
354        readonly state: Required<Mapping>;
355    }
356
357    /* @internal */
358    export interface Mapping {
359        generatedLine: number;
360        generatedCharacter: number;
361        sourceIndex?: number;
362        sourceLine?: number;
363        sourceCharacter?: number;
364        nameIndex?: number;
365    }
366
367    /* @internal */
368    export interface SourceMapping extends Mapping {
369        sourceIndex: number;
370        sourceLine: number;
371        sourceCharacter: number;
372    }
373
374    /* @internal */
375    export function decodeMappings(mappings: string): MappingsDecoder {
376        let done = false;
377        let pos = 0;
378        let generatedLine = 0;
379        let generatedCharacter = 0;
380        let sourceIndex = 0;
381        let sourceLine = 0;
382        let sourceCharacter = 0;
383        let nameIndex = 0;
384        let error: string | undefined;
385
386        return {
387            get pos() { return pos; },
388            get error() { return error; },
389            get state() { return captureMapping(/*hasSource*/ true, /*hasName*/ true); },
390            next() {
391                while (!done && pos < mappings.length) {
392                    const ch = mappings.charCodeAt(pos);
393                    if (ch === CharacterCodes.semicolon) {
394                        // new line
395                        generatedLine++;
396                        generatedCharacter = 0;
397                        pos++;
398                        continue;
399                    }
400
401                    if (ch === CharacterCodes.comma) {
402                        // Next entry is on same line - no action needed
403                        pos++;
404                        continue;
405                    }
406
407                    let hasSource = false;
408                    let hasName = false;
409
410                    generatedCharacter += base64VLQFormatDecode();
411                    if (hasReportedError()) return stopIterating();
412                    if (generatedCharacter < 0) return setErrorAndStopIterating("Invalid generatedCharacter found");
413
414                    if (!isSourceMappingSegmentEnd()) {
415                        hasSource = true;
416
417                        sourceIndex += base64VLQFormatDecode();
418                        if (hasReportedError()) return stopIterating();
419                        if (sourceIndex < 0) return setErrorAndStopIterating("Invalid sourceIndex found");
420                        if (isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Format: No entries after sourceIndex");
421
422                        sourceLine += base64VLQFormatDecode();
423                        if (hasReportedError()) return stopIterating();
424                        if (sourceLine < 0) return setErrorAndStopIterating("Invalid sourceLine found");
425                        if (isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Format: No entries after sourceLine");
426
427                        sourceCharacter += base64VLQFormatDecode();
428                        if (hasReportedError()) return stopIterating();
429                        if (sourceCharacter < 0) return setErrorAndStopIterating("Invalid sourceCharacter found");
430
431                        if (!isSourceMappingSegmentEnd()) {
432                            hasName = true;
433                            nameIndex += base64VLQFormatDecode();
434                            if (hasReportedError()) return stopIterating();
435                            if (nameIndex < 0) return setErrorAndStopIterating("Invalid nameIndex found");
436
437                            if (!isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Error Format: Entries after nameIndex");
438                        }
439                    }
440
441                    return { value: captureMapping(hasSource, hasName), done };
442                }
443
444                return stopIterating();
445            }
446        };
447
448        function captureMapping(hasSource: true, hasName: true): Required<Mapping>;
449        function captureMapping(hasSource: boolean, hasName: boolean): Mapping;
450        function captureMapping(hasSource: boolean, hasName: boolean): Mapping {
451            return {
452                generatedLine,
453                generatedCharacter,
454                sourceIndex: hasSource ? sourceIndex : undefined,
455                sourceLine: hasSource ? sourceLine : undefined,
456                sourceCharacter: hasSource ? sourceCharacter : undefined,
457                nameIndex: hasName ? nameIndex : undefined
458            };
459        }
460
461        function stopIterating(): { value: never, done: true } {
462            done = true;
463            return { value: undefined!, done: true };
464        }
465
466        function setError(message: string) {
467            if (error === undefined) {
468                error = message;
469            }
470        }
471
472        function setErrorAndStopIterating(message: string) {
473            setError(message);
474            return stopIterating();
475        }
476
477        function hasReportedError() {
478            return error !== undefined;
479        }
480
481        function isSourceMappingSegmentEnd() {
482            return (pos === mappings.length ||
483                mappings.charCodeAt(pos) === CharacterCodes.comma ||
484                mappings.charCodeAt(pos) === CharacterCodes.semicolon);
485        }
486
487        function base64VLQFormatDecode(): number {
488            let moreDigits = true;
489            let shiftCount = 0;
490            let value = 0;
491
492            for (; moreDigits; pos++) {
493                if (pos >= mappings.length) return setError("Error in decoding base64VLQFormatDecode, past the mapping string"), -1;
494
495                // 6 digit number
496                const currentByte = base64FormatDecode(mappings.charCodeAt(pos));
497                if (currentByte === -1) return setError("Invalid character in VLQ"), -1;
498
499                // If msb is set, we still have more bits to continue
500                moreDigits = (currentByte & 32) !== 0;
501
502                // least significant 5 bits are the next msbs in the final value.
503                value = value | ((currentByte & 31) << shiftCount);
504                shiftCount += 5;
505            }
506
507            // Least significant bit if 1 represents negative and rest of the msb is actual absolute value
508            if ((value & 1) === 0) {
509                // + number
510                value = value >> 1;
511            }
512            else {
513                // - number
514                value = value >> 1;
515                value = -value;
516            }
517
518            return value;
519        }
520    }
521
522    /* @internal */
523    export function sameMapping<T extends Mapping>(left: T, right: T) {
524        return left === right
525            || left.generatedLine === right.generatedLine
526            && left.generatedCharacter === right.generatedCharacter
527            && left.sourceIndex === right.sourceIndex
528            && left.sourceLine === right.sourceLine
529            && left.sourceCharacter === right.sourceCharacter
530            && left.nameIndex === right.nameIndex;
531    }
532
533    /* @internal */
534    export function isSourceMapping(mapping: Mapping): mapping is SourceMapping {
535        return mapping.sourceIndex !== undefined
536            && mapping.sourceLine !== undefined
537            && mapping.sourceCharacter !== undefined;
538    }
539
540    function base64FormatEncode(value: number) {
541        return value >= 0 && value < 26 ? CharacterCodes.A + value :
542            value >= 26 && value < 52 ? CharacterCodes.a + value - 26 :
543            value >= 52 && value < 62 ? CharacterCodes._0 + value - 52 :
544            value === 62 ? CharacterCodes.plus :
545            value === 63 ? CharacterCodes.slash :
546            Debug.fail(`${value}: not a base64 value`);
547    }
548
549    function base64FormatDecode(ch: number) {
550        return ch >= CharacterCodes.A && ch <= CharacterCodes.Z ? ch - CharacterCodes.A :
551            ch >= CharacterCodes.a && ch <= CharacterCodes.z ? ch - CharacterCodes.a + 26 :
552            ch >= CharacterCodes._0 && ch <= CharacterCodes._9 ? ch - CharacterCodes._0 + 52 :
553            ch === CharacterCodes.plus ? 62 :
554            ch === CharacterCodes.slash ? 63 :
555            -1;
556    }
557
558    function base64VLQFormatEncode(inValue: number) {
559        // Add a new least significant bit that has the sign of the value.
560        // if negative number the least significant bit that gets added to the number has value 1
561        // else least significant bit value that gets added is 0
562        // eg. -1 changes to binary : 01 [1] => 3
563        //     +1 changes to binary : 01 [0] => 2
564        if (inValue < 0) {
565            inValue = ((-inValue) << 1) + 1;
566        }
567        else {
568            inValue = inValue << 1;
569        }
570
571        // Encode 5 bits at a time starting from least significant bits
572        let encodedStr = "";
573        do {
574            let currentDigit = inValue & 31; // 11111
575            inValue = inValue >> 5;
576            if (inValue > 0) {
577                // There are still more digits to decode, set the msb (6th bit)
578                currentDigit = currentDigit | 32;
579            }
580            encodedStr = encodedStr + String.fromCharCode(base64FormatEncode(currentDigit));
581        } while (inValue > 0);
582
583        return encodedStr;
584    }
585
586    interface MappedPosition {
587        generatedPosition: number;
588        source: string | undefined;
589        sourceIndex: number | undefined;
590        sourcePosition: number | undefined;
591        nameIndex: number | undefined;
592    }
593
594    interface SourceMappedPosition extends MappedPosition {
595        source: string;
596        sourceIndex: number;
597        sourcePosition: number;
598    }
599
600    function isSourceMappedPosition(value: MappedPosition): value is SourceMappedPosition {
601        return value.sourceIndex !== undefined
602            && value.sourcePosition !== undefined;
603    }
604
605    function sameMappedPosition(left: MappedPosition, right: MappedPosition) {
606        return left.generatedPosition === right.generatedPosition
607            && left.sourceIndex === right.sourceIndex
608            && left.sourcePosition === right.sourcePosition;
609    }
610
611    function compareSourcePositions(left: SourceMappedPosition, right: SourceMappedPosition) {
612        // Compares sourcePosition without comparing sourceIndex
613        // since the mappings are grouped by sourceIndex
614        Debug.assert(left.sourceIndex === right.sourceIndex);
615        return compareValues(left.sourcePosition, right.sourcePosition);
616    }
617
618    function compareGeneratedPositions(left: MappedPosition, right: MappedPosition) {
619        return compareValues(left.generatedPosition, right.generatedPosition);
620    }
621
622    function getSourcePositionOfMapping(value: SourceMappedPosition) {
623        return value.sourcePosition;
624    }
625
626    function getGeneratedPositionOfMapping(value: MappedPosition) {
627        return value.generatedPosition;
628    }
629
630    /* @internal */
631    export function createDocumentPositionMapper(host: DocumentPositionMapperHost, map: RawSourceMap, mapPath: string): DocumentPositionMapper {
632        const mapDirectory = getDirectoryPath(mapPath);
633        const sourceRoot = map.sourceRoot ? getNormalizedAbsolutePath(map.sourceRoot, mapDirectory) : mapDirectory;
634        const generatedAbsoluteFilePath = getNormalizedAbsolutePath(map.file, mapDirectory);
635        const generatedFile = host.getSourceFileLike(generatedAbsoluteFilePath);
636        const sourceFileAbsolutePaths = map.sources.map(source => getNormalizedAbsolutePath(source, sourceRoot));
637        const sourceToSourceIndexMap = new Map(sourceFileAbsolutePaths.map((source, i) => [host.getCanonicalFileName(source), i]));
638        let decodedMappings: readonly MappedPosition[] | undefined;
639        let generatedMappings: SortedReadonlyArray<MappedPosition> | undefined;
640        let sourceMappings: readonly SortedReadonlyArray<SourceMappedPosition>[] | undefined;
641
642        return {
643            getSourcePosition,
644            getGeneratedPosition
645        };
646
647        function processMapping(mapping: Mapping): MappedPosition {
648            const generatedPosition = generatedFile !== undefined
649                ? getPositionOfLineAndCharacter(generatedFile, mapping.generatedLine, mapping.generatedCharacter, /*allowEdits*/ true)
650                : -1;
651            let source: string | undefined;
652            let sourcePosition: number | undefined;
653            if (isSourceMapping(mapping)) {
654                const sourceFile = host.getSourceFileLike(sourceFileAbsolutePaths[mapping.sourceIndex]);
655                source = map.sources[mapping.sourceIndex];
656                sourcePosition = sourceFile !== undefined
657                    ? getPositionOfLineAndCharacter(sourceFile, mapping.sourceLine, mapping.sourceCharacter, /*allowEdits*/ true)
658                    : -1;
659            }
660            return {
661                generatedPosition,
662                source,
663                sourceIndex: mapping.sourceIndex,
664                sourcePosition,
665                nameIndex: mapping.nameIndex
666            };
667        }
668
669        function getDecodedMappings() {
670            if (decodedMappings === undefined) {
671                const decoder = decodeMappings(map.mappings);
672                const mappings = arrayFrom(decoder, processMapping);
673                if (decoder.error !== undefined) {
674                    if (host.log) {
675                        host.log(`Encountered error while decoding sourcemap: ${decoder.error}`);
676                    }
677                    decodedMappings = emptyArray;
678                }
679                else {
680                    decodedMappings = mappings;
681                }
682            }
683            return decodedMappings;
684        }
685
686        function getSourceMappings(sourceIndex: number) {
687            if (sourceMappings === undefined) {
688                const lists: SourceMappedPosition[][] = [];
689                for (const mapping of getDecodedMappings()) {
690                    if (!isSourceMappedPosition(mapping)) continue;
691                    let list = lists[mapping.sourceIndex];
692                    if (!list) lists[mapping.sourceIndex] = list = [];
693                    list.push(mapping);
694                }
695                sourceMappings = lists.map(list => sortAndDeduplicate<SourceMappedPosition>(list, compareSourcePositions, sameMappedPosition));
696            }
697            return sourceMappings[sourceIndex];
698        }
699
700        function getGeneratedMappings() {
701            if (generatedMappings === undefined) {
702                const list: MappedPosition[] = [];
703                for (const mapping of getDecodedMappings()) {
704                    list.push(mapping);
705                }
706                generatedMappings = sortAndDeduplicate(list, compareGeneratedPositions, sameMappedPosition);
707            }
708            return generatedMappings;
709        }
710
711        function getGeneratedPosition(loc: DocumentPosition): DocumentPosition {
712            const sourceIndex = sourceToSourceIndexMap.get(host.getCanonicalFileName(loc.fileName));
713            if (sourceIndex === undefined) return loc;
714
715            const sourceMappings = getSourceMappings(sourceIndex);
716            if (!some(sourceMappings)) return loc;
717
718            let targetIndex = binarySearchKey(sourceMappings, loc.pos, getSourcePositionOfMapping, compareValues);
719            if (targetIndex < 0) {
720                // if no exact match, closest is 2's complement of result
721                targetIndex = ~targetIndex;
722            }
723
724            const mapping = sourceMappings[targetIndex];
725            if (mapping === undefined || mapping.sourceIndex !== sourceIndex) {
726                return loc;
727            }
728
729            return { fileName: generatedAbsoluteFilePath, pos: mapping.generatedPosition }; // Closest pos
730        }
731
732        function getSourcePosition(loc: DocumentPosition): DocumentPosition {
733            const generatedMappings = getGeneratedMappings();
734            if (!some(generatedMappings)) return loc;
735
736            let targetIndex = binarySearchKey(generatedMappings, loc.pos, getGeneratedPositionOfMapping, compareValues);
737            if (targetIndex < 0) {
738                // if no exact match, closest is 2's complement of result
739                targetIndex = ~targetIndex;
740            }
741
742            const mapping = generatedMappings[targetIndex];
743            if (mapping === undefined || !isSourceMappedPosition(mapping)) {
744                return loc;
745            }
746
747            return { fileName: sourceFileAbsolutePaths[mapping.sourceIndex], pos: mapping.sourcePosition }; // Closest pos
748        }
749    }
750
751    /* @internal */
752    export const identitySourceMapConsumer: DocumentPositionMapper = {
753        getSourcePosition: identity,
754        getGeneratedPosition: identity
755    };
756}
757