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