Skip to content

Commit

Permalink
Merge pull request #1261 from ashgti/main
Browse files Browse the repository at this point in the history
Adding support for indexed source maps.
  • Loading branch information
connor4312 authored May 17, 2022
2 parents 66415de + 1241ce2 commit 7b174a0
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 24 deletions.
2 changes: 1 addition & 1 deletion src/adapter/sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1067,7 +1067,7 @@ export class SourceContainer {
const fileUrl = absolutePath && utils.absolutePathToFileUrl(absolutePath);
const content = this.sourceMapFactory.guardSourceMapFn(
map,
() => map.sourceContentFor(url),
() => map.sourceContentFor(url, true),
() => null,
);

Expand Down
57 changes: 57 additions & 0 deletions src/common/sourceMaps/sourceMap.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { expect } from 'chai';
import { RawIndexMap, RawSourceMap, SourceMapConsumer } from 'source-map';
import { SourceMap } from './sourceMap';

const sampleSource = 'console.log(123)';
const basicSourceMap: RawSourceMap = {
version: 3,
sources: ['one.js'],
sourcesContent: [sampleSource],
names: [],
file: '',
mappings: '',
};
const indexedSourceMap: RawIndexMap = {
version: 3,
sections: [
{
offset: { line: 0, column: 100 },
map: basicSourceMap,
},
],
};

describe('SourceMap', () => {
it('loads basic source-maps', async () => {
const map = new SourceMap(
await new SourceMapConsumer(basicSourceMap),
{
sourceMapUrl: JSON.stringify(basicSourceMap),
compiledPath: 'one.js',
},
'',
['one.js'],
false,
);

expect(map.sourceContentFor('one.js')).to.eq(sampleSource);
});

it('loads indexed source-maps', async () => {
const map = new SourceMap(
await new SourceMapConsumer(indexedSourceMap),
{
sourceMapUrl: JSON.stringify(indexedSourceMap),
compiledPath: 'one.js',
},
'',
['one.js'],
false,
);

expect(map.sourceContentFor('one.js')).to.eq(sampleSource);
});
});
20 changes: 4 additions & 16 deletions src/common/sourceMaps/sourceMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@

import {
BasicSourceMapConsumer,
IndexedSourceMapConsumer,
MappedPosition,
MappingItem,
NullableMappedPosition,
NullablePosition,
Position,
SourceMapConsumer,
} from 'source-map';
import { fixDriveLetterAndSlashes } from '../pathUtils';
import { completeUrlEscapingRoot } from '../urlUtils';
Expand All @@ -22,7 +24,7 @@ export interface ISourceMapMetadata {
/**
* Wrapper for a parsed sourcemap.
*/
export class SourceMap implements BasicSourceMapConsumer {
export class SourceMap implements SourceMapConsumer {
private static idCounter = 0;

/**
Expand All @@ -37,7 +39,7 @@ export class SourceMap implements BasicSourceMapConsumer {
public readonly id = SourceMap.idCounter++;

constructor(
private readonly original: BasicSourceMapConsumer,
private readonly original: BasicSourceMapConsumer | IndexedSourceMapConsumer,
public readonly metadata: Readonly<ISourceMapMetadata>,
private readonly actualRoot: string,
public readonly actualSources: ReadonlyArray<string>,
Expand All @@ -64,13 +66,6 @@ export class SourceMap implements BasicSourceMapConsumer {
return this.actualSources.slice();
}

/**
* Gets the optional name of the generated code that this source map is associated with
*/
public get file() {
return this.metadata.compiledPath ?? this.original.file;
}

/**
* Gets the source root of the sourcemap.
*/
Expand All @@ -79,13 +74,6 @@ export class SourceMap implements BasicSourceMapConsumer {
return this.actualRoot;
}

/**
* Gets the sources content.
*/
public get sourcesContent() {
return this.original.sourcesContent;
}

/**
* Gets the source URL computed from the compiled path and the source root.
*/
Expand Down
84 changes: 84 additions & 0 deletions src/common/sourceMaps/sourceMapFactory.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/

import { expect } from 'chai';
import dataUriToBuffer from 'data-uri-to-buffer';
import { RawIndexMap, RawSourceMap } from 'source-map';
import Dap from '../../dap/api';
import { stubbedDapApi, StubDapApi } from '../../dap/stubbedApi';
import { Logger } from '../logging/logger';
import { SourceMapFactory } from './sourceMapFactory';

const sampleSource = 'console.log(123)';
const basicSourceMap: RawSourceMap = {
version: 3,
sources: ['one.js'],
sourcesContent: [sampleSource],
names: [],
file: '',
mappings: '',
};
const indexedSourceMap: RawIndexMap = {
version: 3,
sections: [
{
offset: { line: 0, column: 100 },
map: basicSourceMap,
},
],
};

describe('SourceMapFactory', () => {
let stubDap: StubDapApi;

beforeEach(() => {
stubDap = stubbedDapApi();
});

it('loads source-maps', async () => {
const factory = new SourceMapFactory(
{
rebaseRemoteToLocal() {
return '/tmp/local';
},
rebaseLocalToRemote() {
return '/tmp/remote';
},
shouldResolveSourceMap() {
return true;
},
urlToAbsolutePath() {
return Promise.resolve('/tmp/abs');
},
absolutePathToUrlRegexp() {
return undefined;
},
},
{
fetch(url) {
return Promise.resolve({
ok: true,
body: dataUriToBuffer(url).toString('utf8'),
url: url,
statusCode: 500,
});
},
fetchJson<T>() {
return Promise.resolve({ ok: true, body: {} as T, url: '', statusCode: 200 });
},
},
stubDap as unknown as Dap.Api,
Logger.null,
);

const map = await factory.load({
sourceMapUrl:
'data:application/json;base64,' +
Buffer.from(JSON.stringify(indexedSourceMap)).toString('base64'),
compiledPath: '/tmp/local/one.js',
});

expect(map.sources).to.eql(['one.js']);
});
});
28 changes: 21 additions & 7 deletions src/common/sourceMaps/sourceMapFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*--------------------------------------------------------*/

import { inject, injectable } from 'inversify';
import { BasicSourceMapConsumer, RawSourceMap, SourceMapConsumer } from 'source-map';
import { RawIndexMap, RawSourceMap, SourceMapConsumer } from 'source-map';
import { IResourceProvider } from '../../adapter/resourceProvider';
import Dap from '../../dap/api';
import { IRootDapApi } from '../../dap/connection';
Expand Down Expand Up @@ -57,7 +57,7 @@ export class SourceMapFactory implements ISourceMapFactory {
* @inheritdoc
*/
public async load(metadata: ISourceMapMetadata): Promise<SourceMap> {
let basic: RawSourceMap | undefined;
let basic: RawSourceMap | RawIndexMap | undefined;
try {
basic = await this.parseSourceMap(metadata.sourceMapUrl);
} catch (e) {
Expand All @@ -75,18 +75,32 @@ export class SourceMapFactory implements ISourceMapFactory {
const actualRoot = basic.sourceRoot;
basic.sourceRoot = undefined;

let hasNames = false;

// The source map library (also) "helpfully" normalizes source URLs, so
// preserve them in the same way. Then, rename the sources to prevent any
// of their names colliding (e.g. "webpack://./index.js" and "webpack://../index.js")
const actualSources = basic.sources;
basic.sources = basic.sources.map((_, i) => `source${i}.js`);
let actualSources: string[] = [];
if ('sections' in basic && Array.isArray(basic.sections)) {
actualSources = [];
let i = 0;
for (const section of basic.sections) {
actualSources.push(...section.map.sources);
section.map.sources = section.map.sources.map(() => `source${i++}.js`);
hasNames ||= !!section.map.names?.length;
}
} else if ('sources' in basic && Array.isArray(basic.sources)) {
actualSources = basic.sources;
basic.sources = basic.sources.map((_, i) => `source${i}.js`);
hasNames = !!basic.names?.length;
}

return new SourceMap(
(await new SourceMapConsumer(basic)) as BasicSourceMapConsumer,
await new SourceMapConsumer(basic),
metadata,
actualRoot ?? '',
actualSources,
!!basic.names?.length,
hasNames,
);
}

Expand Down Expand Up @@ -136,7 +150,7 @@ export class SourceMapFactory implements ISourceMapFactory {
// no-op
}

private async parseSourceMap(sourceMapUrl: string): Promise<RawSourceMap> {
private async parseSourceMap(sourceMapUrl: string): Promise<RawSourceMap | RawIndexMap> {
let absolutePath = fileUrlToAbsolutePath(sourceMapUrl);
if (absolutePath) {
absolutePath = this.pathResolve.rebaseRemoteToLocal(absolutePath);
Expand Down

0 comments on commit 7b174a0

Please sign in to comment.