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