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

add option decodeMappings to remapping #88

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 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
5 changes: 3 additions & 2 deletions src/build-source-map-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ function asArray<T>(value: T | T[]): T[] {
export default function buildSourceMapTree(
input: SourceMapInput | SourceMapInput[],
loader: SourceMapLoader,
relativeRoot?: string
relativeRoot?: string,
segmentsAreSorted?: boolean
): SourceMapTree {
const maps = asArray(input).map(decodeSourceMap);
const maps = asArray(input).map((map: SourceMapInput): DecodedSourceMap => decodeSourceMap(map, segmentsAreSorted)) as DecodedSourceMap[];
milahu marked this conversation as resolved.
Show resolved Hide resolved
milahu marked this conversation as resolved.
Show resolved Hide resolved
const map = maps.pop()!;

for (let i = 0; i < maps.length; i++) {
Expand Down
14 changes: 8 additions & 6 deletions src/decode-source-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,25 @@ import { DecodedSourceMap, RawSourceMap, SourceMapInput, SourceMapSegment } from
* Valid input maps include a `DecodedSourceMap`, a `RawSourceMap`, or JSON
* representations of either type.
*/
export default function decodeSourceMap(map: SourceMapInput): DecodedSourceMap {
export default function decodeSourceMap(map: SourceMapInput, segmentsAreSorted?: boolean): DecodedSourceMap {
if (typeof map === 'string') {
map = JSON.parse(map) as DecodedSourceMap | RawSourceMap;
}

let { mappings } = map;
if (typeof mappings === 'string') {
mappings = decode(mappings);
} else {
} else if (!segmentsAreSorted) {
// Clone the Line so that we can sort it. We don't want to mutate an array
// that we don't own directly.
mappings = mappings.map(cloneSegmentLine);
}
// Sort each Line's segments. There's no guarantee that segments are sorted for us,
// and even Chrome's implementation sorts:
// https://cs.chromium.org/chromium/src/third_party/devtools-frontend/src/front_end/sdk/SourceMap.js?l=507-508&rcl=109232bcf479c8f4ef8ead3cf56c49eb25f8c2f0
mappings.forEach(sortSegments);
if (!segmentsAreSorted) {
// Sort each Line's segments. There's no guarantee that segments are sorted for us,
// and even Chrome's implementation sorts:
// https://cs.chromium.org/chromium/src/third_party/devtools-frontend/src/front_end/sdk/SourceMap.js?l=507-508&rcl=109232bcf479c8f4ef8ead3cf56c49eb25f8c2f0
mappings.forEach(sortSegments);
}

return defaults({ mappings }, map);
}
Expand Down
20 changes: 13 additions & 7 deletions src/remapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import buildSourceMapTree from './build-source-map-tree';
import SourceMap from './source-map';
import { SourceMapInput, SourceMapLoader } from './types';
import { DecodedSourceMap, SourceMapInput, SourceMapLoader } from './types';

/**
* Traces through all the mappings in the root sourcemap, through the sources
Expand All @@ -27,14 +27,20 @@ import { SourceMapInput, SourceMapLoader } from './types';
* it returns a falsey value, that source file is treated as an original,
* unmodified source file.
*
* Pass `excludeContent` content to exclude any self-containing source file
* content from the output sourcemap.
* Pass `excludeContent` to exclude any self-containing source file content
* from the output sourcemap.
*
* Pass `decodeMappings` to get a sourcemap with decoded mappings.
milahu marked this conversation as resolved.
Show resolved Hide resolved
*/
export default function remapping(
input: SourceMapInput | SourceMapInput[],
loader: SourceMapLoader,
excludeContent?: boolean
): SourceMap {
const graph = buildSourceMapTree(input, loader);
return new SourceMap(graph.traceMappings(), !!excludeContent);
excludeContent?: boolean,
decodeMappings?: boolean,
segmentsAreSorted?: boolean
milahu marked this conversation as resolved.
Show resolved Hide resolved
): SourceMap | DecodedSourceMap {
const graph = buildSourceMapTree(input, loader, '', !!segmentsAreSorted);
return decodeMappings
? graph.traceMappings()
: new SourceMap(graph.traceMappings(), !!excludeContent);
milahu marked this conversation as resolved.
Show resolved Hide resolved
}
118 changes: 107 additions & 11 deletions test/unit/remapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,43 @@
*/

import remapping from '../../src/remapping';
import { RawSourceMap } from '../../src/types';
import { DecodedSourceMap, RawSourceMap } from '../../src/types';

describe('remapping', () => {

// transform chain:
// 1+1 \n 1+1 \n\n 1 + 1;
// line 0 column 0 < line 1 column 1 < line 2 column 2
// transpiled.min.js < transpiled.js < helloworld.js
// v v
// rawMap transpiledMap
// v
// translatedMap

// segment = output_column [, source, line, column [, name]]
// all decoded numbers are zero-based

const rawMap: RawSourceMap = {
file: 'transpiled.min.js',
// 0th column of 1st line of output file translates into the 1st source
// file, line 2, column 1, using 1st name.
mappings: 'AACCA',
// line 0, column 0 <- source 0, line 1, column 1, name 0
mappings: 'AACCA', // [[[ 0, 0, 1, 1, 0 ]]]
names: ['add'],
sources: ['transpiled.js'],
sourcesContent: ['1+1'],
sourcesContent: ['\n 1+1'],
version: 3,
};
const transpiledMap: RawSourceMap = {
// 1st column of 2nd line of output file translates into the 1st source
// file, line 3, column 2
mappings: ';CAEE',
// line 1, column 1 <- source 0, line 2, column 2
mappings: ';CAEE', // [ [], [[ 1, 0, 2, 2 ]] ]
names: [],
sources: ['helloworld.js'],
sourcesContent: ['\n\n 1 + 1;'],
version: 3,
};
const translatedMap: RawSourceMap = {
file: 'transpiled.min.js',
// 0th column of 1st line of output file translates into the 1st source
// file, line 3, column 2, using first name
mappings: 'AAEEA',
// line 0, column 0 <- source 0, line 2, column 2, name 0
mappings: 'AAEEA', // [[[ 0, 0, 2, 2, 0 ]]]
names: ['add'],
// TODO: support sourceRoot
// sourceRoot: '',
Expand All @@ -50,6 +60,48 @@ describe('remapping', () => {
version: 3,
};

const rawMapDecoded: DecodedSourceMap = {
...rawMap,
mappings: [[[ 0, 0, 1, 1, 0 ]]],
};
const transpiledMapDecoded: DecodedSourceMap = {
...transpiledMap,
mappings: [ [], [[ 1, 0, 2, 2 ]] ],
};
const translatedMapDecoded: DecodedSourceMap = {
...translatedMap,
mappings: [[[ 0, 0, 2, 2, 0 ]]],
};

// segments in reverse order to test `segmentsAreSorted` option
// sort order is preserved in result
// transform chain:
// line 0 column 0 < line 1 column 1 < line 2 column 2
// line 0 column 1 < line 1 column 2 < line 2 column 1
// transpiled.min.js < transpiled.js < helloworld.js
// v v
// rawMap transpiledMap
const rawMapDecodedReversed: DecodedSourceMap = {
...rawMap,
// line 0, column 1 <- source 0, line 1, column 2, name 0
// line 0, column 0 <- source 0, line 1, column 1
mappings: [[ [ 1, 0, 1, 2, 0 ], [ 0, 0, 1, 1 ] ]],
};
const transpiledMapDecodedReversed: DecodedSourceMap = {
...transpiledMap,
// line 1, column 2 <- source 0, line 2, column 1
// line 1, column 1 <- source 0, line 2, column 2
mappings: [ [], [ [ 2, 0, 2, 1 ], [ 1, 0, 2, 2 ] ] ],
};
const translatedMapDecodedReversed: DecodedSourceMap = {
...translatedMap,
// line 0, column 1 <- source 0, line 2, column 1, name 0
// line 0, column 0 <- source 0, line 2, column 2
mappings: [[ [ 1, 0, 2, 1, 0 ], [ 0, 0, 2, 2 ] ]],
};



test('does not alter a lone sourcemap', () => {
const map = remapping(rawMap, () => null);
expect(map).toEqual(rawMap);
Expand Down Expand Up @@ -155,4 +207,48 @@ describe('remapping', () => {

expect(map).not.toHaveProperty('sourcesContent');
});

test('returns decoded mappings if `decodeMappings` is set', () => {
const map = remapping(
rawMap,
(name: string) => {
if (name === 'transpiled.js') {
return transpiledMap;
}
},
false,
true
);

expect(map).toEqual(translatedMapDecoded);
});

test('accepts decoded mappings as input', () => {
const map = remapping(
rawMapDecoded,
(name: string) => {
if (name === 'transpiled.js') {
return transpiledMapDecoded;
}
}
);

expect(map).toEqual(translatedMap);
});

test('skips sorting of segments if `segmentsAreSorted` is set', () => {
const map = remapping(
rawMapDecodedReversed,
(name: string) => {
if (name === 'transpiled.js') {
return transpiledMapDecodedReversed;
}
},
false,
true,
true
);

expect(map).toEqual(translatedMapDecodedReversed);
});
});