Skip to content

Commit 7228cc9

Browse files
author
David Sheldrick
committed
source map composition + tests
1 parent 88fd34e commit 7228cc9

File tree

9 files changed

+349
-7
lines changed

9 files changed

+349
-7
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"*.ts": ["prettier --no-semi --single-quote --trailing-comma es5"]
2929
},
3030
"devDependencies": {
31+
"@types/jest": "^22.0.1",
3132
"babel-preset-react-native": "^1.9.1",
3233
"husky": "^0.13.3",
3334
"jest": "^22.0.6",
@@ -45,6 +46,6 @@
4546
".(ts|tsx)": "<rootDir>/node_modules/ts-jest/preprocessor.js"
4647
},
4748
"testRegex": "src/.*(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
48-
"moduleFileExtensions": ["ts", "tsx", "js"]
49+
"moduleFileExtensions": ["ts", "tsx", "js", "json"]
4950
}
5051
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`composeSourceMaps composes two source maps together 1`] = `
4+
"console.log(\\"line6\\");
5+
export const line8 = true;
6+
//# sourceMappingURL=hello.js.map"
7+
`;
8+
9+
exports[`composeSourceMaps composes two source maps together 2`] = `
10+
"Object.defineProperty(exports, \\"__esModule\\", {
11+
value: true
12+
});
13+
console.log(\\"line6\\");
14+
var line8 = exports.line8 = true;"
15+
`;
16+
17+
exports[`convertMetroRawSourceMapToStandardSourceMap takes a raw source map and converts it to a non-raw source map 1`] = `
18+
Array [
19+
Array [
20+
4,
21+
0,
22+
1,
23+
7,
24+
],
25+
Array [
26+
4,
27+
4,
28+
1,
29+
13,
30+
"line1",
31+
],
32+
Array [
33+
4,
34+
28,
35+
1,
36+
21,
37+
],
38+
Array [
39+
4,
40+
29,
41+
1,
42+
7,
43+
],
44+
Array [
45+
5,
46+
0,
47+
2,
48+
7,
49+
],
50+
Array [
51+
5,
52+
4,
53+
2,
54+
13,
55+
"line2",
56+
],
57+
Array [
58+
5,
59+
28,
60+
2,
61+
21,
62+
],
63+
Array [
64+
5,
65+
29,
66+
2,
67+
7,
68+
],
69+
Array [
70+
6,
71+
0,
72+
3,
73+
7,
74+
],
75+
Array [
76+
6,
77+
4,
78+
3,
79+
13,
80+
"line3",
81+
],
82+
Array [
83+
6,
84+
28,
85+
3,
86+
21,
87+
],
88+
Array [
89+
6,
90+
29,
91+
3,
92+
7,
93+
],
94+
Array [
95+
8,
96+
0,
97+
5,
98+
7,
99+
],
100+
Array [
101+
8,
102+
4,
103+
5,
104+
13,
105+
"line5",
106+
],
107+
Array [
108+
8,
109+
28,
110+
5,
111+
21,
112+
],
113+
Array [
114+
8,
115+
29,
116+
5,
117+
7,
118+
],
119+
]
120+
`;
121+
122+
exports[`convertMetroRawSourceMapToStandardSourceMap takes a raw source map and converts it to a non-raw source map 2`] = `"{\\"version\\":3,\\"sources\\":[\\"/Users/dshe/code/react-native-obfuscating-transformer/src/__tests__/numberedLines.js\\"],\\"names\\":[\\"line1\\",\\"line2\\",\\"line3\\",\\"line5\\"],\\"mappings\\":\\";;;AAAO,IAAMA,wBAAQ,CAAd;AACA,IAAMC,wBAAQ,CAAd;AACA,IAAMC,wBAAQ,CAAd;;AAEA,IAAMC,wBAAQ,CAAd\\",\\"sourcesContent\\":[\\"export const line1 = 1\\\\nexport const line2 = 2\\\\nexport const line3 = 3\\\\n\\\\nexport const line5 = 5\\\\n\\"]}"`;
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { getMetroTransformer } from "../getMetroTransformer"
2+
import {
3+
convertMetroRawSourceMapToStandardSourceMap,
4+
MetroRawSourceMap,
5+
composeSourceMaps,
6+
} from "../composeSourceMaps"
7+
import * as fs from "fs"
8+
import * as path from "path"
9+
import { SourceMapConsumer } from "source-map/source-map"
10+
import { getPositionOfSubstring } from "./getPositionOfSubstring"
11+
import * as ts from "typescript"
12+
13+
const numberedLines = fs
14+
.readFileSync(require.resolve("./files/numberedLines.js"))
15+
.toString()
16+
17+
describe("convertMetroRawSourceMapToStandardSourceMap", () => {
18+
it("takes a raw source map and converts it to a non-raw source map", () => {
19+
const transformer = getMetroTransformer(47)
20+
21+
const { map, code } = transformer.transform({
22+
filename: require.resolve("./files/numberedLines.js"),
23+
src: numberedLines,
24+
options: {
25+
retainLines: false,
26+
},
27+
})
28+
29+
if (typeof code !== "string") {
30+
// use this rather than expect for typescript's sake
31+
throw new Error("code must be a string")
32+
}
33+
34+
expect(Array.isArray(map)).toBe(true)
35+
expect(map).toMatchSnapshot()
36+
37+
const standardMap = convertMetroRawSourceMapToStandardSourceMap(
38+
map as MetroRawSourceMap,
39+
path.join(__dirname, "numberedLines.js"),
40+
numberedLines,
41+
)
42+
43+
const standardMapConsumer = new SourceMapConsumer(standardMap as any) // upstream types are wrong
44+
45+
for (const substring of ["line1", "line2", "line3", "line5"])
46+
expect(
47+
standardMapConsumer.originalPositionFor(
48+
getPositionOfSubstring(code, substring)!,
49+
),
50+
).toMatchObject(getPositionOfSubstring(numberedLines, substring)!)
51+
52+
expect(standardMap).toMatchSnapshot()
53+
})
54+
})
55+
56+
describe("composeSourceMaps", () => {
57+
it("composes two source maps together", () => {
58+
const filename = require.resolve("./files/hello.ts")
59+
const hello = fs.readFileSync(filename).toString()
60+
61+
const tsTranspileResult = ts.transpileModule(hello, {
62+
fileName: filename,
63+
compilerOptions: {
64+
sourceMap: true,
65+
target: ts.ScriptTarget.ES2015,
66+
},
67+
})
68+
69+
expect(tsTranspileResult.outputText).toMatchSnapshot()
70+
71+
const upstreamTransformResult = getMetroTransformer(47).transform({
72+
filename,
73+
src: tsTranspileResult.outputText,
74+
options: {
75+
retainLines: false,
76+
},
77+
})
78+
79+
expect(upstreamTransformResult.code).toMatchSnapshot()
80+
81+
const composedMap = new SourceMapConsumer(composeSourceMaps(
82+
tsTranspileResult.sourceMapText!,
83+
convertMetroRawSourceMapToStandardSourceMap(
84+
upstreamTransformResult.map as MetroRawSourceMap,
85+
filename,
86+
hello,
87+
),
88+
filename,
89+
hello,
90+
) as any) // upstream types are wrong
91+
92+
expect(
93+
composedMap.originalPositionFor(
94+
getPositionOfSubstring(upstreamTransformResult.code!, "line6")!,
95+
),
96+
).toMatchObject({ line: 6 })
97+
98+
expect(
99+
composedMap.originalPositionFor(
100+
getPositionOfSubstring(upstreamTransformResult.code!, "line8")!,
101+
),
102+
).toMatchObject({ line: 8 })
103+
})
104+
})

