195 lines
9.3 KiB
JavaScript
195 lines
9.3 KiB
JavaScript
|
"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
|