First crack at sourcemap handling when scripts are not on disk, and sourceRoot issues
This commit is contained in:
Родитель
11aa378831
Коммит
0539922332
|
@ -25,3 +25,9 @@ In VS Code, run the `launch as server` launch config - it will start the adapter
|
|||
There is a set of mocha tests which can be run with `gulp test` or with the `test` launch config. Also run `gulp tslint` to check your code against our tslint rules.
|
||||
|
||||
See the project under testapp/ for a bunch of test scenarios crammed onto one page.
|
||||
|
||||
## Code
|
||||
Some files were copied from [the Node adapter](https://github.com/Microsoft/vscode-node-debug) and I'll periodically check for fixes to port over.
|
||||
* Files under common/
|
||||
* adapter/sourceMaps/sourceMaps.ts
|
||||
* adapter/sourceMaps/pathUtilities.ts
|
|
@ -21,23 +21,13 @@ export class PathTransformer implements IDebugTransformer {
|
|||
private _pendingBreakpointsByPath = new Map<string, IPendingBreakpoint>();
|
||||
|
||||
public launch(args: ILaunchRequestArgs): void {
|
||||
this.initWebRoot(args);
|
||||
this._webRoot = utils.getWebRoot(args);
|
||||
}
|
||||
|
||||
public attach(args: IAttachRequestArgs): void {
|
||||
this.initWebRoot(args);
|
||||
this._webRoot = utils.getWebRoot(args);
|
||||
}
|
||||
|
||||
private initWebRoot(args: ILaunchRequestArgs|IAttachRequestArgs): void {
|
||||
if (args.webRoot) {
|
||||
this._webRoot = args.webRoot
|
||||
if (!path.isAbsolute(this._webRoot)) {
|
||||
this._webRoot = path.resolve(args.cwd, this._webRoot);
|
||||
}
|
||||
} else {
|
||||
this._webRoot = args.cwd;
|
||||
}
|
||||
}
|
||||
|
||||
public setBreakpoints(args: ISetBreakpointsArgs): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
|
|
|
@ -31,7 +31,7 @@ export class SourceMapTransformer implements IDebugTransformer {
|
|||
|
||||
private init(args: ILaunchRequestArgs | IAttachRequestArgs): void {
|
||||
if (args.sourceMaps) {
|
||||
this._sourceMaps = new SourceMaps();
|
||||
this._sourceMaps = new SourceMaps(utils.getWebRoot(args));
|
||||
this._requestSeqToSetBreakpointsArgs = new Map<number, DebugProtocol.SetBreakpointsArguments>();
|
||||
this._allRuntimeScriptPaths = new Set<string>();
|
||||
}
|
||||
|
@ -125,6 +125,7 @@ export class SourceMapTransformer implements IDebugTransformer {
|
|||
public scriptParsed(event: DebugProtocol.Event): void {
|
||||
if (this._sourceMaps) {
|
||||
this._allRuntimeScriptPaths.add(event.body.scriptUrl);
|
||||
this._sourceMaps.ProcessNewSourceMap(event.body.scriptUrl, event.body.sourceMapURL);
|
||||
|
||||
const sources = this._sourceMaps.AllMappedSources(event.body.scriptUrl);
|
||||
if (sources) {
|
||||
|
|
|
@ -3,9 +3,11 @@
|
|||
*--------------------------------------------------------*/
|
||||
|
||||
import * as Path from 'path';
|
||||
import * as URL from 'url';
|
||||
import * as FS from 'fs';
|
||||
import {SourceMapConsumer} from 'source-map';
|
||||
import * as PathUtils from './pathUtilities';
|
||||
import {Logger} from '../../webkit/utilities';
|
||||
|
||||
|
||||
export interface MappingResult {
|
||||
|
@ -37,6 +39,11 @@ export interface ISourceMaps {
|
|||
* Get all the sources that map to this generated file
|
||||
*/
|
||||
AllMappedSources(path: string): string[];
|
||||
|
||||
/**
|
||||
* With a known sourceMapURL for a generated script, process create the SourceMap and cache for later
|
||||
*/
|
||||
ProcessNewSourceMap(path: string, sourceMapURL: string): void;
|
||||
}
|
||||
|
||||
|
||||
|
@ -49,8 +56,11 @@ export class SourceMaps implements ISourceMaps {
|
|||
private _generatedToSourceMaps: { [id: string] : SourceMap; } = {}; // generated -> source file
|
||||
private _sourceToGeneratedMaps: { [id: string] : SourceMap; } = {}; // source file -> generated
|
||||
|
||||
/* Path to resolve / paths against */
|
||||
private _webRoot: string;
|
||||
|
||||
public constructor() {
|
||||
public constructor(webRoot: string) {
|
||||
this._webRoot = webRoot;
|
||||
}
|
||||
|
||||
public MapPathFromSource(pathToSource: string): string {
|
||||
|
@ -91,6 +101,10 @@ export class SourceMaps implements ISourceMaps {
|
|||
return map ? map.sources : null;
|
||||
}
|
||||
|
||||
public ProcessNewSourceMap(pathToGenerated: string, sourceMapURL: string): void {
|
||||
this._findGeneratedToSourceMapping(pathToGenerated, sourceMapURL);
|
||||
}
|
||||
|
||||
//---- private -----------------------------------------------------------------------
|
||||
|
||||
private _findSourceToGeneratedMapping(pathToSource: string): SourceMap {
|
||||
|
@ -114,7 +128,7 @@ export class SourceMaps implements ISourceMaps {
|
|||
return null;
|
||||
}
|
||||
|
||||
private _findGeneratedToSourceMapping(pathToGenerated: string): SourceMap {
|
||||
private _findGeneratedToSourceMapping(pathToGenerated: string, map_path?: string): SourceMap {
|
||||
|
||||
if (pathToGenerated) {
|
||||
|
||||
|
@ -124,29 +138,31 @@ export class SourceMaps implements ISourceMaps {
|
|||
|
||||
let map: SourceMap = null;
|
||||
|
||||
if (!map_path) {
|
||||
// try to find a source map URL in the generated source
|
||||
let map_path: string = null;
|
||||
const uri = this._findSourceMapInGeneratedSource(pathToGenerated);
|
||||
if (uri) {
|
||||
if (uri.indexOf("data:application/json;base64,") >= 0) {
|
||||
const pos = uri.indexOf(',');
|
||||
map_path = this._findSourceMapInGeneratedSource(pathToGenerated);
|
||||
}
|
||||
|
||||
if (map_path) {
|
||||
if (map_path.indexOf("data:application/json;base64,") >= 0) {
|
||||
const pos = map_path.indexOf(',');
|
||||
if (pos > 0) {
|
||||
const data = uri.substr(pos+1);
|
||||
const data = map_path.substr(pos+1);
|
||||
try {
|
||||
const buffer = new Buffer(data, 'base64');
|
||||
const json = buffer.toString();
|
||||
if (json) {
|
||||
map = new SourceMap(pathToGenerated, json);
|
||||
map = new SourceMap(pathToGenerated, json, this._webRoot);
|
||||
this._generatedToSourceMaps[pathToGenerated] = map;
|
||||
return map;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.error(`FindGeneratedToSourceMapping: exception while processing data url (${e})`);
|
||||
Logger.log(`FindGeneratedToSourceMapping: exception while processing data url (${e})`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
map_path = uri;
|
||||
map_path = map_path;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,7 +190,7 @@ export class SourceMaps implements ISourceMaps {
|
|||
private _createSourceMap(map_path: string, path: string): SourceMap {
|
||||
try {
|
||||
const contents = FS.readFileSync(Path.join(map_path)).toString();
|
||||
return new SourceMap(path, contents);
|
||||
return new SourceMap(path, contents, this._webRoot);
|
||||
}
|
||||
catch (e) {
|
||||
console.error(`CreateSourceMap: {e}`);
|
||||
|
@ -212,36 +228,55 @@ class SourceMap {
|
|||
|
||||
private _generatedFile: string; // the generated file for this sourcemap
|
||||
private _sources: string[]; // the sources of generated file (relative to sourceRoot)
|
||||
private _sourceRoot: string; // the common prefix for the source (can be a URL)
|
||||
private _absSourceRoot: string; // the common prefix for the source (can be a URL)
|
||||
private _smc: SourceMapConsumer; // the source map
|
||||
private _webRoot: string; // if the sourceRoot starts with /, it's resolved from this absolute path
|
||||
|
||||
|
||||
public constructor(generatedPath: string, json: string) {
|
||||
|
||||
public constructor(generatedPath: string, json: string, webRoot: string) {
|
||||
Logger.log(`SourceMap: creating SM for ${generatedPath}`)
|
||||
this._generatedFile = generatedPath;
|
||||
this._webRoot = webRoot;
|
||||
|
||||
const sm = JSON.parse(json);
|
||||
this._sources = sm.sources;
|
||||
let sr = <string> sm.sourceRoot;
|
||||
if (sr) {
|
||||
sr = PathUtils.canonicalizeUrl(sr);
|
||||
this._sourceRoot = PathUtils.makePathAbsolute(generatedPath, sr);
|
||||
if (URL.parse(sr).protocol === 'file:') {
|
||||
// sourceRoot points to a local path like "file:///c:/project/src"
|
||||
this._absSourceRoot = PathUtils.canonicalizeUrl(sr);
|
||||
} else if (Path.isAbsolute(sr)) {
|
||||
// like "/src", would be like http://localhost/src, resolve to a local path under webRoot
|
||||
this._absSourceRoot = Path.join(this._webRoot, sr);
|
||||
} else {
|
||||
this._sourceRoot = Path.dirname(generatedPath);
|
||||
// like "src" or "../src", relative to the script
|
||||
this._absSourceRoot = PathUtils.makePathAbsolute(generatedPath, sr);
|
||||
}
|
||||
|
||||
if (this._sourceRoot[this._sourceRoot.length-1] !== Path.sep) {
|
||||
this._sourceRoot += Path.sep;
|
||||
Logger.log(`SourceMap: resolved sourceRoot ${sr} -> ${this._absSourceRoot}`);
|
||||
} else {
|
||||
this._absSourceRoot = Path.dirname(generatedPath);
|
||||
Logger.log(`SourceMap: no sourceRoot specified, using script dirname: ${this._absSourceRoot}`);
|
||||
}
|
||||
|
||||
// Strip trailing /
|
||||
this._absSourceRoot = this._absSourceRoot.replace(new RegExp(Path.sep + '$'), '');
|
||||
sm.sourceRoot = 'file:///' + this._absSourceRoot;
|
||||
this._smc = new SourceMapConsumer(sm);
|
||||
|
||||
// rewrite sources as absolute paths
|
||||
this._sources = sm.sources.map(sourcePath => {
|
||||
sourcePath = PathUtils.canonicalizeUrl(sourcePath);
|
||||
return Path.isAbsolute(sourcePath) ?
|
||||
sourcePath :
|
||||
Path.resolve(this._absSourceRoot, sourcePath);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Return all mapped sources as absolute paths
|
||||
*/
|
||||
public get sources(): string[] {
|
||||
return this._sources.map(sourcePath => Path.join(this._sourceRoot, sourcePath));
|
||||
return this._sources;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -255,13 +290,7 @@ class SourceMap {
|
|||
* returns true if this source map originates from the given source.
|
||||
*/
|
||||
public doesOriginateFrom(absPath: string): boolean {
|
||||
for (let name of this._sources) {
|
||||
const p = Path.join(this._sourceRoot, name);
|
||||
if (p === absPath) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return this.sources.some(path => path === absPath);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -277,7 +306,6 @@ class SourceMap {
|
|||
|
||||
if (mp.source) {
|
||||
mp.source = PathUtils.canonicalizeUrl(mp.source);
|
||||
mp.source = PathUtils.makePathAbsolute(this._generatedFile, mp.source);
|
||||
}
|
||||
|
||||
return mp;
|
||||
|
@ -289,8 +317,8 @@ class SourceMap {
|
|||
public generatedPositionFor(src: string, line: number, column: number, bias = Bias.GREATEST_LOWER_BOUND): SourceMap.Position {
|
||||
|
||||
// make input path relative to sourceRoot
|
||||
if (this._sourceRoot) {
|
||||
src = Path.relative(this._sourceRoot, src);
|
||||
if (this._absSourceRoot) {
|
||||
src = Path.relative(this._absSourceRoot, src);
|
||||
}
|
||||
|
||||
// source-maps always use forward slashes
|
||||
|
|
|
@ -88,6 +88,8 @@ suite('SourceMapTransformer', () => {
|
|||
mock.expects('AllMappedSources')
|
||||
.once()
|
||||
.withArgs(RUNTIME_PATH).returns([AUTHORED_PATH]);
|
||||
mock.expects('ProcessNewSourceMap')
|
||||
.once();
|
||||
args.lines.forEach((line, i) => {
|
||||
mock.expects('MapFromSource')
|
||||
.once()
|
||||
|
@ -215,4 +217,7 @@ class MockSourceMaps implements ISourceMaps {
|
|||
public AllMappedSources(pathToGenerated: string): string[] {
|
||||
return [AUTHORED_PATH];
|
||||
}
|
||||
|
||||
public ProcessNewSourceMap(pathToGenerated: string, sourceMapURL: string): void {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -340,3 +340,22 @@ export function errP(msg: any): Promise<any> {
|
|||
|
||||
return Promise.reject(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the webRoot from a launch/attach request. The webRoot is the directory that the
|
||||
* files are served from by a web server, (or the directory that they would be served from, and which
|
||||
* sourceRoot may be relative to).
|
||||
*/
|
||||
export function getWebRoot(args: ILaunchRequestArgs|IAttachRequestArgs): string {
|
||||
let webRoot: string;
|
||||
if (args.webRoot) {
|
||||
webRoot = args.webRoot;
|
||||
if (!path.isAbsolute(webRoot)) {
|
||||
webRoot = path.resolve(args.cwd, webRoot);
|
||||
}
|
||||
} else {
|
||||
webRoot = args.cwd;
|
||||
}
|
||||
|
||||
return webRoot;
|
||||
}
|
||||
|
|
|
@ -247,7 +247,7 @@ export class WebKitDebugAdapter implements IDebugAdapter {
|
|||
this._scriptsById.set(script.scriptId, script);
|
||||
|
||||
if (!this.isExtensionScript(script)) {
|
||||
this.fireEvent(new Event('scriptParsed', { scriptUrl: script.url }));
|
||||
this.fireEvent(new Event('scriptParsed', { scriptUrl: script.url, sourceMapURL: script.sourceMapURL }));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче