UNPKG

@inst/vscode-bin-darwin

Version:

BINARY ONLY - VSCode binary deployment for macOS

498 lines (496 loc) 19.7 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); const Path = require("path"); const FS = require("fs"); const CRYPTO = require("crypto"); const OS = require("os"); const XHR = require("request-light"); const SM = require("source-map"); const PathUtils = require("./pathUtilities"); const URI_1 = require("./URI"); const util = require('../../node_modules/source-map/lib/util.js'); var Bias; (function (Bias) { Bias[Bias["GREATEST_LOWER_BOUND"] = 1] = "GREATEST_LOWER_BOUND"; Bias[Bias["LEAST_UPPER_BOUND"] = 2] = "LEAST_UPPER_BOUND"; })(Bias = exports.Bias || (exports.Bias = {})); class SourceMaps { constructor(session, generatedCodeDirectory, generatedCodeGlobs) { this._sourceMapCache = new Map(); // all cached source maps this._generatedToSourceMaps = new Map(); // generated file -> SourceMap this._sourceToGeneratedMaps = new Map(); // source file -> SourceMap this._session = session; generatedCodeGlobs = generatedCodeGlobs || []; if (generatedCodeDirectory) { generatedCodeGlobs.push(generatedCodeDirectory + '/**/*.js'); // backward compatibility: turn old outDir into a glob pattern } // try to find all source files upfront asynchroneously if (generatedCodeGlobs.length > 0) { this._preLoad = PathUtils.multiGlob(generatedCodeGlobs).then(paths => { return Promise.all(paths.map(path => { return this._findSourceMapUrlInFile(path).then(uri => { return this._getSourceMap(uri, path); }).catch(err => { return null; }); })).then(results => { return void 0; }).catch(err => { // silently ignore errors return void 0; }); }); } else { this._preLoad = Promise.resolve(void 0); } } MapPathFromSource(pathToSource) { return this._preLoad.then(() => { return this._findSourceToGeneratedMapping(pathToSource).then(map => { return map ? map.generatedPath() : null; }); }); } MapFromSource(pathToSource, line, column, bias) { return this._preLoad.then(() => { return this._findSourceToGeneratedMapping(pathToSource).then(map => { if (map) { line += 1; // source map impl is 1 based const mr = map.generatedPositionFor(pathToSource, line, column, bias); if (mr && mr.line !== null && mr.column !== null) { return { path: map.generatedPath(), line: mr.line - 1, column: mr.column }; } } return null; }); }); } MapToSource(pathToGenerated, content, line, column) { return this._preLoad.then(() => { return this._findGeneratedToSourceMapping(pathToGenerated, content).then(map => { if (map) { line += 1; // source map impl is 1 based let mr = map.originalPositionFor(line, column, Bias.GREATEST_LOWER_BOUND); if (!mr) { mr = map.originalPositionFor(line, column, Bias.LEAST_UPPER_BOUND); } if (mr && mr.source && mr.line !== null && mr.column !== null) { return { path: mr.source, content: mr.content, line: mr.line - 1, column: mr.column }; } } return null; }); }); } //---- private ----------------------------------------------------------------------- /** * Tries to find a SourceMap for the given source. * This is a bit tricky because the source does not contain any information about where * the generated code or the source map is located. * The code relies on the source cache populated by the exhaustive search over the 'outFiles' glob patterns * and some heuristics. */ _findSourceToGeneratedMapping(pathToSource) { if (!pathToSource) { return Promise.resolve(null); } // try to find in cache by source path const pathToSourceKey = PathUtils.pathNormalize(pathToSource); const map = this._sourceToGeneratedMaps.get(pathToSourceKey); if (map) { return Promise.resolve(map); } let pathToGenerated = pathToSource; return Promise.resolve(null).then(map => { // heuristic: try to find the generated code side by side to the source const ext = Path.extname(pathToSource); if (ext !== '.js') { // use heuristic: change extension to ".js" and find a map for it const pos = pathToSource.lastIndexOf('.'); if (pos >= 0) { pathToGenerated = pathToSource.substr(0, pos) + '.js'; return this._findGeneratedToSourceMapping(pathToGenerated); } } return map; }).then(map => { if (!map) { // heuristic for VSCode extension host support: // we know that the plugin has an "out" directory next to the "src" directory // TODO: get rid of this and use glob patterns instead if (!map) { let srcSegment = Path.sep + 'src' + Path.sep; if (pathToGenerated.indexOf(srcSegment) >= 0) { const outSegment = Path.sep + 'out' + Path.sep; return this._findGeneratedToSourceMapping(pathToGenerated.replace(srcSegment, outSegment)); } } } return map; }).then(map => { if (map) { // remember found map for source key this._sourceToGeneratedMaps.set(pathToSourceKey, map); } return map; }); } /** * Tries to find a SourceMap for the given path to a generated file. * This is simple if the generated file has the 'sourceMappingURL' at the end. * If not, we are using some heuristics... */ _findGeneratedToSourceMapping(pathToGenerated, content) { if (!pathToGenerated) { return Promise.resolve(null); } const pathToGeneratedKey = PathUtils.pathNormalize(pathToGenerated); const map = this._generatedToSourceMaps.get(pathToGeneratedKey); if (map) { return Promise.resolve(map); } // try to find a source map URL in the generated file return this._findSourceMapUrlInFile(pathToGenerated, content).then(uri => { if (uri) { return this._getSourceMap(uri, pathToGenerated); } // heuristic: try to find map file side-by-side to the generated source let map_path = pathToGenerated + '.map'; if (FS.existsSync(map_path)) { return this._getSourceMap(URI_1.URI.file(map_path), pathToGenerated); } return Promise.resolve(null); }); } /** * Try to find the 'sourceMappingURL' in content or the file with the given path. * Returns null if no source map url is found or if an error occured. */ _findSourceMapUrlInFile(pathToGenerated, content) { if (content) { return Promise.resolve(this._findSourceMapUrl(content, pathToGenerated)); } return this._readFile(pathToGenerated).then(content => { return this._findSourceMapUrl(content, pathToGenerated); }).catch(err => { return null; }); } /** * Try to find the 'sourceMappingURL' at the end of the given contents. * Relative file paths are converted into absolute paths. * Returns null if no source map url is found. */ _findSourceMapUrl(contents, pathToGenerated) { const lines = contents.split('\n'); for (let l = lines.length - 1; l >= Math.max(lines.length - 10, 0); l--) { const line = lines[l].trim(); const matches = SourceMaps.SOURCE_MAPPING_MATCHER.exec(line); if (matches && matches.length === 2) { let uri = matches[1].trim(); if (pathToGenerated) { this._log(`_findSourceMapUrl: source map url found at end of generated file '${pathToGenerated}'`); return URI_1.URI.parse(uri, Path.dirname(pathToGenerated)); } else { this._log(`_findSourceMapUrl: source map url found at end of generated content`); return URI_1.URI.parse(uri); } } } return null; } /** * Returns a (cached) SourceMap specified via the given uri. */ _getSourceMap(uri, pathToGenerated) { if (!uri) { return Promise.resolve(null); } // use sha256 to ensure the hash value can be used in filenames const hash = CRYPTO.createHash('sha256').update(uri.uri()).digest('hex'); let promise = this._sourceMapCache.get(hash); if (promise) { return promise; } try { const prom = this._loadSourceMap(uri, pathToGenerated, hash); this._sourceMapCache.set(hash, prom); return prom; } catch (err) { this._log(`_loadSourceMap: loading source map '${uri.uri()}' failed with exception: ${err}`); return Promise.resolve(null); } } /** * Loads a SourceMap specified by the given uri. */ _loadSourceMap(uri, pathToGenerated, hash) { if (uri.isFile()) { const map_path = uri.filePath(); return this._readFile(map_path).then(content => { return this._registerSourceMap(new SourceMap(map_path, pathToGenerated, content)); }); } if (uri.isData()) { const data = uri.data(); if (data) { try { const buffer = new Buffer(data, 'base64'); const json = buffer.toString(); if (json) { return Promise.resolve(this._registerSourceMap(new SourceMap(pathToGenerated, pathToGenerated, json))); } } catch (e) { throw new Error(`exception while processing data url`); } } throw new Error(`exception while processing data url`); } if (uri.isHTTP()) { const cache_path = Path.join(OS.tmpdir(), 'com.microsoft.VSCode', 'node-debug', 'sm-cache'); const path = Path.join(cache_path, hash); return Promise.resolve(FS.existsSync(path)).then(exists => { if (exists) { return this._readFile(path).then(content => { return this._registerSourceMap(new SourceMap(pathToGenerated, pathToGenerated, content)); }); } const options = { url: uri.uri(), followRedirects: 5 }; return XHR.xhr(options).then(response => { return this._writeFile(path, response.responseText).then(content => { return this._registerSourceMap(new SourceMap(pathToGenerated, pathToGenerated, content)); }); }).catch((error) => { return Promise.reject(XHR.getErrorStatusDescription(error.status) || error.toString()); }); }); } throw new Error(`url is not a valid source map`); } /** * Register the given source map in all maps. */ _registerSourceMap(map) { if (map) { const genPath = PathUtils.pathNormalize(map.generatedPath()); this._generatedToSourceMaps.set(genPath, map); const sourcePaths = map.allSourcePaths(); for (let path of sourcePaths) { const key = PathUtils.pathNormalize(path); this._sourceToGeneratedMaps.set(key, map); this._log(`_registerSourceMap: ${key} -> ${genPath}`); } } return map; } _readFile(path, encoding = 'utf8') { return new Promise((resolve, reject) => { FS.readFile(path, encoding, (err, fileContents) => { if (err) { reject(err); } else { resolve(PathUtils.stripBOM(fileContents)); } }); }); } _writeFile(path, data) { return new Promise((resolve, reject) => { PathUtils.mkdirs(Path.dirname(path)); FS.writeFile(path, data, err => { if (err) { // ignore error // reject(err); } resolve(data); }); }); } _log(message) { this._session.log('sm', message); } } SourceMaps.SOURCE_MAPPING_MATCHER = new RegExp('^//[#@] ?sourceMappingURL=(.+)$'); exports.SourceMaps = SourceMaps; class SourceMap { constructor(mapPath, generatedPath, json) { this._sourcemapLocation = this.fixPath(Path.dirname(mapPath)); const sm = JSON.parse(json); if (!generatedPath) { let file = sm.file; if (!PathUtils.isAbsolutePath(file)) { generatedPath = PathUtils.makePathAbsolute(mapPath, file); } } generatedPath = PathUtils.pathToNative(generatedPath); this._generatedFile = generatedPath; // fix all paths for use with the source-map npm module. sm.sourceRoot = this.fixPath(sm.sourceRoot, ''); for (let i = 0; i < sm.sources.length; i++) { sm.sources[i] = this.fixPath(sm.sources[i]); } this._sourceRoot = sm.sourceRoot; // use source-map utilities to normalize sources entries this._sources = sm.sources .map(util.normalize) .map((source) => { return this._sourceRoot && util.isAbsolute(this._sourceRoot) && util.isAbsolute(source) ? util.relative(this._sourceRoot, source) : source; }); try { this._smc = new SM.SourceMapConsumer(sm); } catch (e) { // ignore exception and leave _smc undefined } } /* * The generated file this source map belongs to. */ generatedPath() { return this._generatedFile; } allSourcePaths() { const paths = new Array(); for (let name of this._sources) { if (!util.isAbsolute(name)) { name = util.join(this._sourceRoot, name); } let path = this.absolutePath(name); paths.push(path); } return paths; } /* * Finds the nearest source location for the given location in the generated file. * Returns null if sourcemap is invalid. */ originalPositionFor(line, column, bias) { if (!this._smc) { return null; } const needle = { line: line, column: column, bias: bias || Bias.LEAST_UPPER_BOUND }; const mp = this._smc.originalPositionFor(needle); if (mp.source) { // if source map has inlined source, return it const src = this._smc.sourceContentFor(mp.source); if (src) { mp.content = src; } // map result back to absolute path mp.source = this.absolutePath(mp.source); mp.source = PathUtils.pathToNative(mp.source); } return mp; } /* * Finds the nearest location in the generated file for the given source location. * Returns null if sourcemap is invalid. */ generatedPositionFor(absPath, line, column, bias) { if (!this._smc) { return null; } // make sure that we use an entry from the "sources" array that matches the passed absolute path const source = this.findSource(absPath); if (source) { const needle = { source: source, line: line, column: column, bias: bias || Bias.LEAST_UPPER_BOUND }; return this._smc.generatedPositionFor(needle); } return null; } /** * fix a path for use with the source-map npm module because: * - source map sources are URLs, so even on Windows they should be using forward slashes. * - the source-map library expects forward slashes and their relative path logic * (specifically the "normalize" function) gives incorrect results when passing in backslashes. * - paths starting with drive letters are not recognized as absolute by the source-map library. */ fixPath(path, dflt) { if (path) { path = path.replace(/\\/g, '/'); // if path starts with a drive letter convert path to a file url so that the source-map library can handle it if (/^[a-zA-Z]\:\//.test(path)) { // Windows drive letter must be prefixed with a slash path = encodeURI('file:///' + path); } return path; } return dflt; } /** * undo the fix */ unfixPath(path) { const prefix = 'file://'; if (path.indexOf(prefix) === 0) { path = path.substr(prefix.length); path = decodeURI(path); if (/^\/[a-zA-Z]\:\//.test(path)) { path = path.substr(1); // remove additional '/' } } return path; } /** * returns the first entry from the sources array that matches the given absPath * or null otherwise. */ findSource(absPath) { absPath = PathUtils.pathNormalize(absPath); for (let name of this._sources) { if (!util.isAbsolute(name)) { name = util.join(this._sourceRoot, name); } let path = this.absolutePath(name); path = PathUtils.pathNormalize(path); if (absPath === path) { return name; } } return null; } /** * Tries to make the given path absolute by prefixing it with the source map's location. * Any url schemes are removed. */ absolutePath(path) { if (!util.isAbsolute(path)) { path = util.join(this._sourcemapLocation, path); } return this.unfixPath(path); } } exports.SourceMap = SourceMap; //# sourceMappingURL=../../out/node/sourceMaps.js.map