Files
TypeScript/src/services/sourcemaps.ts
Andy 64555aa6a9 navigateTo: Collect results from all referenced projects. (#25283)
* 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)
2018-07-05 15:39:03 -07:00

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);
}
}
}