Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support x_google_ignoreList in source map infra #973

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 59 additions & 23 deletions packages/metro-source-map/src/Generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @flow strict-local
* @format
* @oncall react_native
*/
Expand All @@ -19,6 +19,10 @@ import type {

const B64Builder = require('./B64Builder');

type FileFlags = $ReadOnly<{
addToIgnoreList?: boolean,
}>;

/**
* Generates a source map from raw mappings.
*
Expand All @@ -45,6 +49,8 @@ class Generator {
sources: Array<string>;
sourcesContent: Array<?string>;
x_facebook_sources: Array<?FBSourceMetadata>;
// https://developer.chrome.com/blog/devtools-better-angular-debugging/#the-x_google_ignorelist-source-map-extension
x_google_ignoreList: Array<number>;

constructor() {
this.builder = new B64Builder();
Expand All @@ -61,15 +67,26 @@ class Generator {
this.sources = [];
this.sourcesContent = [];
this.x_facebook_sources = [];
this.x_google_ignoreList = [];
}

/**
* Mark the beginning of a new source file.
*/
startFile(file: string, code: string, functionMap: ?FBSourceFunctionMap) {
this.source = this.sources.push(file) - 1;
startFile(
file: string,
code: string,
functionMap: ?FBSourceFunctionMap,
flags?: FileFlags,
) {
const {addToIgnoreList = false} = flags ?? {};
const sourceIndex = this.sources.push(file) - 1;
this.source = sourceIndex;
this.sourcesContent.push(code);
this.x_facebook_sources.push(functionMap ? [functionMap] : null);
if (addToIgnoreList) {
this.x_google_ignoreList.push(sourceIndex);
}
}

/**
Expand Down Expand Up @@ -159,32 +176,41 @@ class Generator {
file?: string,
options?: {excludeSource?: boolean, ...},
): BasicSourceMap {
let content, sourcesMetadata;
const content: {
sourcesContent?: Array<?string>,
} =
options && options.excludeSource === true
? {}
: {sourcesContent: this.sourcesContent.slice()};

if (options && options.excludeSource) {
content = {};
} else {
content = {sourcesContent: this.sourcesContent.slice()};
}
const sourcesMetadata: {
x_facebook_sources?: Array<FBSourceMetadata>,
} = this.hasSourcesMetadata()
? {
x_facebook_sources: JSON.parse(
JSON.stringify(this.x_facebook_sources),
),
}
: {};

if (this.hasSourcesMetadata()) {
sourcesMetadata = {
x_facebook_sources: JSON.parse(JSON.stringify(this.x_facebook_sources)),
};
} else {
sourcesMetadata = {};
}
const ignoreList: {
x_google_ignoreList?: Array<number>,
} = this.x_google_ignoreList.length
? {
x_google_ignoreList: this.x_google_ignoreList,
}
: {};

return {
return ({
version: 3,
file,
sources: this.sources.slice(),
// $FlowFixMe[exponential-spread]
...content,
...sourcesMetadata,
...ignoreList,
names: this.names.items(),
mappings: this.builder.toString(),
};
}: BasicSourceMap);
}

/**
Expand All @@ -193,14 +219,14 @@ class Generator {
* This is ~2.5x faster than calling `JSON.stringify(generator.toMap())`
*/
toString(file?: string, options?: {excludeSource?: boolean, ...}): string {
let content, sourcesMetadata;

if (options && options.excludeSource) {
let content;
if (options && options.excludeSource === true) {
content = '';
} else {
content = `"sourcesContent":${JSON.stringify(this.sourcesContent)},`;
}

let sourcesMetadata;
if (this.hasSourcesMetadata()) {
sourcesMetadata = `"x_facebook_sources":${JSON.stringify(
this.x_facebook_sources,
Expand All @@ -209,13 +235,23 @@ class Generator {
sourcesMetadata = '';
}

let ignoreList;
if (this.x_google_ignoreList.length) {
ignoreList = `"x_google_ignoreList":${JSON.stringify(
this.x_google_ignoreList,
)},`;
} else {
ignoreList = '';
}

return (
'{' +
'"version":3,' +
(file ? `"file":${JSON.stringify(file)},` : '') +
(file != null ? `"file":${JSON.stringify(file)},` : '') +
`"sources":${JSON.stringify(this.sources)},` +
content +
sourcesMetadata +
ignoreList +
`"names":${JSON.stringify(this.names.items())},` +
`"mappings":"${this.builder.toString()}"` +
'}'
Expand Down
40 changes: 39 additions & 1 deletion packages/metro-source-map/src/__tests__/Generator-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ describe('full map generation', () => {
generator.addSimpleMapping(1, 2);
generator.addNamedSourceMapping(3, 4, 5, 6, 'plums');
generator.endFile();
generator.startFile('lemons', 'oranges');
generator.startFile('lemons', 'oranges', undefined, {
addToIgnoreList: true,
});
generator.addNamedSourceMapping(7, 8, 9, 10, 'tangerines');
generator.addNamedSourceMapping(11, 12, 13, 14, 'tangerines');
generator.addSimpleMapping(15, 16);
Expand All @@ -113,6 +115,7 @@ describe('full map generation', () => {
sources: ['apples', 'lemons'],
sourcesContent: ['pears', 'oranges'],
names: ['plums', 'tangerines'],
x_google_ignoreList: [1],
});
});

Expand All @@ -133,3 +136,38 @@ describe('full map generation', () => {
expect(JSON.parse(generator.toString(file))).toEqual(generator.toMap(file));
});
});

describe('x_google_ignoreList', () => {
it('add files to ignore list', () => {
const file1 = 'just/a/file';
const file2 = 'another/file';
const file3 = 'file3';
const source1 = 'var a = 1;';
const source2 = 'var a = 2;';

generator.startFile(file1, source1, undefined, {addToIgnoreList: true});
generator.startFile(file2, source2, undefined, {addToIgnoreList: false});
generator.startFile(file3, source2, undefined, {addToIgnoreList: true});

expect(generator.toMap()).toEqual(
objectContaining({
sources: [file1, file2, file3],
x_google_ignoreList: [0, 2],
}),
);
});

it('not emitted if no files are ignored', () => {
const file1 = 'just/a/file';
const file2 = 'another/file';
const file3 = 'file3';
const source1 = 'var a = 1;';
const source2 = 'var a = 2;';

generator.startFile(file1, source1);
generator.startFile(file2, source2, undefined, {addToIgnoreList: false});
generator.startFile(file3, source2, undefined, {addToIgnoreList: false});

expect(generator.toMap()).not.toHaveProperty('x_google_ignoreList');
});
});
87 changes: 87 additions & 0 deletions packages/metro-source-map/src/__tests__/composeSourceMaps-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const {add0, add1} = require('ob1');
const path = require('path');
const terser = require('terser');

const {objectContaining} = expect;

/* eslint-disable no-multi-str */

const TestScript1 =
Expand Down Expand Up @@ -165,6 +167,91 @@ describe('composeSourceMaps', () => {
]);
});

it('preserves and reindexes x_google_ignoreList', () => {
const map1 = {
version: 3,
sections: [
{
offset: {line: 0, column: 0},
map: {
version: 3,
sources: ['unused.js', 'src.js'],
x_google_ignoreList: [1],
names: ['global'],
mappings: ';CCCCA',
},
},
],
};

const map2 = {
version: 3,
sources: ['src-transformed.js'],
names: ['gLoBAl'],
mappings: ';CACCA',
};

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

expect(mergedMap).toEqual(
objectContaining({
sources: ['src.js'],
x_google_ignoreList: [0],
}),
);
});

it('x_google_ignoreList: a source with inconsistent ignore status is considered to be ignored', () => {
const map1 = {
version: 3,
sections: [
{
offset: {line: 0, column: 0},
map: {
version: 3,
sources: ['unused.js', 'src.js'],
x_google_ignoreList: [1],
names: ['global'],
// First line of src-transformed.js maps to src.js (source #1 here)
mappings: 'ACAAA',
},
},
{
offset: {line: 1, column: 0},
map: {
version: 3,
sources: ['src.js', 'other.js'],
x_google_ignoreList: ([]: Array<number>),
names: ['global'],

mappings:
// Second line of src-transformed.js maps to src.js (source #0 here)
'AAAAA' +
// Third line of src-transformed.js maps to other.js (source #1 here)
';ACAAA',
},
},
],
};

const map2 = {
version: 3,
sources: ['src-transformed.js'],
names: ['gLoBAl'],
// Map each line to itself
mappings: 'AAAAA;AACAA;AACAA',
};

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

expect(mergedMap).toEqual(
objectContaining({
sources: ['src.js', 'other.js'],
x_google_ignoreList: [0],
}),
);
});

it('preserves sourcesContent', () => {
const map1 = {
version: 3,
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 @@ -27,6 +27,7 @@ function composeSourceMaps(
): MixedSourceMap {
// NOTE: require() here to break dependency cycle
const SourceMetadataMapConsumer = require('metro-symbolicate/src/SourceMetadataMapConsumer');
const GoogleIgnoreListConsumer = require('metro-symbolicate/src/GoogleIgnoreListConsumer');
if (maps.length < 1) {
throw new Error('composeSourceMaps: Expected at least one map');
}
Expand Down Expand Up @@ -81,6 +82,11 @@ function composeSourceMaps(
if (function_offsets) {
composedMap.x_hermes_function_offsets = function_offsets;
}
const ignoreListConsumer = new GoogleIgnoreListConsumer(firstMap);
const x_google_ignoreList = ignoreListConsumer.toArray(composedMap.sources);
if (x_google_ignoreList.length) {
composedMap.x_google_ignoreList = x_google_ignoreList;
}
return composedMap;
}

Expand Down
2 changes: 2 additions & 0 deletions packages/metro-source-map/src/source-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export type BasicSourceMap = {
+x_facebook_sources?: FBSourcesArray,
+x_facebook_segments?: FBSegmentMap,
+x_hermes_function_offsets?: HermesFunctionOffsets,
+x_google_ignoreList?: Array<number>,
};

export type IndexMapSection = {
Expand All @@ -82,6 +83,7 @@ export type IndexMap = {
+x_facebook_sources?: void,
+x_facebook_segments?: FBSegmentMap,
+x_hermes_function_offsets?: HermesFunctionOffsets,
+x_google_ignoreList?: void,
};

export type MixedSourceMap = IndexMap | BasicSourceMap;
Expand Down
Loading