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