utils/node_modules/@microsoft/api-extractor/lib/collector/SourceMapper.js

195 lines
9.3 KiB
JavaScript
Raw Normal View History

2024-02-07 01:33:07 -05:00
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SourceMapper = void 0;
const path = __importStar(require("path"));
const source_map_1 = require("source-map");
const node_core_library_1 = require("@rushstack/node-core-library");
class SourceMapper {
constructor() {
// Map from .d.ts file path --> ISourceMap if a source map was found, or null if not found
this._sourceMapByFilePath = new Map();
// Cache the FileSystem.exists() result for mapped .ts files
this._originalFileInfoByPath = new Map();
}
/**
* Given a `.d.ts` source file and a specific position within the file, return the corresponding
* `ISourceLocation`.
*/
getSourceLocation(options) {
const lineAndCharacter = options.sourceFile.getLineAndCharacterOfPosition(options.pos);
const sourceLocation = {
sourceFilePath: options.sourceFile.fileName,
sourceFileLine: lineAndCharacter.line + 1,
sourceFileColumn: lineAndCharacter.character + 1
};
if (options.useDtsLocation) {
return sourceLocation;
}
const mappedSourceLocation = this._getMappedSourceLocation(sourceLocation);
return mappedSourceLocation || sourceLocation;
}
_getMappedSourceLocation(sourceLocation) {
const { sourceFilePath, sourceFileLine, sourceFileColumn } = sourceLocation;
if (!node_core_library_1.FileSystem.exists(sourceFilePath)) {
// Sanity check
throw new node_core_library_1.InternalError('The referenced path was not found: ' + sourceFilePath);
}
const sourceMap = this._getSourceMap(sourceFilePath);
if (!sourceMap)
return;
const nearestMappingItem = SourceMapper._findNearestMappingItem(sourceMap.mappingItems, {
line: sourceFileLine,
column: sourceFileColumn
});
if (!nearestMappingItem)
return;
const mappedFilePath = path.resolve(path.dirname(sourceFilePath), nearestMappingItem.source);
// Does the mapped filename exist? Use a cache to remember the answer.
let originalFileInfo = this._originalFileInfoByPath.get(mappedFilePath);
if (originalFileInfo === undefined) {
originalFileInfo = {
fileExists: node_core_library_1.FileSystem.exists(mappedFilePath),
maxColumnForLine: []
};
if (originalFileInfo.fileExists) {
// Read the file and measure the length of each line
originalFileInfo.maxColumnForLine = node_core_library_1.FileSystem.readFile(mappedFilePath, {
convertLineEndings: node_core_library_1.NewlineKind.Lf
})
.split('\n')
.map((x) => x.length + 1); // +1 since columns are 1-based
originalFileInfo.maxColumnForLine.unshift(0); // Extra item since lines are 1-based
}
this._originalFileInfoByPath.set(mappedFilePath, originalFileInfo);
}
// Don't translate coordinates to a file that doesn't exist
if (!originalFileInfo.fileExists)
return;
// The nearestMappingItem anchor may be above/left of the real position, due to gaps in the mapping. Calculate
// the delta and apply it to the original position.
const guessedPosition = {
line: nearestMappingItem.originalLine + sourceFileLine - nearestMappingItem.generatedLine,
column: nearestMappingItem.originalColumn + sourceFileColumn - nearestMappingItem.generatedColumn
};
// Verify that the result is not out of bounds, in cause our heuristic failed
if (guessedPosition.line >= 1 &&
guessedPosition.line < originalFileInfo.maxColumnForLine.length &&
guessedPosition.column >= 1 &&
guessedPosition.column <= originalFileInfo.maxColumnForLine[guessedPosition.line]) {
return {
sourceFilePath: mappedFilePath,
sourceFileLine: guessedPosition.line,
sourceFileColumn: guessedPosition.column
};
}
else {
// The guessed position was out of bounds, so use the nearestMappingItem position instead.
return {
sourceFilePath: mappedFilePath,
sourceFileLine: nearestMappingItem.originalLine,
sourceFileColumn: nearestMappingItem.originalColumn
};
}
}
_getSourceMap(sourceFilePath) {
let sourceMap = this._sourceMapByFilePath.get(sourceFilePath);
if (sourceMap === undefined) {
// Normalize the path and redo the lookup
const normalizedPath = node_core_library_1.FileSystem.getRealPath(sourceFilePath);
sourceMap = this._sourceMapByFilePath.get(normalizedPath);
if (sourceMap !== undefined) {
// Copy the result from the normalized to the non-normalized key
this._sourceMapByFilePath.set(sourceFilePath, sourceMap);
}
else {
// Given "folder/file.d.ts", check for a corresponding "folder/file.d.ts.map"
const sourceMapPath = normalizedPath + '.map';
if (node_core_library_1.FileSystem.exists(sourceMapPath)) {
// Load up the source map
const rawSourceMap = node_core_library_1.JsonFile.load(sourceMapPath);
const sourceMapConsumer = new source_map_1.SourceMapConsumer(rawSourceMap);
const mappingItems = [];
// Extract the list of mapping items
sourceMapConsumer.eachMapping((mappingItem) => {
mappingItems.push(Object.assign(Object.assign({}, mappingItem), {
// The "source-map" package inexplicably uses 1-based line numbers but 0-based column numbers.
// Fix that up proactively so we don't have to deal with it later.
generatedColumn: mappingItem.generatedColumn + 1, originalColumn: mappingItem.originalColumn + 1 }));
}, this, source_map_1.SourceMapConsumer.GENERATED_ORDER);
sourceMap = { sourceMapConsumer, mappingItems };
}
else {
// No source map for this filename
sourceMap = null;
}
this._sourceMapByFilePath.set(normalizedPath, sourceMap);
if (sourceFilePath !== normalizedPath) {
// Add both keys to the map
this._sourceMapByFilePath.set(sourceFilePath, sourceMap);
}
}
}
return sourceMap;
}
// The `mappingItems` array is sorted by generatedLine/generatedColumn (GENERATED_ORDER).
// The _findNearestMappingItem() lookup is a simple binary search that returns the previous item
// if there is no exact match.
static _findNearestMappingItem(mappingItems, position) {
if (mappingItems.length === 0) {
return undefined;
}
let startIndex = 0;
let endIndex = mappingItems.length - 1;
while (startIndex <= endIndex) {
const middleIndex = startIndex + Math.floor((endIndex - startIndex) / 2);
const diff = SourceMapper._compareMappingItem(mappingItems[middleIndex], position);
if (diff < 0) {
startIndex = middleIndex + 1;
}
else if (diff > 0) {
endIndex = middleIndex - 1;
}
else {
// Exact match
return mappingItems[middleIndex];
}
}
// If we didn't find an exact match, then endIndex < startIndex.
// Take endIndex because it's the smaller value.
return mappingItems[endIndex];
}
static _compareMappingItem(mappingItem, position) {
const diff = mappingItem.generatedLine - position.line;
if (diff !== 0) {
return diff;
}
return mappingItem.generatedColumn - position.column;
}
}
exports.SourceMapper = SourceMapper;
//# sourceMappingURL=SourceMapper.js.map