diff --git a/src/chrome/chromeUtils.ts b/src/chrome/chromeUtils.ts index d8e618c2..08ee4e58 100644 --- a/src/chrome/chromeUtils.ts +++ b/src/chrome/chromeUtils.ts @@ -5,80 +5,82 @@ import * as url from 'url'; import * as path from 'path'; import { Protocol as Crdp } from 'devtools-protocol'; +import { logger } from 'vscode-debugadapter'; import * as utils from '../utils'; import { ITarget } from './chromeConnection'; +import { IPathMapping } from '../debugAdapterInterfaces'; -export function targetUrlToClientPathByPathMappings(scriptUrl: string, pathMapping: any): string { +export function targetUrlPathToClientPath(scriptUrlPath: string, pathMapping: IPathMapping): string { + if (!pathMapping) { + return ''; + } + + if (!scriptUrlPath || !scriptUrlPath.startsWith('/')) { + return ''; + } + + const mappingKeys = Object.keys(pathMapping) + .sort((a, b) => b.length - a.length); + for (let pattern of mappingKeys) { + // empty pattern match nothing use / to match root + if (!pattern) { + continue; + } + + const mappingRHS = pathMapping[pattern]; + if (pattern[0] !== '/') { + logger.log(`PathMapping keys should be absolute: ${pattern}`); + pattern = '/' + pattern; + } + + if (pathMappingPatternMatchesPath(pattern, scriptUrlPath)) { + return toClientPath(pattern, mappingRHS, scriptUrlPath); + } + } + + return ''; +} + +function pathMappingPatternMatchesPath(pattern: string, scriptPath: string): boolean { + if (pattern === scriptPath) { + return true; + } + + if (!pattern.endsWith('/')) { + // Don't match /foo with /foobar/something + pattern += '/'; + } + + return scriptPath.startsWith(pattern); +} + +export function targetUrlToClientPath(scriptUrl: string, pathMapping: IPathMapping): string { const parsedUrl = url.parse(scriptUrl); if (!parsedUrl.protocol || parsedUrl.protocol.startsWith('file') || !parsedUrl.pathname) { // Skip file: URLs and paths, and invalid things return ''; } - const urlWithoutQuery = parsedUrl.protocol + '//' + parsedUrl.host + parsedUrl.pathname; - const mappingKeys = Object.keys(pathMapping) - .sort((a, b) => b.length - a.length); - for (let pattern of mappingKeys) { - // empty pattern match nothing use / to match root - if (pattern) { - const localPath = pathMapping[pattern]; - const parsedPattern = url.parse(pattern); - - if (parsedPattern.protocol) { - // pattern is an url with protocol - if (urlWithoutQuery.startsWith(pattern)) { - const clientPath = toClientPath(localPath, parsedUrl.pathname, pattern); - if (clientPath) { - return clientPath; - } - } - } else if (pattern[0] === '/') { - // pattern is absolute - if (parsedUrl.pathname.startsWith(pattern)) { - const clientPath = toClientPath(localPath, parsedUrl.pathname, pattern); - if (clientPath) { - return clientPath; - } - } - } else { - // pattern is relative - // avoid matching whole segment - pattern = '/' + pattern; - const indexOf = parsedUrl.pathname.indexOf(pattern); - if (indexOf !== -1) { - const clientPath = toClientPath(localPath, parsedUrl.pathname.substring(indexOf), pattern); - if (clientPath) { - return clientPath; - } - } - } - } - } - return ''; + return targetUrlPathToClientPath(parsedUrl.pathname, pathMapping); } -function toClientPath(localPath: string, source: string, pattern: string): string { - if (source.length === pattern.length) { - return localPath; - } else { - // Verify that matching whole segment of the pattern - if (source[pattern.length - 1] === '/' - || source[pattern.length] === '/') { - const r = decodeURIComponent(source.substring(pattern.length)); - return path.join(localPath, r); - } - } - return ''; +function toClientPath(pattern: string, mappingRHS: string, scriptPath: string): string { + const rest = decodeURIComponent(scriptPath.substring(pattern.length)); + const mappedResult = rest ? + path.join(mappingRHS, rest) : + mappingRHS; + + return mappedResult; } /** - * Maps a url from target to an absolute local path. + * Maps a url from target to an absolute local path, if it exists. * If not given an absolute path (with file: prefix), searches the current working directory for a matching file. * http://localhost/scripts/code.js => d:/app/scripts/code.js * file:///d:/scripts/code.js => d:/scripts/code.js */ -export function targetUrlToClientPath(webRoot: string, aUrl: string): string { +export function targetUrlToClientPath2(aUrl: string, pathMapping: IPathMapping): string { if (!aUrl) { return ''; } @@ -90,23 +92,17 @@ export function targetUrlToClientPath(webRoot: string, aUrl: string): string { return canonicalUrl; } - // If we don't have the client workingDirectory for some reason, don't try to map the url to a client path - if (!webRoot) { - return ''; - } - // Search the filesystem under the webRoot for the file that best matches the given url - let pathName = decodeURIComponent(url.parse(canonicalUrl).pathname); + let pathName = url.parse(canonicalUrl).pathname; if (!pathName || pathName === '/') { return ''; } // Dealing with the path portion of either a url or an absolute path to remote file. - // Need to force path.sep separator - pathName = pathName.replace(/\//g, path.sep); - const pathParts = pathName.split(path.sep); + const pathParts = pathName.split(/[\/\\]/); while (pathParts.length > 0) { - const clientPath = path.join(webRoot, pathParts.join(path.sep)); + const joinedPath = '/' + pathParts.join('/'); + const clientPath = targetUrlPathToClientPath(joinedPath, pathMapping); if (utils.existsSync(clientPath)) { return utils.canonicalizeUrl(clientPath); } diff --git a/src/debugAdapterInterfaces.d.ts b/src/debugAdapterInterfaces.d.ts index 24abe7c4..c3e6a395 100644 --- a/src/debugAdapterInterfaces.d.ts +++ b/src/debugAdapterInterfaces.d.ts @@ -9,8 +9,10 @@ import { DebugProtocol } from 'vscode-debugprotocol'; import { Protocol as Crdp } from 'devtools-protocol'; import { ITelemetryPropertyCollector } from './telemetry'; +import { IStringDictionary } from './utils'; -export type ISourceMapPathOverrides = { [pattern: string]: string }; +export type ISourceMapPathOverrides = IStringDictionary; +export type IPathMapping = IStringDictionary; export type BreakOnLoadStrategy = 'regex' | 'instrument' | 'off'; @@ -19,10 +21,9 @@ export { ITelemetryPropertyCollector } from './telemetry'; * Properties valid for both Launch and Attach */ export interface ICommonRequestArgs { - webRoot?: string; remoteRoot?: string; localRoot?: string; - pathMapping?: {[url: string]: string}; + pathMapping?: IPathMapping; outDir?: string; outFiles?: string[]; sourceMaps?: boolean; diff --git a/src/sourceMaps/sourceMap.ts b/src/sourceMaps/sourceMap.ts index 1dff1034..9dab7112 100644 --- a/src/sourceMaps/sourceMap.ts +++ b/src/sourceMaps/sourceMap.ts @@ -8,7 +8,7 @@ import * as path from 'path'; import * as sourceMapUtils from './sourceMapUtils'; import * as utils from '../utils'; import { logger } from 'vscode-debugadapter'; -import { ISourceMapPathOverrides } from '../debugAdapterInterfaces'; +import { IPathMapping } from '../debugAdapterInterfaces'; export type MappedPosition = MappedPosition; @@ -71,11 +71,10 @@ export class SourceMap { } /** - * pathToGenerated - an absolute local path or a URL - * json - sourcemap contents - * webRoot - an absolute path + * generatedPath: an absolute local path or a URL + * json: sourcemap contents as string */ - public constructor(generatedPath: string, json: string, webRoot?: string, sourceMapPathOverrides?: ISourceMapPathOverrides) { + public constructor(generatedPath: string, json: string, pathMapping?: IPathMapping, sourceMapPathOverrides?: utils.IStringDictionary) { this._generatedPath = generatedPath; const sm = JSON.parse(json); @@ -85,12 +84,12 @@ export class SourceMap { logger.log('Warning: if you are using gulp-sourcemaps < 2.0 directly or indirectly, you may need to set sourceRoot manually in your build config, if your files are not actually under a directory called /source'); } logger.log(`SourceMap: sources: ${JSON.stringify(sm.sources)}`); - if (webRoot) { - logger.log(`SourceMap: webRoot: ${webRoot}`); + if (pathMapping) { + logger.log(`SourceMap: pathMapping: ${JSON.stringify(pathMapping)}`); } // Absolute path - const computedSourceRoot = sourceMapUtils.getComputedSourceRoot(sm.sourceRoot, this._generatedPath, webRoot); + const computedSourceRoot = sourceMapUtils.getComputedSourceRoot(sm.sourceRoot, this._generatedPath, pathMapping); // Overwrite the sourcemap's sourceRoot with the version that's resolved to an absolute path, // so the work above only has to be done once diff --git a/src/sourceMaps/sourceMapFactory.ts b/src/sourceMaps/sourceMapFactory.ts index e5829e99..cdab4245 100644 --- a/src/sourceMaps/sourceMapFactory.ts +++ b/src/sourceMaps/sourceMapFactory.ts @@ -11,11 +11,11 @@ import * as sourceMapUtils from './sourceMapUtils'; import * as utils from '../utils'; import { logger } from 'vscode-debugadapter'; import { SourceMap } from './sourceMap'; -import { ISourceMapPathOverrides } from '../debugAdapterInterfaces'; +import { ISourceMapPathOverrides, IPathMapping } from '../debugAdapterInterfaces'; export class SourceMapFactory { constructor( - private _webRoot?: string, + private _pathMapping?: IPathMapping, private _sourceMapPathOverrides?: ISourceMapPathOverrides, private _enableSourceMapCaching?: boolean) { } @@ -26,8 +26,8 @@ export class SourceMapFactory { */ getMapForGeneratedPath(pathToGenerated: string, mapPath: string): Promise { let msg = `SourceMaps.getMapForGeneratedPath: Finding SourceMap for ${pathToGenerated} by URI: ${mapPath}`; - if (this._webRoot) { - msg += ` and webRoot: ${this._webRoot}`; + if (this._pathMapping) { + msg += ` and webRoot/pathMapping: ${JSON.stringify(this._pathMapping)}`; } logger.log(msg); @@ -47,7 +47,7 @@ export class SourceMapFactory { if (contents) { try { // Throws for invalid JSON - return new SourceMap(pathToGenerated, contents, this._webRoot, this._sourceMapPathOverrides); + return new SourceMap(pathToGenerated, contents, this._pathMapping, this._sourceMapPathOverrides); } catch (e) { logger.error(`SourceMaps.getMapForGeneratedPath: exception while processing path: ${pathToGenerated}, sourcemap: ${mapPath}\n${e.stack}`); return null; diff --git a/src/sourceMaps/sourceMapUtils.ts b/src/sourceMaps/sourceMapUtils.ts index 3bce82f7..b85628ef 100644 --- a/src/sourceMaps/sourceMapUtils.ts +++ b/src/sourceMaps/sourceMapUtils.ts @@ -4,10 +4,11 @@ import * as path from 'path'; import * as url from 'url'; - -import * as utils from '../utils'; import { logger } from 'vscode-debugadapter'; -import { ISourceMapPathOverrides } from '../debugAdapterInterfaces'; + +import * as chromeUtils from '../chrome/chromeUtils'; +import * as utils from '../utils'; +import { ISourceMapPathOverrides, IPathMapping } from '../debugAdapterInterfaces'; /** * Resolves a relative path in terms of another file @@ -17,9 +18,9 @@ export function resolveRelativeToFile(absPath: string, relPath: string): string } /** - * Determine the absolute path to the sourceRoot. + * Determine an absolute path for the sourceRoot. */ -export function getComputedSourceRoot(sourceRoot: string, generatedPath: string, webRoot = ''): string { +export function getComputedSourceRoot(sourceRoot: string, generatedPath: string, pathMapping: IPathMapping = {}): string { let absSourceRoot: string; if (sourceRoot) { if (sourceRoot.startsWith('file:///')) { @@ -28,16 +29,16 @@ export function getComputedSourceRoot(sourceRoot: string, generatedPath: string, } else if (sourceRoot.startsWith('/')) { // sourceRoot is like "/src", would be like http://localhost/src, resolve to a local path under webRoot // note that C:/src (or /src as an absolute local path) is not a valid sourceroot - absSourceRoot = path.join(webRoot, sourceRoot); - } else { + absSourceRoot = chromeUtils.targetUrlPathToClientPath(sourceRoot, pathMapping); + } else if (path.isAbsolute(generatedPath)) { // sourceRoot is like "src" or "../src", relative to the script - if (path.isAbsolute(generatedPath)) { - absSourceRoot = resolveRelativeToFile(generatedPath, sourceRoot); - } else { - // generatedPath is a URL so runtime script is not on disk, resolve the sourceRoot location on disk - const genDirname = path.dirname(url.parse(generatedPath).pathname); - absSourceRoot = path.join(webRoot, genDirname, sourceRoot); - } + absSourceRoot = resolveRelativeToFile(generatedPath, sourceRoot); + } else { + // generatedPath is a URL so runtime script is not on disk, resolve the sourceRoot location on disk. + const generatedUrlPath = url.parse(generatedPath).pathname; + const mappedPath = chromeUtils.targetUrlPathToClientPath(generatedUrlPath, pathMapping); + const mappedDirname = path.dirname(mappedPath); + absSourceRoot = path.join(mappedDirname, sourceRoot); } logger.log(`SourceMap: resolved sourceRoot ${sourceRoot} -> ${absSourceRoot}`); @@ -45,10 +46,11 @@ export function getComputedSourceRoot(sourceRoot: string, generatedPath: string, absSourceRoot = path.dirname(generatedPath); logger.log(`SourceMap: no sourceRoot specified, using script dirname: ${absSourceRoot}`); } else { - // runtime script is not on disk, resolve the sourceRoot location on disk - const urlPath = url.parse(generatedPath).pathname; - const scriptPathDirname = urlPath ? path.dirname(urlPath) : ''; // could be debugadapter://123, no other info. - absSourceRoot = path.join(webRoot, scriptPathDirname); + // No sourceRoot and runtime script is not on disk, resolve the sourceRoot location on disk + const urlPathname = url.parse(generatedPath).pathname || '/placeholder.js'; // could be debugadapter://123, no other info. + const mappedPath = chromeUtils.targetUrlPathToClientPath(urlPathname, pathMapping); + const scriptPathDirname = mappedPath ? path.dirname(mappedPath) : ''; + absSourceRoot = scriptPathDirname; logger.log(`SourceMap: no sourceRoot specified, using webRoot + script path dirname: ${absSourceRoot}`); } diff --git a/src/sourceMaps/sourceMaps.ts b/src/sourceMaps/sourceMaps.ts index 50d2107b..8d4970cf 100644 --- a/src/sourceMaps/sourceMaps.ts +++ b/src/sourceMaps/sourceMaps.ts @@ -4,7 +4,7 @@ import { SourceMap, MappedPosition, ISourcePathDetails } from './sourceMap'; import { SourceMapFactory } from './sourceMapFactory'; -import { ISourceMapPathOverrides } from '../debugAdapterInterfaces'; +import { ISourceMapPathOverrides, IPathMapping } from '../debugAdapterInterfaces'; export class SourceMaps { // Maps absolute paths to generated/authored source files to their corresponding SourceMap object @@ -13,8 +13,8 @@ export class SourceMaps { private _sourceMapFactory: SourceMapFactory; - public constructor(webRoot?: string, sourceMapPathOverrides?: ISourceMapPathOverrides, enableSourceMapCaching?: boolean) { - this._sourceMapFactory = new SourceMapFactory(webRoot, sourceMapPathOverrides, enableSourceMapCaching); + public constructor(pathMapping?: IPathMapping, sourceMapPathOverrides?: ISourceMapPathOverrides, enableSourceMapCaching?: boolean) { + this._sourceMapFactory = new SourceMapFactory(pathMapping, sourceMapPathOverrides, enableSourceMapCaching); } /** diff --git a/src/transformers/baseSourceMapTransformer.ts b/src/transformers/baseSourceMapTransformer.ts index f13271b2..99d2ec09 100644 --- a/src/transformers/baseSourceMapTransformer.ts +++ b/src/transformers/baseSourceMapTransformer.ts @@ -65,7 +65,7 @@ export class BaseSourceMapTransformer { protected init(args: ILaunchRequestArgs | IAttachRequestArgs): void { if (args.sourceMaps) { - this._sourceMaps = new SourceMaps(args.webRoot, args.sourceMapPathOverrides, this._enableSourceMapCaching); + this._sourceMaps = new SourceMaps(args.pathMapping, args.sourceMapPathOverrides, this._enableSourceMapCaching); this._requestSeqToSetBreakpointsArgs = new Map(); this._allRuntimeScriptPaths = new Set(); this._authoredPathsToMappedBPs = new Map(); diff --git a/src/transformers/fallbackToClientPathTransformer.ts b/src/transformers/fallbackToClientPathTransformer.ts index 1b3501ca..b5bfe010 100644 --- a/src/transformers/fallbackToClientPathTransformer.ts +++ b/src/transformers/fallbackToClientPathTransformer.ts @@ -17,9 +17,9 @@ export class FallbackToClientPathTransformer extends UrlPathTransformer { super(); } - protected async targetUrlToClientPath(webRoot: string, scriptUrl: string): Promise { + protected async targetUrlToClientPath(scriptUrl: string): Promise { // First try the default UrlPathTransformer transformation - return super.targetUrlToClientPath(webRoot, scriptUrl).then(filePath => { + return super.targetUrlToClientPath(scriptUrl).then(filePath => { // If it returns a valid non empty file path then that should be a valid result, so we use that // If it's an eval script we won't be able to map it, so we also return that return (filePath || ChromeUtils.isEvalScript(scriptUrl)) diff --git a/src/transformers/urlPathTransformer.ts b/src/transformers/urlPathTransformer.ts index c5643974..6ad283b1 100644 --- a/src/transformers/urlPathTransformer.ts +++ b/src/transformers/urlPathTransformer.ts @@ -4,7 +4,7 @@ import { BasePathTransformer } from './basePathTransformer'; -import { ISetBreakpointsArgs, ILaunchRequestArgs, IAttachRequestArgs, IStackTraceResponseBody } from '../debugAdapterInterfaces'; +import { ISetBreakpointsArgs, ILaunchRequestArgs, IAttachRequestArgs, IStackTraceResponseBody, IPathMapping } from '../debugAdapterInterfaces'; import * as utils from '../utils'; import { logger } from 'vscode-debugadapter'; import { DebugProtocol } from 'vscode-debugprotocol'; @@ -16,20 +16,17 @@ import * as path from 'path'; * Converts a local path from Code to a path on the target. */ export class UrlPathTransformer extends BasePathTransformer { - private _webRoot: string; - private _pathMapping: {[url: string]: string} = {}; + private _pathMapping: IPathMapping; private _clientPathToTargetUrl = new Map(); private _targetUrlToClientPath = new Map(); public launch(args: ILaunchRequestArgs): Promise { - this._webRoot = args.webRoot; - this._pathMapping = args.pathMapping || {}; + this._pathMapping = args.pathMapping; return super.launch(args); } public attach(args: IAttachRequestArgs): Promise { - this._webRoot = args.webRoot; - this._pathMapping = args.pathMapping || {}; + this._pathMapping = args.pathMapping; return super.attach(args); } @@ -64,19 +61,15 @@ export class UrlPathTransformer extends BasePathTransformer { } public async scriptParsed(scriptUrl: string): Promise { - let clientPath = ChromeUtils.targetUrlToClientPathByPathMappings(scriptUrl, this._pathMapping); - - if (!clientPath) { - clientPath = await this.targetUrlToClientPath(this._webRoot, scriptUrl); - } + const clientPath = await this.targetUrlToClientPath(scriptUrl); if (!clientPath) { // It's expected that eval scripts (eval://) won't be resolved if (!scriptUrl.startsWith(ChromeUtils.EVAL_NAME_PREFIX)) { - logger.log(`Paths.scriptParsed: could not resolve ${scriptUrl} to a file under webRoot: ${this._webRoot}. It may be external or served directly from the server's memory (and that's OK).`); + logger.log(`Paths.scriptParsed: could not resolve ${scriptUrl} to a file with pathMapping/webRoot: ${JSON.stringify(this._pathMapping)}. It may be external or served directly from the server's memory (and that's OK).`); } } else { - logger.log(`Paths.scriptParsed: resolved ${scriptUrl} to ${clientPath}. webRoot: ${this._webRoot}`); + logger.log(`Paths.scriptParsed: resolved ${scriptUrl} to ${clientPath}. pathMapping/webroot: ${JSON.stringify(this._pathMapping)}`); const canonicalizedClientPath = utils.canonicalizeUrl(clientPath); this._clientPathToTargetUrl.set(canonicalizedClientPath, scriptUrl); this._targetUrlToClientPath.set(scriptUrl, clientPath); @@ -96,7 +89,7 @@ export class UrlPathTransformer extends BasePathTransformer { // Try to resolve the url to a path in the workspace. If it's not in the workspace, // just use the script.url as-is. It will be resolved or cleared by the SourceMapTransformer. const clientPath = this.getClientPathFromTargetPath(source.path) || - await this.targetUrlToClientPath(this._webRoot, source.path); + await this.targetUrlToClientPath(source.path); // Incoming stackFrames have sourceReference and path set. If the path was resolved to a file in the workspace, // clear the sourceReference since it's not needed. @@ -120,7 +113,10 @@ export class UrlPathTransformer extends BasePathTransformer { return this._targetUrlToClientPath.get(targetPath); } - protected async targetUrlToClientPath(webRoot: string, scriptUrl: string): Promise { - return Promise.resolve(ChromeUtils.targetUrlToClientPath(this._webRoot, scriptUrl)); + /** + * Overridable for VS to ask Client to resolve path + */ + protected async targetUrlToClientPath(scriptUrl: string): Promise { + return Promise.resolve(ChromeUtils.targetUrlToClientPath2(scriptUrl, this._pathMapping)); } } diff --git a/src/utils.ts b/src/utils.ts index 482652cc..77f5b2bc 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -13,6 +13,10 @@ import * as https from 'https'; import { IExecutionResultTelemetryProperties } from './telemetry'; +export interface IStringDictionary { + [name: string]: T; +} + export const enum Platform { Windows, OSX, Linux } diff --git a/test/chrome/chromeUtils.test.ts b/test/chrome/chromeUtils.test.ts index 31fcda26..95c6114a 100644 --- a/test/chrome/chromeUtils.test.ts +++ b/test/chrome/chromeUtils.test.ts @@ -44,17 +44,18 @@ suite('ChromeUtils', () => { const TEST_TARGET_LOCAL_URL = 'file:///' + TEST_CLIENT_PATH; const TEST_TARGET_HTTP_URL = 'http://site.com/page/scripts/a.js'; const TEST_WEB_ROOT = 'c:\\site'; + const PATH_MAPPING = { '/': TEST_WEB_ROOT }; test('an empty string is returned for a missing url', () => { - assert.equal(getChromeUtils().targetUrlToClientPath('', ''), ''); + assert.equal(getChromeUtils().targetUrlToClientPath2('', PATH_MAPPING), ''); }); - test('an empty string is returned when the webRoot is missing', () => { - assert.equal(getChromeUtils().targetUrlToClientPath(null, TEST_TARGET_HTTP_URL), ''); + test('an empty string is returned when the pathMapping is missing', () => { + assert.equal(getChromeUtils().targetUrlToClientPath2(TEST_TARGET_HTTP_URL, null), ''); }); test('a url without a path returns an empty string', () => { - assert.equal(getChromeUtils().targetUrlToClientPath(TEST_WEB_ROOT, 'http://site.com'), ''); + assert.equal(getChromeUtils().targetUrlToClientPath2('http://site.com', PATH_MAPPING), ''); }); test('it searches the disk for a path that exists, built from the url', () => { @@ -62,7 +63,7 @@ suite('ChromeUtils', () => { if (aPath !== TEST_CLIENT_PATH) throw new Error('Not found'); }; mockery.registerMock('fs', { statSync }); - assert.equal(getChromeUtils().targetUrlToClientPath(TEST_WEB_ROOT, TEST_TARGET_HTTP_URL), TEST_CLIENT_PATH); + assert.equal(getChromeUtils().targetUrlToClientPath2(TEST_TARGET_HTTP_URL, PATH_MAPPING), TEST_CLIENT_PATH); }); test(`returns an empty string when it can't resolve a url`, () => { @@ -70,23 +71,23 @@ suite('ChromeUtils', () => { throw new Error('Not found'); }; mockery.registerMock('fs', { statSync }); - assert.equal(getChromeUtils().targetUrlToClientPath(TEST_WEB_ROOT, TEST_TARGET_HTTP_URL), ''); + assert.equal(getChromeUtils().targetUrlToClientPath2(TEST_TARGET_HTTP_URL, PATH_MAPPING), ''); }); test('file:/// urls are returned canonicalized', () => { - assert.equal(getChromeUtils().targetUrlToClientPath('', TEST_TARGET_LOCAL_URL), TEST_CLIENT_PATH); + assert.equal(getChromeUtils().targetUrlToClientPath2(TEST_TARGET_LOCAL_URL, PATH_MAPPING), TEST_CLIENT_PATH); }); test('uri encodings are fixed for file:/// paths', () => { const clientPath = 'c:\\project\\path with spaces\\script.js'; - assert.equal(getChromeUtils().targetUrlToClientPath(TEST_WEB_ROOT, 'file:///' + encodeURI(clientPath)), clientPath); + assert.equal(getChromeUtils().targetUrlToClientPath2('file:///' + encodeURI(clientPath), PATH_MAPPING), clientPath); }); test('uri encodings are fixed in URLs', () => { const pathSegment = 'path with spaces\\script.js'; const url = 'http:\\' + encodeURIComponent(pathSegment); - assert.equal(getChromeUtils().targetUrlToClientPath(TEST_WEB_ROOT, url), path.join(TEST_WEB_ROOT, pathSegment)); + assert.equal(getChromeUtils().targetUrlToClientPath2(url, PATH_MAPPING), path.join(TEST_WEB_ROOT, pathSegment)); }); }); @@ -97,28 +98,27 @@ suite('ChromeUtils', () => { const ROOT_MAPPING = { '/': TEST_WEB_ROOT }; const PAGE_MAPPING = { '/page/': TEST_WEB_ROOT }; - const PARTIAL_PAGE_MAPPING = { '/page': TEST_WEB_ROOT, 'page': TEST_WEB_ROOT}; + const PARTIAL_PAGE_MAPPING = { '/page': TEST_WEB_ROOT }; const FILE_MAPPING = { '/page.js': TEST_CLIENT_PATH }; - const RELATIVE_FILE_MAPPING = { 'page.js': TEST_CLIENT_PATH}; test('an empty string is returned for a missing url', () => { - assert.equal(getChromeUtils().targetUrlToClientPathByPathMappings('', { }), ''); + assert.equal(getChromeUtils().targetUrlToClientPath('', { }), ''); }); test('an empty string is returned for file: URLs', () => { - assert.equal(getChromeUtils().targetUrlToClientPathByPathMappings('file:///Users/foo/bar.js', { }), ''); + assert.equal(getChromeUtils().targetUrlToClientPath('file:///Users/foo/bar.js', { }), ''); }); test('an empty string is returned for non-URLs', () => { - assert.equal(getChromeUtils().targetUrlToClientPathByPathMappings('foo.js', { }), ''); + assert.equal(getChromeUtils().targetUrlToClientPath('foo.js', { }), ''); }); test('a url without a path returns an empty string', () => { - assert.equal(getChromeUtils().targetUrlToClientPathByPathMappings('http://site.com', { }), ''); + assert.equal(getChromeUtils().targetUrlToClientPath('http://site.com', { }), ''); }); test(`returns an empty string when it can't resolve a url`, () => { - assert.equal(getChromeUtils().targetUrlToClientPathByPathMappings(TEST_TARGET_HTTP_URL, { '/foo': '/bar' }), ''); + assert.equal(getChromeUtils().targetUrlToClientPath(TEST_TARGET_HTTP_URL, { '/foo': '/bar' }), ''); }); test('decodes uri-encoded characters', () => { @@ -127,7 +127,7 @@ suite('ChromeUtils', () => { const url = 'http://localhost/' + escapedSegment + '/script.js'; assert.equal( - getChromeUtils().targetUrlToClientPathByPathMappings(url, ROOT_MAPPING), + getChromeUtils().targetUrlToClientPath(url, ROOT_MAPPING), path.join(TEST_WEB_ROOT, segmentWithSpaces, 'script.js')); }); @@ -137,50 +137,44 @@ suite('ChromeUtils', () => { const url = 'http://localhost/' + escapedSegment + '/script.js'; assert.equal( - getChromeUtils().targetUrlToClientPathByPathMappings(url, { '/path%20with%20spaces/': TEST_WEB_ROOT }), + getChromeUtils().targetUrlToClientPath(url, { '/path%20with%20spaces/': TEST_WEB_ROOT }), path.join(TEST_WEB_ROOT, 'script.js')); }); test('resolves webroot-style mapping', () => { assert.equal( - getChromeUtils().targetUrlToClientPathByPathMappings(TEST_TARGET_HTTP_URL, PAGE_MAPPING), + getChromeUtils().targetUrlToClientPath(TEST_TARGET_HTTP_URL, PAGE_MAPPING), TEST_CLIENT_PATH); }); test('resolves webroot-style mapping without trailing slash', () => { assert.equal( - getChromeUtils().targetUrlToClientPathByPathMappings(TEST_TARGET_HTTP_URL, PARTIAL_PAGE_MAPPING), + getChromeUtils().targetUrlToClientPath(TEST_TARGET_HTTP_URL, PARTIAL_PAGE_MAPPING), TEST_CLIENT_PATH); }); test('resolves pathMapping for a particular file', () => { assert.equal( - getChromeUtils().targetUrlToClientPathByPathMappings('http://site.com/page.js', FILE_MAPPING), + getChromeUtils().targetUrlToClientPath('http://site.com/page.js', FILE_MAPPING), TEST_CLIENT_PATH); }); test('return an empty string for url that has partially matching directory', () => { const url = 'http://site.com/page-alike/scripts/a.js'; - assert.equal(getChromeUtils().targetUrlToClientPathByPathMappings(url, PARTIAL_PAGE_MAPPING), ''); + assert.equal(getChromeUtils().targetUrlToClientPath(url, PARTIAL_PAGE_MAPPING), ''); }); test('return an empty string for file matching pathMapped directory', () => { const url = 'http://site.com/page.js'; - assert.equal(getChromeUtils().targetUrlToClientPathByPathMappings(url, PARTIAL_PAGE_MAPPING), ''); - }); - - test('resolves pathMapping for a particular relative file', () => { - const url = 'http://site.com/page.js'; - - assert.equal(getChromeUtils().targetUrlToClientPathByPathMappings(url, RELATIVE_FILE_MAPPING), TEST_CLIENT_PATH); + assert.equal(getChromeUtils().targetUrlToClientPath(url, PARTIAL_PAGE_MAPPING), ''); }); test('matches longer patterns first', () => { const url = 'http://localhost/foo/bar'; - assert.equal(getChromeUtils().targetUrlToClientPathByPathMappings(url, { + assert.equal(getChromeUtils().targetUrlToClientPath(url, { '/': 'C:\\a', 'foo': 'C:\\b' }), 'C:\\b\\bar'); diff --git a/test/sourceMaps/sourceMap.test.ts b/test/sourceMaps/sourceMap.test.ts index e72d6cc4..085ab6ca 100644 --- a/test/sourceMaps/sourceMap.test.ts +++ b/test/sourceMaps/sourceMap.test.ts @@ -18,6 +18,7 @@ import { ISourceMapPathOverrides } from '../../src/debugAdapterInterfaces'; suite('SourceMap', () => { const GENERATED_PATH = testUtils.pathResolve('/project/src/app.js'); const WEBROOT = testUtils.pathResolve('/project'); + const PATH_MAPPING = { '/': WEBROOT }; const SOURCEROOT = '/src/'; const SOURCES = [ @@ -47,7 +48,7 @@ suite('SourceMap', () => { test('does not crash when sourceRoot is undefined', () => { // Rare and possibly invalid, but I saw it const sourceMapJSON = getMockSourceMapJSON(SOURCES, undefined); - const sm = new SourceMap(GENERATED_PATH, sourceMapJSON, WEBROOT); + const sm = new SourceMap(GENERATED_PATH, sourceMapJSON, PATH_MAPPING); assert(sm); }); }); @@ -56,14 +57,14 @@ suite('SourceMap', () => { test('relative sources are made absolute', () => { const sourceMapJSON = getMockSourceMapJSON(SOURCES, SOURCEROOT); - const sm = new SourceMap(GENERATED_PATH, sourceMapJSON, WEBROOT); + const sm = new SourceMap(GENERATED_PATH, sourceMapJSON, PATH_MAPPING); assert.deepEqual(sm.authoredSources, ABSOLUTE_SOURCES); }); test('sources with absolute paths are used as-is', () => { const sourceMapJSON = getMockSourceMapJSON(ABSOLUTE_SOURCES, SOURCEROOT); - const sm = new SourceMap(GENERATED_PATH, sourceMapJSON, WEBROOT); + const sm = new SourceMap(GENERATED_PATH, sourceMapJSON, PATH_MAPPING); assert.deepEqual(sm.authoredSources, ABSOLUTE_SOURCES); }); @@ -71,14 +72,14 @@ suite('SourceMap', () => { const fileSources = ABSOLUTE_SOURCES.map(source => 'file:///' + source); const sourceMapJSON = getMockSourceMapJSON(fileSources, SOURCEROOT); - const sm = new SourceMap(GENERATED_PATH, sourceMapJSON, WEBROOT); + const sm = new SourceMap(GENERATED_PATH, sourceMapJSON, PATH_MAPPING); assert.deepEqual(sm.authoredSources, ABSOLUTE_SOURCES); }); test('sourceMapPathOverrides are respected', () => { const sourceMapJSON = getMockSourceMapJSON(SOURCES, SOURCEROOT); - const sm = new SourceMap(GENERATED_PATH, sourceMapJSON, WEBROOT, { '/src/*': testUtils.pathResolve('/project/client/*') }); + const sm = new SourceMap(GENERATED_PATH, sourceMapJSON, PATH_MAPPING, { '/src/*': testUtils.pathResolve('/project/client/*') }); const expectedSources = SOURCES.map(sourcePath => path.join(testUtils.pathResolve('/project/client'), sourcePath)); assert.deepEqual(sm.authoredSources, expectedSources); }); @@ -88,14 +89,14 @@ suite('SourceMap', () => { test('returns true for a source that it contains', () => { const sourceMapJSON = getMockSourceMapJSON(ABSOLUTE_SOURCES, SOURCEROOT); - const sm = new SourceMap(GENERATED_PATH, sourceMapJSON, WEBROOT); + const sm = new SourceMap(GENERATED_PATH, sourceMapJSON, PATH_MAPPING); assert(sm.doesOriginateFrom(ABSOLUTE_SOURCES[0])); }); test('returns false for a source that it does not contain', () => { const sourceMapJSON = getMockSourceMapJSON(ABSOLUTE_SOURCES, SOURCEROOT); - const sm = new SourceMap(GENERATED_PATH, sourceMapJSON, WEBROOT); + const sm = new SourceMap(GENERATED_PATH, sourceMapJSON, PATH_MAPPING); assert(!sm.doesOriginateFrom('c:\\fake\\file.js')); }); }); @@ -104,7 +105,7 @@ suite('SourceMap', () => { let sm: SourceMap; setup(() => { - sm = new SourceMap(GENERATED_PATH, SOURCEMAP_MAPPINGS_JSON, WEBROOT); + sm = new SourceMap(GENERATED_PATH, SOURCEMAP_MAPPINGS_JSON, PATH_MAPPING); }); function getExpectedResult(line: number, column: number, source = ABSOLUTE_SOURCES[0]): MozSourceMap.MappedPosition { @@ -203,7 +204,7 @@ suite('SourceMap', () => { let sm: SourceMap; setup(() => { - sm = new SourceMap(GENERATED_PATH, SOURCEMAP_MAPPINGS_JSON, WEBROOT); + sm = new SourceMap(GENERATED_PATH, SOURCEMAP_MAPPINGS_JSON, PATH_MAPPING); }); function getExpectedResult(line: number, column: number): MozSourceMap.Position { diff --git a/test/sourceMaps/sourceMapUtils.test.ts b/test/sourceMaps/sourceMapUtils.test.ts index dd742dc7..4c61905c 100644 --- a/test/sourceMaps/sourceMapUtils.test.ts +++ b/test/sourceMaps/sourceMapUtils.test.ts @@ -30,52 +30,53 @@ suite('SourceMapUtils', () => { const GEN_URL = 'http://localhost:8080/code/script.js'; const ABS_SOURCEROOT = testUtils.pathResolve('/project/src'); const WEBROOT = testUtils.pathResolve('/project/webroot'); + const PATH_MAPPING = { '/': WEBROOT }; test('handles file:/// sourceRoot', () => { assert.equal( - getComputedSourceRoot('file:///' + ABS_SOURCEROOT, GEN_PATH, WEBROOT), + getComputedSourceRoot('file:///' + ABS_SOURCEROOT, GEN_PATH, PATH_MAPPING), ABS_SOURCEROOT); }); test('handles /src style sourceRoot', () => { assert.equal( - getComputedSourceRoot('/src', GEN_PATH, WEBROOT), + getComputedSourceRoot('/src', GEN_PATH, PATH_MAPPING), testUtils.pathResolve('/project/webroot/src')); }); test('handles ../../src style sourceRoot', () => { assert.equal( - getComputedSourceRoot('../../src', GEN_PATH, WEBROOT), + getComputedSourceRoot('../../src', GEN_PATH, PATH_MAPPING), ABS_SOURCEROOT); }); test('handles src style sourceRoot', () => { assert.equal( - getComputedSourceRoot('src', GEN_PATH, WEBROOT), + getComputedSourceRoot('src', GEN_PATH, PATH_MAPPING), testUtils.pathResolve('/project/webroot/code/src')); }); test('handles runtime script not on disk', () => { assert.equal( - getComputedSourceRoot('../src', GEN_URL, WEBROOT), + getComputedSourceRoot('../src', GEN_URL, PATH_MAPPING), testUtils.pathResolve('/project/webroot/src')); }); test('when no sourceRoot specified and runtime script is on disk, uses the runtime script dirname', () => { assert.equal( - getComputedSourceRoot('', GEN_PATH, WEBROOT), + getComputedSourceRoot('', GEN_PATH, PATH_MAPPING), testUtils.pathResolve('/project/webroot/code')); }); test('when no sourceRoot specified and runtime script is not on disk, uses the runtime script dirname', () => { assert.equal( - getComputedSourceRoot('', GEN_URL, WEBROOT), + getComputedSourceRoot('', GEN_URL, PATH_MAPPING), testUtils.pathResolve('/project/webroot/code')); }); test('no crash on debugadapter:// urls', () => { assert.equal( - getComputedSourceRoot('', 'eval://123', WEBROOT), + getComputedSourceRoot('', 'eval://123', PATH_MAPPING), testUtils.pathResolve(WEBROOT)); }); }); diff --git a/test/sourceMaps/sourceMaps.test.ts b/test/sourceMaps/sourceMaps.test.ts index 1cc6a76a..b8d67c12 100644 --- a/test/sourceMaps/sourceMaps.test.ts +++ b/test/sourceMaps/sourceMaps.test.ts @@ -18,8 +18,9 @@ suite('SourceMaps', () => { const AUTHORED_PATH = path.resolve(DIRNAME, 'testData/source1.ts'); const ALL_SOURCES = [AUTHORED_PATH, path.resolve(DIRNAME, 'testData/source2.ts')]; const WEBROOT = 'http://localhost'; + const PATH_MAPPING = { '/': WEBROOT }; const SOURCEMAP_URL = 'app.js.map'; - const sourceMaps = new SourceMaps(WEBROOT); + const sourceMaps = new SourceMaps(PATH_MAPPING); setup((done) => { testUtils.setupUnhandledRejectionListener(); diff --git a/test/transformers/urlPathTransformer.test.ts b/test/transformers/urlPathTransformer.test.ts index 82d7581c..8a807761 100644 --- a/test/transformers/urlPathTransformer.test.ts +++ b/test/transformers/urlPathTransformer.test.ts @@ -9,10 +9,6 @@ import * as testUtils from '../testUtils'; import {UrlPathTransformer as _UrlPathTransformer } from '../../src/transformers/urlPathTransformer'; import * as chromeUtils from '../../src/chrome/chromeUtils'; -// As of 0.1.0, the included .d.ts is not in the right format to use the import syntax here -// https://github.com/florinn/typemoq/issues/4 -// const typemoq: ITypeMoqStatic = require('typemoq'); - import { Mock, MockBehavior, It, IMock, Times } from 'typemoq'; const MODULE_UNDER_TEST = '../../src/transformers/urlPathTransformer'; @@ -56,11 +52,7 @@ suite('UrlPathTransformer', () => { test('resolves correctly when it can map the client script to the target script', async () => { chromeUtilsMock - .setup(x => x.targetUrlToClientPathByPathMappings(It.isValue(TARGET_URL), It.isAny())) - .returns(() => '').verifiable(); - - chromeUtilsMock - .setup(x => x.targetUrlToClientPath(It.isValue(undefined), It.isValue(TARGET_URL))) + .setup(x => x.targetUrlToClientPath2(It.isValue(TARGET_URL), It.isValue(undefined))) .returns(() => CLIENT_PATH).verifiable(); await transformer.scriptParsed(TARGET_URL); @@ -84,11 +76,7 @@ suite('UrlPathTransformer', () => { suite('scriptParsed', () => { test('returns the client path when the file can be mapped', async () => { chromeUtilsMock - .setup(x => x.targetUrlToClientPathByPathMappings(It.isValue(TARGET_URL), It.isAny())) - .returns(() => '').verifiable(); - - chromeUtilsMock - .setup(x => x.targetUrlToClientPath(It.isValue(undefined), It.isValue(TARGET_URL))) + .setup(x => x.targetUrlToClientPath2(It.isValue(TARGET_URL), It.isValue(undefined))) .returns(() => CLIENT_PATH).verifiable(); assert.equal(await transformer.scriptParsed(TARGET_URL), CLIENT_PATH); @@ -96,11 +84,7 @@ suite('UrlPathTransformer', () => { test(`returns the given path when the file can't be mapped`, async () => { chromeUtilsMock - .setup(x => x.targetUrlToClientPathByPathMappings(It.isValue(TARGET_URL), It.isAny())) - .returns(() => '').verifiable(); - - chromeUtilsMock - .setup(x => x.targetUrlToClientPath(It.isValue(undefined), It.isValue(TARGET_URL))) + .setup(x => x.targetUrlToClientPath2(It.isValue(TARGET_URL), It.isValue(undefined))) .returns(() => '').verifiable(); chromeUtilsMock @@ -112,7 +96,7 @@ suite('UrlPathTransformer', () => { test('ok with uncanonicalized paths', async () => { chromeUtilsMock - .setup(x => x.targetUrlToClientPathByPathMappings(It.isValue(TARGET_URL + '?queryparam'), It.isAny())) + .setup(x => x.targetUrlToClientPath2(It.isValue(TARGET_URL + '?queryparam'), It.isValue(undefined))) .returns(() => CLIENT_PATH).verifiable(); assert.equal(await transformer.scriptParsed(TARGET_URL + '?queryparam'), CLIENT_PATH); @@ -130,9 +114,8 @@ suite('UrlPathTransformer', () => { test('modifies the source path and clears sourceReference when the file can be mapped', async () => { chromeUtilsMock - .setup(x => x.targetUrlToClientPath(It.isValue(undefined), It.isValue(TARGET_URL))) - .returns(() => CLIENT_PATH) - .verifiable(Times.atLeastOnce()); + .setup(x => x.targetUrlToClientPath2(It.isValue(TARGET_URL), It.isValue(undefined))) + .returns(() => CLIENT_PATH).verifiable(Times.atLeastOnce()); const response = testUtils.getStackTraceResponseBody(TARGET_URL, RUNTIME_LOCATIONS, [1, 2, 3]); const expectedResponse = testUtils.getStackTraceResponseBody(CLIENT_PATH, RUNTIME_LOCATIONS); @@ -143,9 +126,8 @@ suite('UrlPathTransformer', () => { test(`doesn't modify the source path or clear the sourceReference when the file can't be mapped`, () => { chromeUtilsMock - .setup(x => x.targetUrlToClientPath(It.isValue(undefined), It.isValue(TARGET_URL))) - .returns(() => '') - .verifiable(Times.atLeastOnce()); + .setup(x => x.targetUrlToClientPath2(It.isValue(TARGET_URL), It.isValue(undefined))) + .returns(() => '').verifiable(Times.atLeastOnce()); const response = testUtils.getStackTraceResponseBody(TARGET_URL, RUNTIME_LOCATIONS, [1, 2, 3]); const expectedResponse = testUtils.getStackTraceResponseBody(TARGET_URL, RUNTIME_LOCATIONS, [1, 2, 3]); diff --git a/tslint.json b/tslint.json index e67799a3..6dcd94bf 100644 --- a/tslint.json +++ b/tslint.json @@ -60,7 +60,7 @@ "no-var-requires": true, "object-literal-sort-keys": false, "one-line": [ - true, + false, "check-open-brace", "check-catch", "check-else",