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