Merge branch 'roblou/pathMappingChanges' - fix Microsoft/vscode#49472

This commit is contained in:
Rob Lourens 2018-06-01 11:42:06 -07:00
Родитель 47782a37b6 e06f4e8ed5
Коммит b1b1f4da58
16 изменённых файлов: 174 добавлений и 197 удалений

Просмотреть файл

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

7
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<string>;
export type IPathMapping = IStringDictionary<string>;
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;

Просмотреть файл

@ -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<string>) {
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

Просмотреть файл

@ -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<SourceMap> {
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;

Просмотреть файл

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

Просмотреть файл

@ -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);
}
/**

Просмотреть файл

@ -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<number, ISavedSetBreakpointsArgs>();
this._allRuntimeScriptPaths = new Set<string>();
this._authoredPathsToMappedBPs = new Map<string, DebugProtocol.SourceBreakpoint[]>();

Просмотреть файл

@ -17,9 +17,9 @@ export class FallbackToClientPathTransformer extends UrlPathTransformer {
super();
}
protected async targetUrlToClientPath(webRoot: string, scriptUrl: string): Promise<string> {
protected async targetUrlToClientPath(scriptUrl: string): Promise<string> {
// 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))

Просмотреть файл

@ -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<string, string>();
private _targetUrlToClientPath = new Map<string, string>();
public launch(args: ILaunchRequestArgs): Promise<void> {
this._webRoot = args.webRoot;
this._pathMapping = args.pathMapping || {};
this._pathMapping = args.pathMapping;
return super.launch(args);
}
public attach(args: IAttachRequestArgs): Promise<void> {
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<string> {
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<string> {
return Promise.resolve(ChromeUtils.targetUrlToClientPath(this._webRoot, scriptUrl));
/**
* Overridable for VS to ask Client to resolve path
*/
protected async targetUrlToClientPath(scriptUrl: string): Promise<string> {
return Promise.resolve(ChromeUtils.targetUrlToClientPath2(scriptUrl, this._pathMapping));
}
}

Просмотреть файл

@ -13,6 +13,10 @@ import * as https from 'https';
import { IExecutionResultTelemetryProperties } from './telemetry';
export interface IStringDictionary<T> {
[name: string]: T;
}
export const enum Platform {
Windows, OSX, Linux
}

Просмотреть файл

@ -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');

Просмотреть файл

@ -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, <ISourceMapPathOverrides>{ '/src/*': testUtils.pathResolve('/project/client/*') });
const sm = new SourceMap(GENERATED_PATH, sourceMapJSON, PATH_MAPPING, <ISourceMapPathOverrides>{ '/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 {

Просмотреть файл

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

Просмотреть файл

@ -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();

Просмотреть файл

@ -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]);

Просмотреть файл

@ -60,7 +60,7 @@
"no-var-requires": true,
"object-literal-sort-keys": false,
"one-line": [
true,
false,
"check-open-brace",
"check-catch",
"check-else",