Skip to content

Commit

Permalink
metro-source-map: propagate sourcesContent when composing source maps (
Browse files Browse the repository at this point in the history
…#574)

Summary:
Pull Request resolved: #574

* Implements the `sourceContentFor` API from `source-map` in Metro's `Consumer`.
* Copies source contents in `composeSourceMaps` if they exist.

Related issues: facebook/react-native#26086, facebook/hermes#85. We'll be able to close the RN issue once this lands in the version of `metro-source-map` used in RN master.

Reviewed By: cpojer

Differential Revision: D22284159

fbshipit-source-id: ac3080ce772f664d7b58559f109b71a9252c325b
  • Loading branch information
motiz88 authored and facebook-github-bot committed Jun 29, 2020
1 parent 920ba69 commit b1e3941
Show file tree
Hide file tree
Showing 9 changed files with 195 additions and 4 deletions.
6 changes: 5 additions & 1 deletion packages/metro-source-map/src/Consumer/AbstractConsumer.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,14 @@ class AbstractConsumer implements IConsumer {
}
}

// flowlint unsafe-getters-setters:off
// flowlint-next-line unsafe-getters-setters:off
get file(): ?string {
return this._sourceMap.file;
}

sourceContentFor(source: string, nullOnMissing: true): ?string {
invariant(false, 'Not implemented');
}
}

module.exports = AbstractConsumer;
6 changes: 5 additions & 1 deletion packages/metro-source-map/src/Consumer/DelegatingConsumer.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,14 @@ class DelegatingConsumer implements IConsumer {
return this._rootConsumer.eachMapping(callback, context, order);
}

// flowlint unsafe-getters-setters:off
// flowlint-next-line unsafe-getters-setters:off
get file(): ?string {
return this._rootConsumer.file;
}

sourceContentFor(source: string, nullOnMissing: true): ?string {
return this._rootConsumer.sourceContentFor(source, nullOnMissing);
}
}

module.exports = DelegatingConsumer;
23 changes: 23 additions & 0 deletions packages/metro-source-map/src/Consumer/MappingsConsumer.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import type {
Mapping,
IConsumer,
} from './types.flow';
import type {Number0} from 'ob1';

/**
* A source map consumer that supports "basic" source maps (that have a
Expand Down Expand Up @@ -192,6 +193,28 @@ class MappingsConsumer extends AbstractConsumer implements IConsumer {
generatedMappings(): Iterable<Mapping> {
return this._decodeAndCacheMappings();
}

_indexOfSource(source: string): ?Number0 {
const idx = this._normalizeAndCacheSources().indexOf(
normalizeSourcePath(source, this._sourceMap),
);
if (idx === -1) {
return null;
}
return add0(idx);
}

sourceContentFor(source: string, nullOnMissing: true): ?string {
const {sourcesContent} = this._sourceMap;
if (!sourcesContent) {
return null;
}
const idx = this._indexOfSource(source);
if (idx == null) {
return null;
}
return sourcesContent[get0(idx)] ?? null;
}
}

module.exports = MappingsConsumer;
10 changes: 10 additions & 0 deletions packages/metro-source-map/src/Consumer/SectionsConsumer.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,16 @@ class SectionsConsumer extends AbstractConsumer implements IConsumer {
);
return index != null ? this._consumers[index] : null;
}

sourceContentFor(source: string, nullOnMissing: true): ?string {
for (const [_, consumer] of this._consumers) {
const content = consumer.sourceContentFor(source, nullOnMissing);
if (content != null) {
return content;
}
}
return null;
}
}

module.exports = SectionsConsumer;
8 changes: 7 additions & 1 deletion packages/metro-source-map/src/Consumer/types.flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ export interface IConsumer {
order?: IterationOrder,
): void;

// flowlint unsafe-getters-setters:off
// flowlint-next-line unsafe-getters-setters:off
get file(): ?string;

sourceContentFor(
source: string,
/* nullOnMissing = false behaves inconsistently upstream, so we don't support it */
nullOnMissing: true,
): ?string;
}
103 changes: 103 additions & 0 deletions packages/metro-source-map/src/__tests__/Consumer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,51 @@ describe('basic maps', () => {
`);
});
});

describe('sourceContentFor', () => {
test('missing sourcesContent', () => {
const consumer = new Consumer({
version: 3,
mappings: '',
names: [],
sources: [],
});
expect(consumer.sourceContentFor('a.js', true)).toBeNull();
});

test('null in sourcesContent', () => {
const consumer = new Consumer({
version: 3,
mappings: '',
names: [],
sources: ['a.js'],
sourcesContent: [null],
});
expect(consumer.sourceContentFor('a.js', true)).toBeNull();
});

test('sourcesContent too short', () => {
const consumer = new Consumer({
version: 3,
mappings: '',
names: [],
sources: ['a.js'],
sourcesContent: [],
});
expect(consumer.sourceContentFor('a.js', true)).toBeNull();
});

test('string in sourcesContent', () => {
const consumer = new Consumer({
version: 3,
mappings: '',
names: [],
sources: ['a.js'],
sourcesContent: ['content of a.js'],
});
expect(consumer.sourceContentFor('a.js', true)).toBe('content of a.js');
});
});
});

describe('indexed (sectioned) maps', () => {
Expand Down Expand Up @@ -374,6 +419,64 @@ describe('indexed (sectioned) maps', () => {
`);
});
});

