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