• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* @internal */
2namespace ts {
3    export interface SourceMapGeneratorOptions {
4        extendedDiagnostics?: boolean;
5    }
6
7    export function createSourceMapGenerator(host: EmitHost, file: string, sourceRoot: string, sourcesDirectoryPath: string, generatorOptions: SourceMapGeneratorOptions): SourceMapGenerator {
8        const { enter, exit } = generatorOptions.extendedDiagnostics
9            ? performance.createTimer("Source Map", "beforeSourcemap", "afterSourcemap")
10            : performance.nullTimer;
11
12        // Current source map file and its index in the sources list
13        const rawSources: string[] = [];
14        const sources: string[] = [];
15        const sourceToSourceIndexMap = new Map<string, number>();
16        let sourcesContent: (string | null)[] | undefined;
17
18        const names: string[] = [];
19        let nameToNameIndexMap: ESMap<string, number> | undefined;
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 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 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 commitPendingMapping() {
214            if (!hasPending || !shouldCommitMapping()) {
215                return;
216            }
217
218            enter();
219
220            // Line/Comma delimiters
221            if (lastGeneratedLine < pendingGeneratedLine) {
222                // Emit line delimiters
223                do {
224                    mappings += ";";
225                    lastGeneratedLine++;
226                    lastGeneratedCharacter = 0;
227                }
228                while (lastGeneratedLine < pendingGeneratedLine);
229            }
230            else {
231                Debug.assertEqual(lastGeneratedLine, pendingGeneratedLine, "generatedLine cannot backtrack");
232                // Emit comma to separate the entry
233                if (hasLast) {
234                    mappings += ",";
235                }
236            }
237
238            // 1. Relative generated character
239            mappings += base64VLQFormatEncode(pendingGeneratedCharacter - lastGeneratedCharacter);
240            lastGeneratedCharacter = pendingGeneratedCharacter;
241
242            if (hasPendingSource) {
243                // 2. Relative sourceIndex
244                mappings += base64VLQFormatEncode(pendingSourceIndex - lastSourceIndex);
245                lastSourceIndex = pendingSourceIndex;
246
247                // 3. Relative source line
248                mappings += base64VLQFormatEncode(pendingSourceLine - lastSourceLine);
249                lastSourceLine = pendingSourceLine;
250
251                // 4. Relative source character
252                mappings += base64VLQFormatEncode(pendingSourceCharacter - lastSourceCharacter);
253                lastSourceCharacter = pendingSourceCharacter;
254
255                if (hasPendingName) {
256                    // 5. Relative nameIndex
257                    mappings += base64VLQFormatEncode(pendingNameIndex - lastNameIndex);
258                    lastNameIndex = pendingNameIndex;
259                }
260            }
261
262            hasLast = true;
263            exit();
264        }
265
266        function toJSON(): RawSourceMap {
267            commitPendingMapping();
268            return {
269                version: 3,
270                file,
271                sourceRoot,
272                sources,
273                names,
274                mappings,
275                sourcesContent,
276            };
277        }
278    }
279
280    // Sometimes tools can see the following line as a source mapping url comment, so we mangle it a bit (the [M])
281    const sourceMapCommentRegExp = /^\/\/[@#] source[M]appingURL=(.+)\s*$/;
282    const whitespaceOrMapCommentRegExp = /^\s*(\/\/[@#] .*)?$/;
283
284    export interface LineInfo {
285        getLineCount(): number;
286        getLineText(line: number): string;
287    }
288
289    export function getLineInfo(text: string, lineStarts: readonly number[]): LineInfo {
290        return {
291            getLineCount: () => lineStarts.length,
292            getLineText: line => text.substring(lineStarts[line], lineStarts[line + 1])
293        };
294    }
295
296    /**
297     * Tries to find the sourceMappingURL comment at the end of a file.
298     */
299    export function tryGetSourceMappingURL(lineInfo: LineInfo) {
300        for (let index = lineInfo.getLineCount() - 1; index >= 0; index--) {
301            const line = lineInfo.getLineText(index);
302            const comment = sourceMapCommentRegExp.exec(line);
303            if (comment) {
304                return comment[1];
305            }
306            // If we see a non-whitespace/map comment-like line, break, to avoid scanning up the entire file
307            else if (!line.match(whitespaceOrMapCommentRegExp)) {
308                break;
309            }
310        }
311    }
312
313    /* eslint-disable no-null/no-null */
314    function isStringOrNull(x: any) {
315        return typeof x === "string" || x === null;
316    }
317
318    export function isRawSourceMap(x: any): x is RawSourceMap {
319        return x !== null
320            && typeof x === "object"
321            && x.version === 3
322            && typeof x.file === "string"
323            && typeof x.mappings === "string"
324            && isArray(x.sources) && every(x.sources, isString)
325            && (x.sourceRoot === undefined || x.sourceRoot === null || typeof x.sourceRoot === "string")
326            && (x.sourcesContent === undefined || x.sourcesContent === null || isArray(x.sourcesContent) && every(x.sourcesContent, isStringOrNull))
327            && (x.names === undefined || x.names === null || isArray(x.names) && every(x.names, isString));
328    }
329    /* eslint-enable no-null/no-null */
330
331    export function tryParseRawSourceMap(text: string) {
332        try {
333            const parsed = JSON.parse(text);
334            if (isRawSourceMap(parsed)) {
335                return parsed;
336            }
337        }
338        catch {
339            // empty
340        }
341
342        return undefined;
343    }
344
345    export interface MappingsDecoder extends Iterator<Mapping> {
346        readonly pos: number;
347        readonly error: string | undefined;
348        readonly state: Required<Mapping>;
349    }
350
351    export interface Mapping {
352        generatedLine: number;
353        generatedCharacter: number;
354        sourceIndex?: number;
355        sourceLine?: number;
356        sourceCharacter?: number;
357        nameIndex?: number;
358    }
359
360    export interface SourceMapping extends Mapping {
361        sourceIndex: number;
362        sourceLine: number;
363        sourceCharacter: number;
364    }
365
366    export function decodeMappings(mappings: string): MappingsDecoder {
367        let done = false;
368        let pos = 0;
369        let generatedLine = 0;
370        let generatedCharacter = 0;
371        let sourceIndex = 0;
372        let sourceLine = 0;
373        let sourceCharacter = 0;
374        let nameIndex = 0;
375        let error: string | undefined;
376
377        return {
378            get pos() { return pos; },
379            get error() { return error; },
380            get state() { return captureMapping(/*hasSource*/ true, /*hasName*/ true); },
381            next() {
382                while (!done && pos < mappings.length) {
383                    const ch = mappings.charCodeAt(pos);
384                    if (ch === CharacterCodes.semicolon) {
385                        // new line
386                        generatedLine++;
387                        generatedCharacter = 0;
388                        pos++;
389                        continue;
390                    }
391
392                    if (ch === CharacterCodes.comma) {
393                        // Next entry is on same line - no action needed
394                        pos++;
395                        continue;
396                    }
397
398                    let hasSource = false;
399                    let hasName = false;
400
401                    generatedCharacter += base64VLQFormatDecode();
402                    if (hasReportedError()) return stopIterating();
403                    if (generatedCharacter < 0) return setErrorAndStopIterating("Invalid generatedCharacter found");
404
405                    if (!isSourceMappingSegmentEnd()) {
406                        hasSource = true;
407
408                        sourceIndex += base64VLQFormatDecode();
409                        if (hasReportedError()) return stopIterating();
410                        if (sourceIndex < 0) return setErrorAndStopIterating("Invalid sourceIndex found");
411                        if (isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Format: No entries after sourceIndex");
412
413                        sourceLine += base64VLQFormatDecode();
414                        if (hasReportedError()) return stopIterating();
415                        if (sourceLine < 0) return setErrorAndStopIterating("Invalid sourceLine found");
416                        if (isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Format: No entries after sourceLine");
417
418                        sourceCharacter += base64VLQFormatDecode();
419                        if (hasReportedError()) return stopIterating();
420                        if (sourceCharacter < 0) return setErrorAndStopIterating("Invalid sourceCharacter found");
421
422                        if (!isSourceMappingSegmentEnd()) {
423                            hasName = true;
424                            nameIndex += base64VLQFormatDecode();
425                            if (hasReportedError()) return stopIterating();
426                            if (nameIndex < 0) return setErrorAndStopIterating("Invalid nameIndex found");
427
428                            if (!isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Error Format: Entries after nameIndex");
429                        }
430                    }
431
432                    return { value: captureMapping(hasSource, hasName), done };
433                }
434
435                return stopIterating();
436            }
437        };
438
439        function captureMapping(hasSource: true, hasName: true): Required<Mapping>;
440        function captureMapping(hasSource: boolean, hasName: boolean): Mapping;
441        function captureMapping(hasSource: boolean, hasName: boolean): Mapping {
442            return {
443                generatedLine,
444                generatedCharacter,
445                sourceIndex: hasSource ? sourceIndex : undefined,
446                sourceLine: hasSource ? sourceLine : undefined,
447                sourceCharacter: hasSource ? sourceCharacter : undefined,
448                nameIndex: hasName ? nameIndex : undefined
449            };
450        }
451
452        function stopIterating(): { value: never, done: true } {
453            done = true;
454            return { value: undefined!, done: true };
455        }
456
457        function setError(message: string) {
458            if (error === undefined) {
459                error = message;
460            }
461        }
462
463        function setErrorAndStopIterating(message: string) {
464            setError(message);
465            return stopIterating();
466        }
467
468        function hasReportedError() {
469            return error !== undefined;
470        }
471
472        function isSourceMappingSegmentEnd() {
473            return (pos === mappings.length ||
474                mappings.charCodeAt(pos) === CharacterCodes.comma ||
475                mappings.charCodeAt(pos) === CharacterCodes.semicolon);
476        }
477
478        function base64VLQFormatDecode(): number {
479            let moreDigits = true;
480            let shiftCount = 0;
481            let value = 0;
482
483            for (; moreDigits; pos++) {
484                if (pos >= mappings.length) return setError("Error in decoding base64VLQFormatDecode, past the mapping string"), -1;
485
486                // 6 digit number
487                const currentByte = base64FormatDecode(mappings.charCodeAt(pos));
488                if (currentByte === -1) return setError("Invalid character in VLQ"), -1;
489
490                // If msb is set, we still have more bits to continue
491                moreDigits = (currentByte & 32) !== 0;
492
493                // least significant 5 bits are the next msbs in the final value.
494                value = value | ((currentByte & 31) << shiftCount);
495                shiftCount += 5;
496            }
497
498            // Least significant bit if 1 represents negative and rest of the msb is actual absolute value
499            if ((value & 1) === 0) {
500                // + number
501                value = value >> 1;
502            }
503            else {
504                // - number
505                value = value >> 1;
506                value = -value;
507            }
508
509            return value;
510        }
511    }
512
513    export function sameMapping<T extends Mapping>(left: T, right: T) {
514        return left === right
515            || left.generatedLine === right.generatedLine
516            && left.generatedCharacter === right.generatedCharacter
517            && left.sourceIndex === right.sourceIndex
518            && left.sourceLine === right.sourceLine
519            && left.sourceCharacter === right.sourceCharacter
520            && left.nameIndex === right.nameIndex;
521    }
522
523    export function isSourceMapping(mapping: Mapping): mapping is SourceMapping {
524        return mapping.sourceIndex !== undefined
525            && mapping.sourceLine !== undefined
526            && mapping.sourceCharacter !== undefined;
527    }
528
529    function base64FormatEncode(value: number) {
530        return value >= 0 && value < 26 ? CharacterCodes.A + value :
531            value >= 26 && value < 52 ? CharacterCodes.a + value - 26 :
532            value >= 52 && value < 62 ? CharacterCodes._0 + value - 52 :
533            value === 62 ? CharacterCodes.plus :
534            value === 63 ? CharacterCodes.slash :
535            Debug.fail(`${value}: not a base64 value`);
536    }
537
538    function base64FormatDecode(ch: number) {
539        return ch >= CharacterCodes.A && ch <= CharacterCodes.Z ? ch - CharacterCodes.A :
540            ch >= CharacterCodes.a && ch <= CharacterCodes.z ? ch - CharacterCodes.a + 26 :
541            ch >= CharacterCodes._0 && ch <= CharacterCodes._9 ? ch - CharacterCodes._0 + 52 :
542            ch === CharacterCodes.plus ? 62 :
543            ch === CharacterCodes.slash ? 63 :
544            -1;
545    }
546
547    function base64VLQFormatEncode(inValue: number) {
548        // Add a new least significant bit that has the sign of the value.
549        // if negative number the least significant bit that gets added to the number has value 1
550        // else least significant bit value that gets added is 0
551        // eg. -1 changes to binary : 01 [1] => 3
552        //     +1 changes to binary : 01 [0] => 2
553        if (inValue < 0) {
554            inValue = ((-inValue) << 1) + 1;
555        }
556        else {
557            inValue = inValue << 1;
558        }
559
560        // Encode 5 bits at a time starting from least significant bits
561        let encodedStr = "";
562        do {
563            let currentDigit = inValue & 31; // 11111
564            inValue = inValue >> 5;
565            if (inValue > 0) {
566                // There are still more digits to decode, set the msb (6th bit)
567                currentDigit = currentDigit | 32;
568            }
569            encodedStr = encodedStr + String.fromCharCode(base64FormatEncode(currentDigit));
570        } while (inValue > 0);
571
572        return encodedStr;
573    }
574
575    interface MappedPosition {
576        generatedPosition: number;
577        source: string | undefined;
578        sourceIndex: number | undefined;
579        sourcePosition: number | undefined;
580        nameIndex: number | undefined;
581    }
582
583    interface SourceMappedPosition extends MappedPosition {
584        source: string;
585        sourceIndex: number;
586        sourcePosition: number;
587    }
588
589    function isSourceMappedPosition(value: MappedPosition): value is SourceMappedPosition {
590        return value.sourceIndex !== undefined
591            && value.sourcePosition !== undefined;
592    }
593
594    function sameMappedPosition(left: MappedPosition, right: MappedPosition) {
595        return left.generatedPosition === right.generatedPosition
596            && left.sourceIndex === right.sourceIndex
597            && left.sourcePosition === right.sourcePosition;
598    }
599
600    function compareSourcePositions(left: SourceMappedPosition, right: SourceMappedPosition) {
601        // Compares sourcePosition without comparing sourceIndex
602        // since the mappings are grouped by sourceIndex
603        Debug.assert(left.sourceIndex === right.sourceIndex);
604        return compareValues(left.sourcePosition, right.sourcePosition);
605    }
606
607    function compareGeneratedPositions(left: MappedPosition, right: MappedPosition) {
608        return compareValues(left.generatedPosition, right.generatedPosition);
609    }
610
611    function getSourcePositionOfMapping(value: SourceMappedPosition) {
612        return value.sourcePosition;
613    }
614
615    function getGeneratedPositionOfMapping(value: MappedPosition) {
616        return value.generatedPosition;
617    }
618
619    export function createDocumentPositionMapper(host: DocumentPositionMapperHost, map: RawSourceMap, mapPath: string): DocumentPositionMapper {
620        const mapDirectory = getDirectoryPath(mapPath);
621        const sourceRoot = map.sourceRoot ? getNormalizedAbsolutePath(map.sourceRoot, mapDirectory) : mapDirectory;
622        const generatedAbsoluteFilePath = getNormalizedAbsolutePath(map.file, mapDirectory);
623        const generatedFile = host.getSourceFileLike(generatedAbsoluteFilePath);
624        const sourceFileAbsolutePaths = map.sources.map(source => getNormalizedAbsolutePath(source, sourceRoot));
625        const sourceToSourceIndexMap = new Map(sourceFileAbsolutePaths.map((source, i) => [host.getCanonicalFileName(source), i]));
626        let decodedMappings: readonly MappedPosition[] | undefined;
627        let generatedMappings: SortedReadonlyArray<MappedPosition> | undefined;
628        let sourceMappings: readonly SortedReadonlyArray<SourceMappedPosition>[] | undefined;
629
630        return {
631            getSourcePosition,
632            getGeneratedPosition
633        };
634
635        function processMapping(mapping: Mapping): MappedPosition {
636            const generatedPosition = generatedFile !== undefined
637                ? getPositionOfLineAndCharacter(generatedFile, mapping.generatedLine, mapping.generatedCharacter, /*allowEdits*/ true)
638                : -1;
639            let source: string | undefined;
640            let sourcePosition: number | undefined;
641            if (isSourceMapping(mapping)) {
642                const sourceFile = host.getSourceFileLike(sourceFileAbsolutePaths[mapping.sourceIndex]);
643                source = map.sources[mapping.sourceIndex];
644                sourcePosition = sourceFile !== undefined
645                    ? getPositionOfLineAndCharacter(sourceFile, mapping.sourceLine, mapping.sourceCharacter, /*allowEdits*/ true)
646                    : -1;
647            }
648            return {
649                generatedPosition,
650                source,
651                sourceIndex: mapping.sourceIndex,
652                sourcePosition,
653                nameIndex: mapping.nameIndex
654            };
655        }
656
657        function getDecodedMappings() {
658            if (decodedMappings === undefined) {
659                const decoder = decodeMappings(map.mappings);
660                const mappings = arrayFrom(decoder, processMapping);
661                if (decoder.error !== undefined) {
662                    if (host.log) {
663                        host.log(`Encountered error while decoding sourcemap: ${decoder.error}`);
664                    }
665                    decodedMappings = emptyArray;
666                }
667                else {
668                    decodedMappings = mappings;
669                }
670            }
671            return decodedMappings;
672        }
673
674        function getSourceMappings(sourceIndex: number) {
675            if (sourceMappings === undefined) {
676                const lists: SourceMappedPosition[][] = [];
677                for (const mapping of getDecodedMappings()) {
678                    if (!isSourceMappedPosition(mapping)) continue;
679                    let list = lists[mapping.sourceIndex];
680                    if (!list) lists[mapping.sourceIndex] = list = [];
681                    list.push(mapping);
682                }
683                sourceMappings = lists.map(list => sortAndDeduplicate<SourceMappedPosition>(list, compareSourcePositions, sameMappedPosition));
684            }
685            return sourceMappings[sourceIndex];
686        }
687
688        function getGeneratedMappings() {
689            if (generatedMappings === undefined) {
690                const list: MappedPosition[] = [];
691                for (const mapping of getDecodedMappings()) {
692                    list.push(mapping);
693                }
694                generatedMappings = sortAndDeduplicate(list, compareGeneratedPositions, sameMappedPosition);
695            }
696            return generatedMappings;
697        }
698
699        function getGeneratedPosition(loc: DocumentPosition): DocumentPosition {
700            const sourceIndex = sourceToSourceIndexMap.get(host.getCanonicalFileName(loc.fileName));
701            if (sourceIndex === undefined) return loc;
702
703            const sourceMappings = getSourceMappings(sourceIndex);
704            if (!some(sourceMappings)) return loc;
705
706            let targetIndex = binarySearchKey(sourceMappings, loc.pos, getSourcePositionOfMapping, compareValues);
707            if (targetIndex < 0) {
708                // if no exact match, closest is 2's complement of result
709                targetIndex = ~targetIndex;
710            }
711
712            const mapping = sourceMappings[targetIndex];
713            if (mapping === undefined || mapping.sourceIndex !== sourceIndex) {
714                return loc;
715            }
716
717            return { fileName: generatedAbsoluteFilePath, pos: mapping.generatedPosition }; // Closest pos
718        }
719
720        function getSourcePosition(loc: DocumentPosition): DocumentPosition {
721            const generatedMappings = getGeneratedMappings();
722            if (!some(generatedMappings)) return loc;
723
724            let targetIndex = binarySearchKey(generatedMappings, loc.pos, getGeneratedPositionOfMapping, compareValues);
725            if (targetIndex < 0) {
726                // if no exact match, closest is 2's complement of result
727                targetIndex = ~targetIndex;
728            }
729
730            const mapping = generatedMappings[targetIndex];
731            if (mapping === undefined || !isSourceMappedPosition(mapping)) {
732                return loc;
733            }
734
735            return { fileName: sourceFileAbsolutePaths[mapping.sourceIndex], pos: mapping.sourcePosition }; // Closest pos
736        }
737    }
738
739    export const identitySourceMapConsumer: DocumentPositionMapper = {
740        getSourcePosition: identity,
741        getGeneratedPosition: identity
742    };
743}
744