mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-17 01:49:57 -05:00
* navigateTo: Collect results from all referenced projects. * Don't use project references, just source maps * Move more code to session * Test when implementation file is deleted * Use tsserver tests instead of fourslash tests to ensure session is used * Support find-all-references * Restore fourslash tests * Update emit baselines (added missing newline) * Support rename * @weswigham code review * Don't open/close files * Avoid growing `toDo` too eagerly * @sheetalkamat code review * Also get symlinked projects for originalLocation * Update API (#24966) * More @sheetalkamat code review * Remove unnecessary test * Update API (#24966)
116 lines
5.4 KiB
TypeScript
116 lines
5.4 KiB
TypeScript
/* @internal */
|
|
namespace ts {
|
|
// Sometimes tools can sometimes see the following line as a source mapping url comment, so we mangle it a bit (the [M])
|
|
const sourceMapCommentRegExp = /^\/\/[@#] source[M]appingURL=(.+)\s*$/;
|
|
const whitespaceOrMapCommentRegExp = /^\s*(\/\/[@#] .*)?$/;
|
|
const base64UrlRegExp = /^data:(?:application\/json(?:;charset=[uU][tT][fF]-8);base64,([A-Za-z0-9+\/=]+)$)?/;
|
|
|
|
export interface SourceMapper {
|
|
toLineColumnOffset(fileName: string, position: number): LineAndCharacter;
|
|
tryGetOriginalLocation(info: sourcemaps.SourceMappableLocation): sourcemaps.SourceMappableLocation | undefined;
|
|
clearCache(): void;
|
|
}
|
|
|
|
export function getSourceMapper(
|
|
getCanonicalFileName: GetCanonicalFileName,
|
|
currentDirectory: string,
|
|
log: (message: string) => void,
|
|
host: LanguageServiceHost,
|
|
getProgram: () => Program,
|
|
): SourceMapper {
|
|
let sourcemappedFileCache: SourceFileLikeCache;
|
|
return { tryGetOriginalLocation, toLineColumnOffset, clearCache };
|
|
|
|
function scanForSourcemapURL(fileName: string) {
|
|
const mappedFile = sourcemappedFileCache.get(toPath(fileName, currentDirectory, getCanonicalFileName));
|
|
if (!mappedFile) {
|
|
return;
|
|
}
|
|
const starts = getLineStarts(mappedFile);
|
|
for (let index = starts.length - 1; index >= 0; index--) {
|
|
const lineText = mappedFile.text.substring(starts[index], starts[index + 1]);
|
|
const comment = sourceMapCommentRegExp.exec(lineText);
|
|
if (comment) {
|
|
return comment[1];
|
|
}
|
|
// If we see a non-whitespace/map comment-like line, break, to avoid scanning up the entire file
|
|
else if (!lineText.match(whitespaceOrMapCommentRegExp)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function convertDocumentToSourceMapper(file: { sourceMapper?: sourcemaps.SourceMapper }, contents: string, mapFileName: string) {
|
|
let maps: sourcemaps.SourceMapData | undefined;
|
|
try {
|
|
maps = JSON.parse(contents);
|
|
}
|
|
catch {
|
|
// swallow error
|
|
}
|
|
if (!maps || !maps.sources || !maps.file || !maps.mappings) {
|
|
// obviously invalid map
|
|
return file.sourceMapper = sourcemaps.identitySourceMapper;
|
|
}
|
|
return file.sourceMapper = sourcemaps.decode({
|
|
readFile: s => host.readFile!(s), // TODO: GH#18217
|
|
fileExists: s => host.fileExists!(s), // TODO: GH#18217
|
|
getCanonicalFileName,
|
|
log,
|
|
}, mapFileName, maps, getProgram(), sourcemappedFileCache);
|
|
}
|
|
|
|
function getSourceMapper(fileName: string, file: SourceFileLike): sourcemaps.SourceMapper {
|
|
if (!host.readFile || !host.fileExists) {
|
|
return file.sourceMapper = sourcemaps.identitySourceMapper;
|
|
}
|
|
if (file.sourceMapper) {
|
|
return file.sourceMapper;
|
|
}
|
|
let mapFileName = scanForSourcemapURL(fileName);
|
|
if (mapFileName) {
|
|
const match = base64UrlRegExp.exec(mapFileName);
|
|
if (match) {
|
|
if (match[1]) {
|
|
const base64Object = match[1];
|
|
return convertDocumentToSourceMapper(file, base64decode(sys, base64Object), fileName);
|
|
}
|
|
// Not a data URL we can parse, skip it
|
|
mapFileName = undefined;
|
|
}
|
|
}
|
|
const possibleMapLocations: string[] = [];
|
|
if (mapFileName) {
|
|
possibleMapLocations.push(mapFileName);
|
|
}
|
|
possibleMapLocations.push(fileName + ".map");
|
|
for (const location of possibleMapLocations) {
|
|
const mapPath = toPath(location, getDirectoryPath(fileName), getCanonicalFileName);
|
|
if (host.fileExists(mapPath)) {
|
|
return convertDocumentToSourceMapper(file, host.readFile(mapPath)!, mapPath); // TODO: GH#18217
|
|
}
|
|
}
|
|
return file.sourceMapper = sourcemaps.identitySourceMapper;
|
|
}
|
|
|
|
function tryGetOriginalLocation(info: sourcemaps.SourceMappableLocation): sourcemaps.SourceMappableLocation | undefined {
|
|
if (!isDeclarationFileName(info.fileName)) return undefined;
|
|
|
|
const file = getProgram().getSourceFile(info.fileName) || sourcemappedFileCache.get(toPath(info.fileName, currentDirectory, getCanonicalFileName));
|
|
if (!file) return undefined;
|
|
const newLoc = getSourceMapper(info.fileName, file).getOriginalPosition(info);
|
|
return newLoc === info ? undefined : tryGetOriginalLocation(newLoc) || newLoc;
|
|
}
|
|
|
|
function toLineColumnOffset(fileName: string, position: number): LineAndCharacter {
|
|
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
|
|
const file = getProgram().getSourceFile(path) || sourcemappedFileCache.get(path)!; // TODO: GH#18217
|
|
return file.getLineAndCharacterOfPosition(position);
|
|
}
|
|
|
|
function clearCache(): void {
|
|
sourcemappedFileCache = createSourceFileLikeCache(host);
|
|
}
|
|
}
|
|
}
|