describe('sourceContentFor', () => {
test('empty map', () => {
const consumer = new Consumer({
version: 3,
sections: [],
});
expect(consumer.sourceContentFor('a.js', true)).toBeNull();
});

test('found in section', () => {
const consumer = new Consumer({
version: 3,
sections: [
{
offset: {line: 0, column: 0},
map: {
version: 3,
mappings: '',
names: [],
sources: ['a.js'],
sourcesContent: ['content of a.js'],
},
},
],
});
expect(consumer.sourceContentFor('a.js', true)).toBe('content of a.js');
});

test('found in multiple sections', () => {
const consumer = new Consumer({
version: 3,
sections: [
{
offset: {line: 0, column: 0},
map: {
version: 3,
mappings: '',
names: [],
sources: ['a.js'],
sourcesContent: [null],
},
},
{
offset: {line: 1, column: 0},
map: {
version: 3,
mappings: '',
names: [],
sources: ['a.js'],
sourcesContent: ['content of a.js'],
},
},
],
});
expect(consumer.sourceContentFor('a.js', true)).toBe('content of a.js');
});
});
});

describe('.file', () => {
Expand Down
34 changes: 34 additions & 0 deletions packages/metro-source-map/src/__tests__/composeSourceMaps-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,40 @@ describe('composeSourceMaps', () => {
]);
});

it('preserves sourcesContent', () => {
const map1 = {
version: 3,
sections: [
{
offset: {line: 0, column: 0},
map: {
version: 3,
sources: ['1.js', '2.js'],
sourcesContent: ['content of 1.js', 'content of 2.js'],
names: [],
// One column from 2.js, one column from 1.js
mappings: 'ACAA,CDAA',
},
},
],
};

const map2 = {
version: 3,
sources: ['transformed.js'],
names: [],
// Two consecutive columns from transformed.js
mappings: 'AAAA,CAAC',
};

const mergedMap = composeSourceMaps([map1, map2]);

expect(mergedMap.sourcesContent).toEqual([
'content of 2.js',
'content of 1.js',
]);
});

it('merges two maps', () => {
const mergedMap = composeSourceMaps([
fixtures['1.json'],
Expand Down
6 changes: 6 additions & 0 deletions packages/metro-source-map/src/composeSourceMaps.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ function composeSourceMaps(

const composedMap = generator.toJSON();

composedMap.sourcesContent = composedMap.sources.map(source =>
consumers[consumers.length - 1].sourceContentFor(source, true),
);
if (composedMap.sourcesContent.every(content => content == null)) {
delete composedMap.sourcesContent;
}
const metadataConsumer = new SourceMetadataMapConsumer(firstMap);
composedMap.x_facebook_sources = metadataConsumer.toArray(
composedMap.sources,
Expand Down
3 changes: 2 additions & 1 deletion packages/metro-source-map/src/source-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,12 @@ export type IndexMapSection = {
export type IndexMap = {|
+file?: string,
+mappings?: void, // avoids SourceMap being a disjoint union
+sourcesContent?: void,
+sections: Array<IndexMapSection>,
+version: number,
+x_facebook_offsets?: Array<number>,
+x_metro_module_paths?: Array<string>,
+x_facebook_sources?: FBSourcesArray,
+x_facebook_sources?: void,
+x_facebook_segments?: FBSegmentMap,
+x_hermes_function_offsets?: HermesFunctionOffsets,
|};
Expand Down

0 comments on commit b1e3941

Please sign in to comment.