src/__tests__/files/hello.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export interface Hello {
2+
readonly line2: string
3+
line3: number
4+
}
5+
6+
console.log("line6")
7+
8+
export const line8 = true
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const line1 = 1
2+
export const line2 = 2
3+
export const line3 = 3
4+
5+
export const line5 = 5
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export function getPositionOfSubstring(
2+
text: string,
3+
substring: string,
4+
): { line: number; column: number } | null {
5+
const lines = text.split(/\r?\n/)
6+
7+
for (let line = 0; line < lines.length; line++) {
8+
const column = lines[line].indexOf(substring)
9+
10+
if (column >= 0) {
11+
return { line: line + 1, column }
12+
}
13+
}
14+
15+
return null
16+
}

src/composeSourceMaps.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { SourceMapConsumer, SourceMapGenerator, RawSourceMap } from "source-map"
2+
3+
export type MetroRawSourceMap = Array<
4+
[number, number, number, number, string | undefined]
5+
>
6+
7+
export function convertMetroRawSourceMapToStandardSourceMap(
8+
map: MetroRawSourceMap,
9+
originalFileName: string,
10+
originalFileContent: string,
11+
): string {
12+
const outputMap = new SourceMapGenerator()
13+
14+
outputMap.setSourceContent(originalFileName, originalFileContent)
15+
16+
map.forEach(args => {
17+
const [generatedLine, generatedColumn, originalLine, originalColumn] = args
18+
outputMap.addMapping({
19+
generated: {
20+
line: generatedLine,
21+
column: generatedColumn,
22+
},
23+
original: {
24+
line: originalLine,
25+
column: originalColumn,
26+
},
27+
source: originalFileName,
28+
name: args.length === 5 ? (args[4] as string) : undefined,
29+
})
30+
})
31+
32+
return outputMap.toString()
33+
}
34+
35+
export function composeSourceMaps(
36+
sourceMap: string | RawSourceMap,
37+
targetMap: string | RawSourceMap,
38+
sourceFileName: string,
39+
sourceContent: string,
40+
) {
41+
const tsConsumer = new SourceMapConsumer(sourceMap as any) // upstreeam types are wrong
42+
const babelConsumer = new SourceMapConsumer(targetMap as any)
43+
const map = new SourceMapGenerator()
44+
map.setSourceContent(sourceFileName, sourceContent)
45+
babelConsumer.eachMapping(
46+
({
47+
generatedLine,
48+
generatedColumn,
49+
originalLine,
50+
originalColumn,
51+
name,
52+
}) => {
53+
if (originalLine) {
54+
const original = tsConsumer.originalPositionFor({
55+
line: originalLine,
56+
column: originalColumn,
57+
})
58+
if (original.line) {
59+
map.addMapping({
60+
generated: {
61+
line: generatedLine,
62+
column: generatedColumn,
63+
},
64+
original: {
65+
line: original.line,
66+
column: original.column,
67+
},
68+
source: sourceFileName,
69+
name: name,
70+
})
71+
}
72+
}
73+
},
74+
)
75+
return map.toString()
76+
}

0 commit comments

Comments
 (0)