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