├── .gitignore ├── tests ├── fixtures │ ├── react-native-hermes │ │ ├── input.js │ │ ├── module.js │ │ ├── README.md │ │ └── output.map │ ├── ram_bundle │ │ ├── file_bundle_1 │ │ │ ├── js-modules │ │ │ │ ├── 3.js │ │ │ │ ├── UNBUNDLE │ │ │ │ └── 0.js │ │ │ └── basic.bundle │ │ └── indexed_bundle_1 │ │ │ ├── basic.jsbundle │ │ │ └── basic.jsbundle.map │ ├── adjust_mappings │ │ ├── webpack.bundle.js │ │ ├── rollup.bundle.js │ │ ├── esbuild.bundle.js │ │ ├── vite.bundle.js │ │ ├── webpack-injected.bundle.js.map │ │ ├── esbuild-injected.bundle.js.map │ │ ├── rollup-injected.bundle.js.map │ │ ├── vite-injected.bundle.js.map │ │ ├── webpack-injected.bundle.js │ │ ├── rollup-injected.bundle.js │ │ ├── esbuild-injected.bundle.js │ │ ├── vite-injected.bundle.js │ │ ├── rollup.bundle.js.map │ │ ├── rollup-composed.bundle.js.map │ │ ├── vite.bundle.js.map │ │ ├── vite-composed.bundle.js.map │ │ ├── webpack.bundle.js.map │ │ ├── esbuild-composed.bundle.js.map │ │ ├── webpack-composed.bundle.js.map │ │ ├── rspack.bundle.js │ │ ├── rspack.bundle.js.map │ │ ├── rspack-composed.bundle.js.map │ │ ├── esbuild.bundle.js.map │ │ ├── rspack-injected.bundle.js.map │ │ └── rspack-injected.bundle.js │ └── react-native-metro │ │ └── README.md ├── test_builder.rs ├── test_regular.rs ├── test_hermes.rs ├── test_detector.rs ├── test_encoder.rs ├── test_namemap.rs ├── test_decoder.rs └── test_index.rs ├── .craft.yml ├── cli ├── Cargo.toml └── src │ └── main.rs ├── scripts └── bump-version ├── upload-docs.sh ├── .github └── workflows │ ├── weekly.yml │ ├── release.yml │ └── ci.yml ├── Makefile ├── examples ├── read.rs ├── rewrite.rs └── split_ram_bundle.rs ├── Cargo.toml ├── README.md ├── LICENSE ├── src ├── lib.rs ├── js_identifiers.rs ├── detector.rs ├── errors.rs ├── jsontypes.rs ├── vlq.rs ├── hermes.rs ├── utils.rs ├── encoder.rs ├── builder.rs ├── sourceview.rs └── decoder.rs └── CHANGELOG.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | target 3 | Cargo.lock 4 | .idea -------------------------------------------------------------------------------- /tests/fixtures/react-native-hermes/input.js: -------------------------------------------------------------------------------- 1 | import { foo } from "./module.js" 2 | 3 | foo(); 4 | -------------------------------------------------------------------------------- /tests/fixtures/react-native-hermes/module.js: -------------------------------------------------------------------------------- 1 | export function foo() { 2 | throw new Error("lets throw!"); 3 | } 4 | -------------------------------------------------------------------------------- /tests/fixtures/ram_bundle/file_bundle_1/js-modules/3.js: -------------------------------------------------------------------------------- 1 | __d(function(g,r,i,a,m,e,d){m.exports=function(n){return n&&n.__esModule?n:{default:n}}},12,[]); 2 | -------------------------------------------------------------------------------- /tests/fixtures/ram_bundle/file_bundle_1/js-modules/UNBUNDLE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsentry/rust-sourcemap/HEAD/tests/fixtures/ram_bundle/file_bundle_1/js-modules/UNBUNDLE -------------------------------------------------------------------------------- /tests/fixtures/ram_bundle/indexed_bundle_1/basic.jsbundle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsentry/rust-sourcemap/HEAD/tests/fixtures/ram_bundle/indexed_bundle_1/basic.jsbundle -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/webpack.bundle.js: -------------------------------------------------------------------------------- 1 | (()=>{"use strict";(function(n,o){!function(n,o){n(o)}(n,"boop")})((function(n){throw new Error(n)}))})(); 2 | //# sourceMappingURL=webpack.bundle.js.map -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/rollup.bundle.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";var n;n=function(n){throw new Error(n)},function(n,o){!function(n,o){n(o)}(n,o)}(n,"boop")}(); 2 | //# sourceMappingURL=rollup.bundle.js.map 3 | -------------------------------------------------------------------------------- /.craft.yml: -------------------------------------------------------------------------------- 1 | --- 2 | minVersion: "0.21.0" 3 | changelogPolicy: auto 4 | artifactProvider: 5 | name: none 6 | preReleaseCommand: bash scripts/bump-version 7 | targets: 8 | - name: crates 9 | - name: github 10 | -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/esbuild.bundle.js: -------------------------------------------------------------------------------- 1 | (()=>{function t(r,o){r(o)}function n(r,o){t(r,o)}function f(r,o){n(r,o)}f(function(o){throw new Error(o)},"boop");})(); 2 | //# sourceMappingURL=esbuild.bundle.js.map 3 | -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/vite.bundle.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";function t(n,o){n(o)}function c(n,o){t(n,o)}function f(n,o){c(n,o)}f(function(o){throw new Error(o)},"boop")})(); 2 | //# sourceMappingURL=vite.bundle.js.map 3 | -------------------------------------------------------------------------------- /cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cli" 3 | version = "9.3.1" 4 | authors = ["Armin Ronacher "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | sourcemap = { path = "../" } 9 | argh = "0.1.3" 10 | -------------------------------------------------------------------------------- /tests/fixtures/ram_bundle/file_bundle_1/basic.bundle: -------------------------------------------------------------------------------- 1 | var __DEV__=false,__BUNDLE_START_TIME__=this.nativePerformanceNow?nativePerformanceNow():Date.now(),process=this.process||{};process.env=process.env||{};process.env.NODE_ENV="production"; 2 | -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/webpack-injected.bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"mappings":";;AAAA;AACA;;","names":[],"sources":["pre_injection.js"],"sourcesContent":["(()=>{\"use strict\";(function(n,o){!function(n,o){n(o)}(n,\"boop\")})((function(n){throw new Error(n)}))})();\n//# sourceMappingURL=webpack.bundle.js.map"],"file":null} -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/esbuild-injected.bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"mappings":";;AAAA;AACA;;","names":[],"sources":["pre_injection.js"],"sourcesContent":["(()=>{function t(r,o){r(o)}function n(r,o){t(r,o)}function f(r,o){n(r,o)}f(function(o){throw new Error(o)},\"boop\");})();\n//# sourceMappingURL=esbuild.bundle.js.map\n"],"file":null} -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/rollup-injected.bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"mappings":";;AAAA;AACA;;","names":[],"sources":["pre_injection.js"],"sourcesContent":["!function(){\"use strict\";var n;n=function(n){throw new Error(n)},function(n,o){!function(n,o){n(o)}(n,o)}(n,\"boop\")}();\n//# sourceMappingURL=rollup.bundle.js.map\n"],"file":null} -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/vite-injected.bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"mappings":";;AAAA;AACA;;","names":[],"sources":["pre_injection.js"],"sourcesContent":["(function(){\"use strict\";function t(n,o){n(o)}function c(n,o){t(n,o)}function f(n,o){c(n,o)}f(function(o){throw new Error(o)},\"boop\")})();\n//# sourceMappingURL=vite.bundle.js.map\n"],"file":null} -------------------------------------------------------------------------------- /tests/fixtures/ram_bundle/file_bundle_1/js-modules/0.js: -------------------------------------------------------------------------------- 1 | __d(function(g,r,i,a,m,e,d){'use strict';var t=Date.now();r(d[0]),r(d[1]),r(d[2]),r(d[3]),r(d[4]),r(d[5]),r(d[6]),r(d[7]),r(d[8]),r(d[9]),r(d[10]),r(d[11]),r(d[12]);var n=r(d[13]);n.markPoint('initializeCore_start',n.currentTimestamp()-(Date.now()-t)),n.markPoint('initializeCore_end')},0,[91,92,100,101,106,109,114,116,121,144,146,152,160,157]); -------------------------------------------------------------------------------- /scripts/bump-version: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 5 | cd $SCRIPT_DIR/.. 6 | 7 | OLD_VERSION="${1}" 8 | NEW_VERSION="${2}" 9 | 10 | echo "Bumping version: ${NEW_VERSION}" 11 | 12 | find . -name Cargo.toml -type f -exec sed -i '' -e "s/^version.*/version = \"$NEW_VERSION\"/" {} \; 13 | cargo update -p sourcemap --precise "${NEW_VERSION}" 14 | -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/webpack-injected.bundle.js: -------------------------------------------------------------------------------- 1 | 2 | !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},n=(new Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000")}catch(e){}}(); 3 | (()=>{"use strict";(function(n,o){!function(n,o){n(o)}(n,"boop")})((function(n){throw new Error(n)}))})(); 4 | //# sourceMappingURL=webpack.bundle.js.map 5 | //# debugId=00000000-0000-0000-0000-000000000000 6 | -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/rollup-injected.bundle.js: -------------------------------------------------------------------------------- 1 | 2 | !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},n=(new Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000")}catch(e){}}(); 3 | !function(){"use strict";var n;n=function(n){throw new Error(n)},function(n,o){!function(n,o){n(o)}(n,o)}(n,"boop")}(); 4 | //# sourceMappingURL=rollup.bundle.js.map 5 | 6 | //# debugId=00000000-0000-0000-0000-000000000000 7 | -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/esbuild-injected.bundle.js: -------------------------------------------------------------------------------- 1 | 2 | !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},n=(new Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000")}catch(e){}}(); 3 | (()=>{function t(r,o){r(o)}function n(r,o){t(r,o)}function f(r,o){n(r,o)}f(function(o){throw new Error(o)},"boop");})(); 4 | //# sourceMappingURL=esbuild.bundle.js.map 5 | 6 | //# debugId=00000000-0000-0000-0000-000000000000 7 | -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/vite-injected.bundle.js: -------------------------------------------------------------------------------- 1 | 2 | !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},n=(new Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000")}catch(e){}}(); 3 | (function(){"use strict";function t(n,o){n(o)}function c(n,o){t(n,o)}function f(n,o){c(n,o)}f(function(o){throw new Error(o)},"boop")})(); 4 | //# sourceMappingURL=vite.bundle.js.map 5 | 6 | //# debugId=00000000-0000-0000-0000-000000000000 7 | -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/rollup.bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"rollup.bundle.js","sources":["../src/index.js","../src/foo.js","../src/bar.js"],"sourcesContent":["import { foo } from './foo.js';\n\nfunction wat(fn, msg) {\n foo(fn, msg);\n}\n\nwat(function hello(msg) {\n throw new Error(msg);\n}, 'boop');\n","import { bar } from './bar.js';\n\nexport function foo(fn, msg) {\n bar(fn, msg);\n}\n","export function bar(fn, msg) {\n fn(msg);\n}\n"],"names":["fn","msg","Error","bar","foo"],"mappings":"yBAEA,IAAaA,IAIT,SAAeC,GACjB,MAAM,IAAIC,MAAMD,EAClB,ECNO,SAAaD,EAAIC,ICFjB,SAAaD,EAAIC,GACtBD,EAAGC,EACL,CDCEE,CAAIH,EAAIC,EACV,CDDEG,CAAIJ,EAKH"} -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/rollup-composed.bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"rollup.bundle.js","sources":["../src/index.js","../src/foo.js","../src/bar.js"],"sourcesContent":["import { foo } from './foo.js';\n\nfunction wat(fn, msg) {\n foo(fn, msg);\n}\n\nwat(function hello(msg) {\n throw new Error(msg);\n}, 'boop');\n","import { bar } from './bar.js';\n\nexport function foo(fn, msg) {\n bar(fn, msg);\n}\n","export function bar(fn, msg) {\n fn(msg);\n}\n"],"names":["fn","msg","Error","bar","foo"],"mappings":";;yBAEA,IAAaA,IAIT,SAAeC,GACjB,MAAM,IAAIC,MAAMD,EAClB,ECNO,SAAaD,EAAIC,ICFjB,SAAaD,EAAIC,GACtBD,EAAGC,EACL,CDCEE,CAAIH,EAAIC,EACV,CDDEG,CAAIJ,EAKH"} -------------------------------------------------------------------------------- /upload-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Make a new repo for the gh-pages branch 4 | rm -rf .gh-pages 5 | mkdir .gh-pages 6 | cd .gh-pages 7 | git init 8 | 9 | # Copy over the documentation 10 | cp -r ../target/doc/* . 11 | cat < index.html 12 | 13 | sourcemap 14 | 15 | EOF 16 | 17 | # Add, commit and push files 18 | git add -f --all . 19 | git commit -m "Built documentation" 20 | git checkout -b gh-pages 21 | git remote add origin git@github.com:getsentry/rust-sourcemap.git 22 | git push -qf origin gh-pages 23 | 24 | # Cleanup 25 | cd .. 26 | rm -rf .gh-pages 27 | -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/vite.bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"vite.bundle.js","sources":["../src/bar.js","../src/foo.js","../src/index.js"],"sourcesContent":["export function bar(fn, msg) {\n fn(msg);\n}\n","import { bar } from './bar.js';\n\nexport function foo(fn, msg) {\n bar(fn, msg);\n}\n","import { foo } from './foo.js';\n\nfunction wat(fn, msg) {\n foo(fn, msg);\n}\n\nwat(function hello(msg) {\n throw new Error(msg);\n}, 'boop');\n"],"names":["bar","fn","msg","foo","wat"],"mappings":"yBAAO,SAASA,EAAIC,EAAIC,EAAK,CAC3BD,EAAGC,CAAG,CACR,CCAO,SAASC,EAAIF,EAAIC,EAAK,CAC3BF,EAAIC,EAAIC,CAAG,CACb,CCFA,SAASE,EAAIH,EAAIC,EAAK,CACpBC,EAAIF,EAAIC,CAAG,CACb,CAEAE,EAAI,SAAeF,EAAK,CACtB,MAAM,IAAI,MAAMA,CAAG,CACrB,EAAG,MAAM"} -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/vite-composed.bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"vite.bundle.js","sources":["../src/bar.js","../src/foo.js","../src/index.js"],"sourcesContent":["export function bar(fn, msg) {\n fn(msg);\n}\n","import { bar } from './bar.js';\n\nexport function foo(fn, msg) {\n bar(fn, msg);\n}\n","import { foo } from './foo.js';\n\nfunction wat(fn, msg) {\n foo(fn, msg);\n}\n\nwat(function hello(msg) {\n throw new Error(msg);\n}, 'boop');\n"],"names":["bar","fn","msg","foo","wat"],"mappings":";;yBAAO,SAASA,EAAIC,EAAIC,EAAK,CAC3BD,EAAGC,CAAG,CACR,CCAO,SAASC,EAAIF,EAAIC,EAAK,CAC3BF,EAAIC,EAAIC,CAAG,CACb,CCFA,SAASE,EAAIH,EAAIC,EAAK,CACpBC,EAAIF,EAAIC,CAAG,CACb,CAEAE,EAAI,SAAeF,EAAK,CACtB,MAAM,IAAI,MAAMA,CAAG,CACrB,EAAG,MAAM"} -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/webpack.bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"webpack.bundle.js","mappings":"oBAEO,SAAaA,EAAIC,ICFjB,SAAaD,EAAIC,GACtBD,EAAGC,EACL,CDCEC,CAAIF,EEKH,OFJH,EEDEG,EAGE,SAAeF,GACjB,MAAM,IAAIG,MAAMH,EAClB,G","sources":["webpack://sourcemaps-playground/./src/foo.js","webpack://sourcemaps-playground/./src/bar.js","webpack://sourcemaps-playground/./src/index.js"],"sourcesContent":["import { bar } from './bar.js';\n\nexport function foo(fn, msg) {\n bar(fn, msg);\n}\n","export function bar(fn, msg) {\n fn(msg);\n}\n","import { foo } from './foo.js';\n\nfunction wat(fn, msg) {\n foo(fn, msg);\n}\n\nwat(function hello(msg) {\n throw new Error(msg);\n}, 'boop');\n"],"names":["fn","msg","bar","foo","Error"],"sourceRoot":""} -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/esbuild-composed.bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/bar.js","../src/foo.js","../src/index.js"],"sourcesContent":["export function bar(fn, msg) {\n fn(msg);\n}\n","import { bar } from './bar.js';\n\nexport function foo(fn, msg) {\n bar(fn, msg);\n}\n","import { foo } from './foo.js';\n\nfunction wat(fn, msg) {\n foo(fn, msg);\n}\n\nwat(function hello(msg) {\n throw new Error(msg);\n}, 'boop');\n"],"names":["bar","fn","msg","foo","fn","msg","bar","wat","fn","msg","foo"],"mappings":";;MAAO,SAASA,EAAIC,EAAIC,EAAK,CAC3BD,EAAGC,CAAG,CACR,CCAO,SAASC,EAAIC,EAAIC,EAAK,CAC3BC,EAAIF,EAAIC,CAAG,CACb,CCFA,SAASE,EAAIC,EAAIC,EAAK,CACpBC,EAAIF,EAAIC,CAAG,CACb,CAEAF,EAAI,SAAeE,EAAK,CACtB,MAAM,IAAI,MAAMA,CAAG,CACrB,EAAG,MAAM"} -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/webpack-composed.bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"webpack.bundle.js","sources":["webpack://sourcemaps-playground/./src/foo.js","webpack://sourcemaps-playground/./src/bar.js","webpack://sourcemaps-playground/./src/index.js"],"sourceRoot":"","sourcesContent":["import { bar } from './bar.js';\n\nexport function foo(fn, msg) {\n bar(fn, msg);\n}\n","export function bar(fn, msg) {\n fn(msg);\n}\n","import { foo } from './foo.js';\n\nfunction wat(fn, msg) {\n foo(fn, msg);\n}\n\nwat(function hello(msg) {\n throw new Error(msg);\n}, 'boop');\n"],"names":["fn","msg","bar","foo","Error"],"mappings":";;oBAEO,SAAaA,EAAIC,ICFjB,SAAaD,EAAIC,GACtBD,EAAGC,EACL,CDCEC,CAAIF,EEKH,OFJH,EEDEG,EAGE,SAAeF,GACjB,MAAM,IAAIG,MAAMH,EAClB,G"} -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/rspack.bundle.js: -------------------------------------------------------------------------------- 1 | !function(){var e={62:function(e,r,t){"use strict";function n(e,r){e(r)}Object.defineProperty(r,"__esModule",{value:!0}),Object.defineProperty(r,"bar",{enumerable:!0,get:function(){return n}})},447:function(e,r,t){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),Object.defineProperty(r,"foo",{enumerable:!0,get:function(){return o}});var n=t("62");function o(e,r){(0,n.bar)(e,r)}},151:function(e,r,t){"use strict";Object.defineProperty(r,"__esModule",{value:!0});var n,o,u=t("447");n=function(e){throw Error(e)},o="boop",(0,u.foo)(n,o)}},r={};!function t(n){var o=r[n];if(void 0!==o)return o.exports;var u=r[n]={exports:{}};return e[n](u,u.exports,t),u.exports}("151")}(); 2 | //# sourceMappingURL=rspack.bundle.js.map -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/rspack.bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"rspack.bundle.js","sources":["./src/bar.js","./src/foo.js","./src/index.js"],"sourcesContent":["export function bar(fn, msg) {\n fn(msg);\n}\n","import { bar } from './bar.js';\n\nexport function foo(fn, msg) {\n bar(fn, msg);\n}\n","import { foo } from './foo.js';\n\nfunction wat(fn, msg) {\n foo(fn, msg);\n}\n\nwat(function hello(msg) {\n throw new Error(msg);\n}, 'boop');\n"],"names":["bar","fn","msg","foo","Error"],"mappings":"mDAAO,SAASA,EAAIC,CAAE,CAAEC,CAAG,EACzBD,EAAGC,EACL,C,yEAFgB,O,oCAAAF,C,+GCEA,O,oCAAAG,C,kBAAT,SAASA,EAAIF,CAAE,CAAEC,CAAG,EACzB,KAAAF,GAAA,EAAIC,EAAIC,EACV,C,wFCFaD,EAAIC,E,WAAJD,EAIT,SAAeC,CAAG,EACpB,MAAM,AAAIE,MAAMF,EAClB,EANiBA,EAMd,OALD,KAAAC,GAAA,EAAIF,EAAIC,E"} -------------------------------------------------------------------------------- /.github/workflows/weekly.yml: -------------------------------------------------------------------------------- 1 | name: Weekly CI 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * 1" # every monday at 00:00 6 | workflow_dispatch: 7 | 8 | env: 9 | RUSTFLAGS: -Dwarnings 10 | 11 | jobs: 12 | weekly-ci: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | rust: [nightly, beta] 17 | 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - run: | 24 | rustup toolchain install ${{ matrix.rust }} --profile minimal --component clippy --no-self-update 25 | rustup default ${{ matrix.rust }} 26 | 27 | - run: cargo clippy --all-features --workspace --tests --examples -- -D clippy::all 28 | 29 | - run: cargo test --workspace --all-features 30 | -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/rspack-composed.bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"rspack.bundle.js","sources":["./src/bar.js","./src/foo.js","./src/index.js"],"sourcesContent":["export function bar(fn, msg) {\n fn(msg);\n}\n","import { bar } from './bar.js';\n\nexport function foo(fn, msg) {\n bar(fn, msg);\n}\n","import { foo } from './foo.js';\n\nfunction wat(fn, msg) {\n foo(fn, msg);\n}\n\nwat(function hello(msg) {\n throw new Error(msg);\n}, 'boop');\n"],"names":["bar","fn","msg","foo","Error"],"mappings":";;mDAAO,SAASA,EAAIC,CAAE,CAAEC,CAAG,EACzBD,EAAGC,EACL,C,yEAFgB,O,oCAAAF,C,+GCEA,O,oCAAAG,C,kBAAT,SAASA,EAAIF,CAAE,CAAEC,CAAG,EACzB,KAAAF,GAAA,EAAIC,EAAIC,EACV,C,wFCFaD,EAAIC,E,WAAJD,EAIT,SAAeC,CAAG,EACpB,MAAM,AAAIE,MAAMF,EAClB,EANiBA,EAMd,OALD,KAAAC,GAAA,EAAIF,EAAIC,E"} -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/esbuild.bundle.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../src/bar.js", "../src/foo.js", "../src/index.js"], 4 | "sourcesContent": ["export function bar(fn, msg) {\n fn(msg);\n}\n", "import { bar } from './bar.js';\n\nexport function foo(fn, msg) {\n bar(fn, msg);\n}\n", "import { foo } from './foo.js';\n\nfunction wat(fn, msg) {\n foo(fn, msg);\n}\n\nwat(function hello(msg) {\n throw new Error(msg);\n}, 'boop');\n"], 5 | "mappings": "MAAO,SAASA,EAAIC,EAAIC,EAAK,CAC3BD,EAAGC,CAAG,CACR,CCAO,SAASC,EAAIC,EAAIC,EAAK,CAC3BC,EAAIF,EAAIC,CAAG,CACb,CCFA,SAASE,EAAIC,EAAIC,EAAK,CACpBC,EAAIF,EAAIC,CAAG,CACb,CAEAF,EAAI,SAAeE,EAAK,CACtB,MAAM,IAAI,MAAMA,CAAG,CACrB,EAAG,MAAM", 6 | "names": ["bar", "fn", "msg", "foo", "fn", "msg", "bar", "wat", "fn", "msg", "foo"] 7 | } 8 | -------------------------------------------------------------------------------- /tests/test_builder.rs: -------------------------------------------------------------------------------- 1 | use sourcemap::SourceMapBuilder; 2 | 3 | #[test] 4 | fn test_builder_into_sourcemap() { 5 | let mut builder = SourceMapBuilder::new(None); 6 | builder.set_source_root(Some("/foo/bar")); 7 | builder.add_source("baz.js"); 8 | builder.add_name("x"); 9 | builder.add_to_ignore_list(0); 10 | 11 | let sm = builder.into_sourcemap(); 12 | assert_eq!(sm.get_source_root(), Some("/foo/bar")); 13 | assert_eq!(sm.get_source(0), Some("/foo/bar/baz.js")); 14 | assert_eq!(sm.get_name(0), Some("x")); 15 | 16 | let expected = br#"{"version":3,"sources":["baz.js"],"sourceRoot":"/foo/bar","names":["x"],"mappings":"","ignoreList":[0]}"#; 17 | let mut output: Vec = vec![]; 18 | sm.to_writer(&mut output).unwrap(); 19 | assert_eq!(output, expected); 20 | } 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: check test 2 | .PHONY: all 3 | 4 | check: style lint 5 | .PHONY: check 6 | 7 | clean: 8 | cargo clean 9 | .PHONY: clean 10 | 11 | build: 12 | cargo build 13 | .PHONY: build 14 | 15 | test: 16 | cargo test --all 17 | cargo test --all --all-features 18 | .PHONY: test 19 | 20 | style: 21 | @rustup component add rustfmt --toolchain stable 2> /dev/null 22 | cargo +stable fmt -- --check 23 | .PHONY: style 24 | 25 | lint: 26 | @rustup component add clippy --toolchain stable 2> /dev/null 27 | cargo +stable clippy --all-features --all --tests --examples -- -D clippy::all 28 | .PHONY: lint 29 | 30 | format: 31 | @rustup component add rustfmt --toolchain stable 2> /dev/null 32 | cargo +stable fmt 33 | .PHONY: format 34 | 35 | docs: build 36 | @cargo doc --no-deps --all-features 37 | .PHONY: docs 38 | 39 | upload-docs: docs 40 | @./upload-docs.sh 41 | .PHONY: upload-docs 42 | -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/rspack-injected.bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"mappings":";;AAAA;AACA;;","names":[],"sources":["pre_injection.js"],"sourcesContent":["!function(){var e={62:function(e,r,t){\"use strict\";function n(e,r){e(r)}Object.defineProperty(r,\"__esModule\",{value:!0}),Object.defineProperty(r,\"bar\",{enumerable:!0,get:function(){return n}})},447:function(e,r,t){\"use strict\";Object.defineProperty(r,\"__esModule\",{value:!0}),Object.defineProperty(r,\"foo\",{enumerable:!0,get:function(){return o}});var n=t(\"62\");function o(e,r){(0,n.bar)(e,r)}},151:function(e,r,t){\"use strict\";Object.defineProperty(r,\"__esModule\",{value:!0});var n,o,u=t(\"447\");n=function(e){throw Error(e)},o=\"boop\",(0,u.foo)(n,o)}},r={};!function t(n){var o=r[n];if(void 0!==o)return o.exports;var u=r[n]={exports:{}};return e[n](u,u.exports,t),u.exports}(\"151\")}();\n//# sourceMappingURL=rspack.bundle.js.map"],"file":null} -------------------------------------------------------------------------------- /tests/fixtures/react-native-metro/README.md: -------------------------------------------------------------------------------- 1 | Similar to the Hermes example, this uses the metro bundler which generates scope mappings. 2 | 3 | ```sh 4 | $ npx react-native bundle --platform android --entry-file input.js --bundle-output output.js --sourcemap-output output.js.map 5 | ``` 6 | 7 | Running the bundled JS through node gives the following stack trace: 8 | 9 | ``` 10 | Error: lets throw! 11 | at foo (output.js:1289:11) 12 | at output.js:1280:19 13 | at loadModuleImplementation (output.js:271:7) 14 | at guardedLoadModule (output.js:163:23) 15 | at metroRequire (output.js:98:75) 16 | at Object. (output.js:1292:1) 17 | at Module._compile (internal/modules/cjs/loader.js:1151:30) 18 | at Object.Module._extensions..js (internal/modules/cjs/loader.js:1171:10) 19 | at Module.load (internal/modules/cjs/loader.js:1000:32) 20 | at Function.Module._load (internal/modules/cjs/loader.js:899:14) 21 | ``` 22 | -------------------------------------------------------------------------------- /tests/fixtures/adjust_mappings/rspack-injected.bundle.js: -------------------------------------------------------------------------------- 1 | 2 | !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},n=(new Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000")}catch(e){}}(); 3 | !function(){var e={62:function(e,r,t){"use strict";function n(e,r){e(r)}Object.defineProperty(r,"__esModule",{value:!0}),Object.defineProperty(r,"bar",{enumerable:!0,get:function(){return n}})},447:function(e,r,t){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),Object.defineProperty(r,"foo",{enumerable:!0,get:function(){return o}});var n=t("62");function o(e,r){(0,n.bar)(e,r)}},151:function(e,r,t){"use strict";Object.defineProperty(r,"__esModule",{value:!0});var n,o,u=t("447");n=function(e){throw Error(e)},o="boop",(0,u.foo)(n,o)}},r={};!function t(n){var o=r[n];if(void 0!==o)return o.exports;var u=r[n]={exports:{}};return e[n](u,u.exports,t),u.exports}("151")}(); 4 | //# sourceMappingURL=rspack.bundle.js.map 5 | //# debugId=00000000-0000-0000-0000-000000000000 6 | -------------------------------------------------------------------------------- /tests/fixtures/react-native-hermes/README.md: -------------------------------------------------------------------------------- 1 | The sourcemap here was generated like this: 2 | 3 | Basically this is the same that the `react-native` gradle file does, see: 4 | https://github.com/facebook/react-native/blob/4185a45be40e014d5e6315c70de00fe5f76c726a/react.gradle#L156-L204 5 | 6 | ```sh 7 | $ npx react-native bundle --platform android --entry-file input.js --bundle-output intermediate.js --sourcemap-output intermediate.js.map 8 | $ hermes -O -emit-binary -output-source-map -out=intermediate2 intermediate.js 9 | $ node react-native/scripts/compose-source-maps.js intermediate.js.map intermediate2.map -o output.map 10 | ``` 11 | 12 | When running the bytecode, we will get the following stacktrace 13 | (probably a bit different when done in react-native properly): 14 | 15 | ``` 16 | Error: lets throw! 17 | at foo (address at unknown:1:11939) 18 | at anonymous (address at unknown:1:11857) 19 | at loadModuleImplementation (address at unknown:1:2608) 20 | at guardedLoadModule (address at unknown:1:1973) 21 | at metroRequire (address at unknown:1:1494) 22 | at global (address at unknown:1:508) 23 | ``` 24 | -------------------------------------------------------------------------------- /examples/read.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs; 3 | use std::io::Read; 4 | 5 | use sourcemap::{decode, DecodedMap, RewriteOptions, SourceMap}; 6 | 7 | fn load_from_reader(mut rdr: R) -> SourceMap { 8 | match decode(&mut rdr).unwrap() { 9 | DecodedMap::Regular(sm) => sm, 10 | DecodedMap::Index(idx) => idx 11 | .flatten_and_rewrite(&RewriteOptions { 12 | load_local_source_contents: true, 13 | ..Default::default() 14 | }) 15 | .unwrap(), 16 | _ => panic!("unexpected sourcemap format"), 17 | } 18 | } 19 | 20 | fn main() { 21 | let args: Vec<_> = env::args().collect(); 22 | let mut f = fs::File::open(&args[1]).unwrap(); 23 | let sm = load_from_reader(&mut f); 24 | 25 | let line = if args.len() > 2 { 26 | args[2].parse::().unwrap() 27 | } else { 28 | 0 29 | }; 30 | let column = if args.len() > 3 { 31 | args[3].parse::().unwrap() 32 | } else { 33 | 0 34 | }; 35 | 36 | let token = sm.lookup_token(line, column).unwrap(); // line-number and column 37 | println!("token: {token}"); 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: Version to release 8 | required: true 9 | force: 10 | description: Force a release even when there are release-blockers (optional) 11 | required: false 12 | 13 | jobs: 14 | release: 15 | runs-on: ubuntu-latest 16 | name: "Release a new version" 17 | steps: 18 | - name: Get auth token 19 | id: token 20 | uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0 21 | with: 22 | app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} 23 | private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} 24 | 25 | - uses: actions/checkout@v4 26 | with: 27 | token: ${{ steps.token.outputs.token }} 28 | fetch-depth: 0 29 | 30 | - name: Prepare release 31 | uses: getsentry/action-prepare-release@v1 32 | env: 33 | GITHUB_TOKEN: ${{ steps.token.outputs.token }} 34 | with: 35 | version: ${{ github.event.inputs.version }} 36 | force: ${{ github.event.inputs.force }} 37 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sourcemap" 3 | version = "9.3.1" 4 | authors = ["Sentry "] 5 | keywords = ["javascript", "sourcemap", "sourcemaps"] 6 | description = "Basic sourcemap handling for Rust" 7 | homepage = "https://github.com/getsentry/rust-sourcemap" 8 | repository = "https://github.com/getsentry/rust-sourcemap" 9 | license = "BSD-3-Clause" 10 | readme = "README.md" 11 | edition = "2018" 12 | autoexamples = true 13 | 14 | include = [ 15 | "/src/**/*.rs", 16 | "/examples/*.rs", 17 | "/*.toml", 18 | "/LICENSE", 19 | "/README.md", 20 | ] 21 | 22 | [package.metadata.docs.rs] 23 | all-features = true 24 | 25 | [dependencies] 26 | url = "2.1.1" 27 | serde = { version = "1.0.104", features = ["derive"] } 28 | serde_json = "1.0.48" 29 | unicode-id-start = "1" 30 | if_chain = "1.0.0" 31 | scroll = { version = "0.12.0", features = ["derive"], optional = true } 32 | data-encoding = "2.3.3" 33 | debugid = {version = "0.8.0", features = ["serde"] } 34 | base64-simd = { version = "0.8" } 35 | bitvec = "1.0.1" 36 | rustc-hash = "2.1.1" 37 | 38 | [features] 39 | ram_bundle = ["scroll"] 40 | 41 | [[example]] 42 | name = "split_ram_bundle" 43 | required-features = ["ram_bundle"] 44 | 45 | [dev-dependencies] 46 | magic_string = "0.3.4" 47 | proptest = "1.2.0" 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sourcemap 2 | 3 | This library implements basic processing of JavaScript sourcemaps. 4 | 5 | ## Installation 6 | 7 | The crate is called sourcemap and you can depend on it via cargo: 8 | 9 | ```toml 10 | [dependencies] 11 | sourcemap = "*" 12 | ``` 13 | 14 | If you want to use the git version: 15 | 16 | ```toml 17 | [dependencies.sourcemap] 18 | git = "https://github.com/getsentry/rust-sourcemap.git" 19 | ``` 20 | 21 | ## Basic Operation 22 | 23 | This crate can load JavaScript sourcemaps from JSON files. It uses 24 | `serde` for parsing of the JSON data. Due to the nature of sourcemaps 25 | the entirety of the file must be loaded into memory which can be quite 26 | memory intensive. 27 | 28 | Usage: 29 | 30 | ```rust 31 | use sourcemap::SourceMap; 32 | let input: &[_] = b"{ 33 | \"version\":3, 34 | \"sources\":[\"coolstuff.js\"], 35 | \"names\":[\"x\",\"alert\"], 36 | \"mappings\":\"AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM\" 37 | }"; 38 | let sm = SourceMap::from_reader(input).unwrap(); 39 | let token = sm.lookup_token(0, 0).unwrap(); // line-number and column 40 | println!("token: {}", token); 41 | ``` 42 | 43 | ## Features 44 | 45 | Functionality of the crate can be turned on and off by feature flags. This is the 46 | current list of feature flags: 47 | 48 | * `ram_bundle`: turns on RAM bundle support 49 | 50 | 51 | License: BSD-3-Clause 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 by Armin Ronacher. 2 | 3 | Some rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | 17 | * The names of the contributors may not be used to endorse or 18 | promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /examples/rewrite.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs; 3 | use std::io::Read; 4 | use std::path::Path; 5 | 6 | use sourcemap::{decode, DecodedMap, RewriteOptions, SourceMap}; 7 | 8 | fn test(sm: &SourceMap) { 9 | for (src_id, source) in sm.sources().enumerate() { 10 | let path = Path::new(source); 11 | if path.is_file() { 12 | let mut f = fs::File::open(path).unwrap(); 13 | let mut contents = String::new(); 14 | if f.read_to_string(&mut contents).ok().is_none() { 15 | continue; 16 | } 17 | if Some(contents.as_str()) != sm.get_source_contents(src_id as u32) { 18 | println!(" !!! {source}"); 19 | } 20 | } 21 | } 22 | } 23 | 24 | fn load_from_reader(mut rdr: R) -> SourceMap { 25 | match decode(&mut rdr).unwrap() { 26 | DecodedMap::Regular(sm) => sm, 27 | DecodedMap::Index(idx) => idx 28 | .flatten_and_rewrite(&RewriteOptions { 29 | load_local_source_contents: true, 30 | ..Default::default() 31 | }) 32 | .unwrap(), 33 | _ => panic!("unexpected sourcemap format"), 34 | } 35 | } 36 | 37 | fn main() { 38 | let args: Vec<_> = env::args().collect(); 39 | let mut f = fs::File::open(&args[1]).unwrap(); 40 | let sm = load_from_reader(&mut f); 41 | println!("before dump"); 42 | test(&sm); 43 | 44 | println!("after dump"); 45 | let mut json: Vec = vec![]; 46 | sm.to_writer(&mut json).unwrap(); 47 | let sm = load_from_reader(json.as_slice()); 48 | test(&sm); 49 | } 50 | -------------------------------------------------------------------------------- /tests/test_regular.rs: -------------------------------------------------------------------------------- 1 | use sourcemap::{SourceMap, SourceMapBuilder}; 2 | 3 | #[test] 4 | fn test_basic_sourcemap() { 5 | let input: &[_] = br#"{ 6 | "version": 3, 7 | "sources": ["coolstuff.js"], 8 | "names": ["x","alert"], 9 | "mappings": "AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM" 10 | }"#; 11 | let sm = SourceMap::from_reader(input).unwrap(); 12 | 13 | assert_eq!( 14 | sm.lookup_token(0, 0).unwrap().to_tuple(), 15 | ("coolstuff.js", 0, 0, None) 16 | ); 17 | assert_eq!( 18 | sm.lookup_token(0, 3).unwrap().to_tuple(), 19 | ("coolstuff.js", 0, 4, Some("x")) 20 | ); 21 | assert_eq!( 22 | sm.lookup_token(0, 24).unwrap().to_tuple(), 23 | ("coolstuff.js", 2, 8, None) 24 | ); 25 | 26 | // Lines continue out to infinity 27 | assert_eq!( 28 | sm.lookup_token(0, 1000).unwrap().to_tuple(), 29 | ("coolstuff.js", 2, 8, None) 30 | ); 31 | 32 | // Token can return prior lines. 33 | assert_eq!( 34 | sm.lookup_token(1000, 0).unwrap().to_tuple(), 35 | ("coolstuff.js", 2, 8, None) 36 | ); 37 | } 38 | 39 | #[test] 40 | fn test_basic_range() { 41 | let mut b = SourceMapBuilder::new(None); 42 | let id = b.add_source("input.js"); 43 | b.add_raw(1, 0, 2, 2, Some(id), None, true); 44 | let sm = b.into_sourcemap(); 45 | 46 | assert_eq!( 47 | sm.lookup_token(1, 0).unwrap().to_tuple(), 48 | ("input.js", 2, 2, None) 49 | ); 50 | assert_eq!( 51 | sm.lookup_token(1, 8).unwrap().to_tuple(), 52 | ("input.js", 2, 10, None) 53 | ); 54 | assert_eq!( 55 | sm.lookup_token(1, 12).unwrap().to_tuple(), 56 | ("input.js", 2, 14, None) 57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - "release/**" 8 | pull_request: 9 | 10 | env: 11 | RUSTFLAGS: -Dwarnings 12 | 13 | jobs: 14 | lints: 15 | name: Lints 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - run: rustup toolchain install stable --profile minimal --component rustfmt --component clippy --no-self-update 21 | 22 | - uses: Swatinem/rust-cache@v2 23 | 24 | - run: cargo fmt --all -- --check 25 | 26 | - run: cargo clippy --all-features --workspace --tests --examples -- -D clippy::all 27 | 28 | unit-test: 29 | name: Unit Tests 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v4 33 | 34 | - run: rustup toolchain install stable --profile minimal --no-self-update 35 | 36 | - uses: Swatinem/rust-cache@v2 37 | 38 | - run: cargo test --workspace 39 | 40 | unit-test-all-features: 41 | name: "Unit Tests - all features" 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v4 45 | 46 | - run: rustup toolchain install stable --profile minimal --no-self-update 47 | 48 | - uses: Swatinem/rust-cache@v2 49 | 50 | - run: cargo test --workspace --all-features --all-targets 51 | 52 | doc-comments: 53 | name: Rust doc comments 54 | runs-on: ubuntu-latest 55 | env: 56 | RUSTDOCFLAGS: -Dwarnings 57 | steps: 58 | - uses: actions/checkout@v4 59 | 60 | - run: rustup toolchain install stable --profile minimal --component rust-docs --no-self-update 61 | 62 | - uses: Swatinem/rust-cache@v2 63 | 64 | - run: cargo doc --workspace --all-features --document-private-items --no-deps 65 | -------------------------------------------------------------------------------- /tests/test_hermes.rs: -------------------------------------------------------------------------------- 1 | use sourcemap::SourceMapHermes; 2 | 3 | #[test] 4 | fn test_react_native_hermes() { 5 | let input: &[_] = include_bytes!("./fixtures/react-native-hermes/output.map"); 6 | let sm = SourceMapHermes::from_reader(input).unwrap(); 7 | let sm = sm.rewrite(&Default::default()).unwrap(); 8 | 9 | // at foo (address at unknown:1:11939) 10 | assert_eq!( 11 | sm.lookup_token(0, 11939).unwrap().to_tuple(), 12 | ("module.js", 1, 10, None) 13 | ); 14 | assert_eq!(sm.get_original_function_name(11939), Some("foo")); 15 | 16 | // at anonymous (address at unknown:1:11857) 17 | assert_eq!( 18 | sm.lookup_token(0, 11857).unwrap().to_tuple(), 19 | ("input.js", 2, 0, None) 20 | ); 21 | assert_eq!(sm.get_original_function_name(11857), Some("")); 22 | 23 | assert_eq!( 24 | sm.lookup_token(0, 11947).unwrap().to_tuple(), 25 | ("module.js", 1, 4, None) 26 | ); 27 | assert_eq!(sm.get_original_function_name(11947), Some("foo")); 28 | } 29 | 30 | #[test] 31 | fn test_react_native_metro() { 32 | let input: &[_] = include_bytes!("./fixtures/react-native-metro/output.js.map"); 33 | let sm = SourceMapHermes::from_reader(input).unwrap(); 34 | // The given map has a bogus `__prelude__` first source, which is being 35 | // dropped (as its not referenced) by rewriting the sourcemap, and thus 36 | // the internal hermes mappings also need to be rewritten accordingly 37 | let sm = sm.rewrite(&Default::default()).unwrap(); 38 | 39 | // at foo (output.js:1289:11) 40 | let token = sm.lookup_token(1288, 10).unwrap(); 41 | assert_eq!(token.to_tuple(), ("module.js", 1, 10, None)); 42 | assert_eq!(sm.get_scope_for_token(token), Some("foo")); 43 | 44 | // at output.js:1280:19 45 | let token = sm.lookup_token(1279, 18).unwrap(); 46 | assert_eq!(token.to_tuple(), ("input.js", 2, 0, None)); 47 | assert_eq!(sm.get_scope_for_token(token), Some("")); 48 | } 49 | -------------------------------------------------------------------------------- /tests/test_detector.rs: -------------------------------------------------------------------------------- 1 | use sourcemap::{is_sourcemap_slice, locate_sourcemap_reference, SourceMapRef}; 2 | 3 | #[test] 4 | fn test_basic_locate() { 5 | let input: &[_] = b"foo();\nbar();\n//# sourceMappingURL=foo.js"; 6 | assert_eq!( 7 | locate_sourcemap_reference(input).unwrap(), 8 | Some(SourceMapRef::Ref("foo.js".into())) 9 | ); 10 | assert_eq!( 11 | locate_sourcemap_reference(input) 12 | .unwrap() 13 | .unwrap() 14 | .get_url(), 15 | "foo.js" 16 | ); 17 | } 18 | 19 | #[test] 20 | fn test_legacy_locate() { 21 | let input: &[_] = b"foo();\nbar();\n//@ sourceMappingURL=foo.js"; 22 | assert_eq!( 23 | locate_sourcemap_reference(input).unwrap(), 24 | Some(SourceMapRef::LegacyRef("foo.js".into())) 25 | ); 26 | assert_eq!( 27 | locate_sourcemap_reference(input) 28 | .unwrap() 29 | .unwrap() 30 | .get_url(), 31 | "foo.js" 32 | ); 33 | } 34 | 35 | #[test] 36 | fn test_no_ref() { 37 | let input: &[_] = b"foo();\nbar();\n// whatever"; 38 | assert_eq!(locate_sourcemap_reference(input).unwrap(), None); 39 | } 40 | 41 | #[test] 42 | fn test_detect_basic_sourcemap() { 43 | let input: &[_] = br#"{ 44 | "version": 3, 45 | "sources": ["coolstuff.js"], 46 | "names": ["x","alert"], 47 | "mappings": "AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM" 48 | }"#; 49 | assert!(is_sourcemap_slice(input)); 50 | } 51 | 52 | #[test] 53 | fn test_detect_bad_sourcemap() { 54 | let input: &[_] = br#"{ 55 | "sources": ["coolstuff.js"], 56 | "names": ["x","alert"] 57 | }"#; 58 | assert!(!is_sourcemap_slice(input)); 59 | } 60 | 61 | #[test] 62 | fn test_detect_basic_sourcemap_with_junk_header() { 63 | let input: &[_] = br#")]}garbage\n 64 | { 65 | "version": 3, 66 | "sources": ["coolstuff.js"], 67 | "names": ["x","alert"], 68 | "mappings": "AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM" 69 | }"#; 70 | assert!(is_sourcemap_slice(input)); 71 | } 72 | -------------------------------------------------------------------------------- /examples/split_ram_bundle.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs; 3 | use std::fs::File; 4 | use std::path::Path; 5 | 6 | use sourcemap::ram_bundle::{split_ram_bundle, RamBundle, RamBundleType}; 7 | use sourcemap::SourceMapIndex; 8 | 9 | const USAGE: &str = " 10 | Usage: 11 | ./split_ram_bundle RAM_BUNDLE SOURCEMAP OUT_DIRECTORY 12 | 13 | This example app splits the given RAM bundle and the sourcemap into a set of 14 | source files and their sourcemaps. 15 | 16 | Both indexed and file RAM bundles are supported. 17 | "; 18 | 19 | fn main() -> Result<(), Box> { 20 | let args: Vec<_> = env::args().collect(); 21 | if args.len() < 4 { 22 | println!("{USAGE}"); 23 | std::process::exit(1); 24 | } 25 | 26 | let bundle_path = Path::new(&args[1]); 27 | let ram_bundle = RamBundle::parse_indexed_from_path(bundle_path) 28 | .or_else(|_| RamBundle::parse_unbundle_from_path(bundle_path))?; 29 | 30 | match ram_bundle.bundle_type() { 31 | RamBundleType::Indexed => println!("Indexed RAM Bundle detected"), 32 | RamBundleType::Unbundle => println!("File RAM Bundle detected"), 33 | } 34 | 35 | let sourcemap_file = File::open(&args[2])?; 36 | let ism = SourceMapIndex::from_reader(sourcemap_file).unwrap(); 37 | 38 | let output_directory = Path::new(&args[3]); 39 | if !output_directory.exists() { 40 | panic!("Directory {} does not exist!", output_directory.display()); 41 | } 42 | 43 | println!( 44 | "Ouput directory: {}", 45 | output_directory.canonicalize()?.display() 46 | ); 47 | let ram_bundle_iter = split_ram_bundle(&ram_bundle, &ism).unwrap(); 48 | for result in ram_bundle_iter { 49 | let (name, sv, sm) = result.unwrap(); 50 | println!("Writing down source: {name}"); 51 | fs::write(output_directory.join(name.clone()), sv.source())?; 52 | 53 | let sourcemap_name = format!("{name}.map"); 54 | println!("Writing down sourcemap: {sourcemap_name}"); 55 | let out_sm = File::create(output_directory.join(sourcemap_name))?; 56 | sm.to_writer(out_sm)?; 57 | } 58 | println!("Done."); 59 | 60 | Ok(()) 61 | } 62 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This library implements basic processing of JavaScript sourcemaps. 2 | //! 3 | //! # Installation 4 | //! 5 | //! The crate is called sourcemap and you can depend on it via cargo: 6 | //! 7 | //! ```toml 8 | //! [dependencies] 9 | //! sourcemap = "*" 10 | //! ``` 11 | //! 12 | //! If you want to use the git version: 13 | //! 14 | //! ```toml 15 | //! [dependencies.sourcemap] 16 | //! git = "https://github.com/getsentry/rust-sourcemap.git" 17 | //! ``` 18 | //! 19 | //! # Basic Operation 20 | //! 21 | //! This crate can load JavaScript sourcemaps from JSON files. It uses 22 | //! `serde` for parsing of the JSON data. Due to the nature of sourcemaps 23 | //! the entirety of the file must be loaded into memory which can be quite 24 | //! memory intensive. 25 | //! 26 | //! Usage: 27 | //! 28 | //! ```rust 29 | //! use sourcemap::SourceMap; 30 | //! let input: &[_] = b"{ 31 | //! \"version\":3, 32 | //! \"sources\":[\"coolstuff.js\"], 33 | //! \"names\":[\"x\",\"alert\"], 34 | //! \"mappings\":\"AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM\" 35 | //! }"; 36 | //! let sm = SourceMap::from_reader(input).unwrap(); 37 | //! let token = sm.lookup_token(0, 0).unwrap(); // line-number and column 38 | //! println!("token: {}", token); 39 | //! ``` 40 | //! 41 | //! # Features 42 | //! 43 | //! Functionality of the crate can be turned on and off by feature flags. This is the 44 | //! current list of feature flags: 45 | //! 46 | //! * `ram_bundle`: turns on RAM bundle support 47 | //! 48 | pub use crate::builder::SourceMapBuilder; 49 | pub use crate::decoder::{decode, decode_data_url, decode_slice}; 50 | pub use crate::detector::{ 51 | is_sourcemap, is_sourcemap_slice, locate_sourcemap_reference, locate_sourcemap_reference_slice, 52 | SourceMapRef, 53 | }; 54 | pub use crate::errors::{Error, Result}; 55 | pub use crate::hermes::SourceMapHermes; 56 | pub use crate::sourceview::SourceView; 57 | pub use crate::types::{ 58 | DecodedMap, NameIter, RawToken, RewriteOptions, SourceContentsIter, SourceIter, SourceMap, 59 | SourceMapIndex, SourceMapSection, SourceMapSectionIter, Token, TokenIter, 60 | }; 61 | pub use crate::utils::make_relative_path; 62 | 63 | mod builder; 64 | mod decoder; 65 | mod detector; 66 | mod encoder; 67 | mod errors; 68 | mod hermes; 69 | mod js_identifiers; 70 | mod jsontypes; 71 | mod sourceview; 72 | mod types; 73 | mod utils; 74 | 75 | #[cfg(feature = "ram_bundle")] 76 | pub mod ram_bundle; 77 | pub mod vlq; 78 | -------------------------------------------------------------------------------- /src/js_identifiers.rs: -------------------------------------------------------------------------------- 1 | /// Returns true if `c` is a valid character for an identifier start. 2 | fn is_valid_start(c: char) -> bool { 3 | c == '$' || c == '_' || c.is_ascii_alphabetic() || { 4 | if c.is_ascii() { 5 | false 6 | } else { 7 | unicode_id_start::is_id_start_unicode(c) 8 | } 9 | } 10 | } 11 | 12 | /// Returns true if `c` is a valid character for an identifier part after start. 13 | fn is_valid_continue(c: char) -> bool { 14 | // As specified by the ECMA-262 spec, U+200C (ZERO WIDTH NON-JOINER) and U+200D 15 | // (ZERO WIDTH JOINER) are format-control characters that are used to make necessary 16 | // distinctions when forming words or phrases in certain languages. They are however 17 | // not considered by UnicodeID to be universally valid identifier characters. 18 | c == '$' || c == '_' || c == '\u{200c}' || c == '\u{200d}' || c.is_ascii_alphanumeric() || { 19 | if c.is_ascii() { 20 | false 21 | } else { 22 | unicode_id_start::is_id_continue_unicode(c) 23 | } 24 | } 25 | } 26 | 27 | fn strip_identifier(s: &str) -> Option<&str> { 28 | let mut iter = s.char_indices(); 29 | // Is the first character a valid starting character 30 | match iter.next() { 31 | Some((_, c)) => { 32 | if !is_valid_start(c) { 33 | return None; 34 | } 35 | } 36 | None => { 37 | return None; 38 | } 39 | }; 40 | // Slice up to the last valid continuation character 41 | let mut end_idx = 0; 42 | for (i, c) in iter { 43 | if is_valid_continue(c) { 44 | end_idx = i; 45 | } else { 46 | break; 47 | } 48 | } 49 | Some(&s[..=end_idx]) 50 | } 51 | 52 | pub fn is_valid_javascript_identifier(s: &str) -> bool { 53 | // check stripping does not reduce the length of the token 54 | strip_identifier(s).map_or(0, |t| t.len()) == s.len() 55 | } 56 | 57 | /// Finds the first valid identifier in the JS Source string given, provided 58 | /// the string begins with the identifier or whitespace. 59 | pub fn get_javascript_token(source_line: &str) -> Option<&str> { 60 | match source_line.split_whitespace().next() { 61 | Some(s) => strip_identifier(s), 62 | None => None, 63 | } 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use super::*; 69 | 70 | #[test] 71 | fn test_is_valid_javascript_identifier() { 72 | // assert_eq!(is_valid_javascript_identifier("foo 123")); 73 | assert!(is_valid_javascript_identifier("foo_$123")); 74 | assert!(!is_valid_javascript_identifier(" foo")); 75 | assert!(!is_valid_javascript_identifier("foo ")); 76 | assert!(!is_valid_javascript_identifier("[123]")); 77 | assert!(!is_valid_javascript_identifier("foo.bar")); 78 | // Should these pass? 79 | // assert!(is_valid_javascript_identifier("foo [bar]")); 80 | assert_eq!(get_javascript_token("foo "), Some("foo")); 81 | assert_eq!(get_javascript_token("f _hi"), Some("f")); 82 | assert_eq!(get_javascript_token("foo.bar"), Some("foo")); 83 | assert_eq!(get_javascript_token("[foo,bar]"), None); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /tests/test_encoder.rs: -------------------------------------------------------------------------------- 1 | use sourcemap::SourceMap; 2 | 3 | #[test] 4 | fn test_basic_sourcemap() { 5 | let input: &[_] = br#"{ 6 | "version": 3, 7 | "sources": ["coolstuff.js"], 8 | "names": ["x","alert"], 9 | "mappings": "AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM" 10 | }"#; 11 | let sm = SourceMap::from_reader(input).unwrap(); 12 | let mut out: Vec = vec![]; 13 | sm.to_writer(&mut out).unwrap(); 14 | 15 | let sm2 = SourceMap::from_reader(&out[..]).unwrap(); 16 | 17 | for (tok1, tok2) in sm.tokens().zip(sm2.tokens()) { 18 | assert_eq!(tok1, tok2); 19 | } 20 | } 21 | 22 | #[test] 23 | fn test_sourcemap_data_url() { 24 | let input: &[_] = br#"{"version":3,"file":"build/foo.min.js","sources":["src/foo.js"],"names":[],"mappings":"AAAA","sourceRoot":"/"}"#; 25 | let sm = SourceMap::from_reader(input).unwrap(); 26 | assert_eq!(sm.to_data_url().unwrap(), "data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVpbGQvZm9vLm1pbi5qcyIsInNvdXJjZXMiOlsic3JjL2Zvby5qcyJdLCJzb3VyY2VSb290IjoiLyIsIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBIn0="); 27 | } 28 | 29 | #[test] 30 | fn test_basic_range() { 31 | let input: &[_] = br#"{ 32 | "version": 3, 33 | "sources": [null], 34 | "names": ["console","log","ab"], 35 | "mappings": "AACAA,QAAQC,GAAG,CAAC,OAAM,OAAM,QACxBD,QAAQC,GAAG,CAAC,QAEZD,QAAQC,GAAG,CAJD;IAACC,IAAI;AAAI,IAKnBF,QAAQC,GAAG,CAAC,YACZD,QAAQC,GAAG,CAAC", 36 | "rangeMappings": "AAB;;g" 37 | }"#; 38 | let sm = SourceMap::from_reader(input).unwrap(); 39 | let mut out: Vec = vec![]; 40 | sm.to_writer(&mut out).unwrap(); 41 | 42 | let sm2 = SourceMap::from_reader(&out[..]).unwrap(); 43 | 44 | for (tok1, tok2) in sm.tokens().zip(sm2.tokens()) { 45 | assert_eq!(tok1, tok2); 46 | } 47 | } 48 | 49 | #[test] 50 | fn test_empty_range() { 51 | let input = r#"{ 52 | "version": 3, 53 | "sources": [null], 54 | "names": ["console","log","ab"], 55 | "mappings": "AACAA,QAAQC,GAAG,CAAC,OAAM,OAAM,QACxBD,QAAQC,GAAG,CAAC,QAEZD,QAAQC,GAAG,CAJD;IAACC,IAAI;AAAI,IAKnBF,QAAQC,GAAG,CAAC,YACZD,QAAQC,GAAG,CAAC", 56 | "rangeMappings": "" 57 | }"#; 58 | let sm = SourceMap::from_reader(input.as_bytes()).unwrap(); 59 | let mut out: Vec = vec![]; 60 | sm.to_writer(&mut out).unwrap(); 61 | 62 | let out = String::from_utf8(out).unwrap(); 63 | assert!(!out.contains("rangeMappings")); 64 | } 65 | 66 | #[test] 67 | fn test_sourcemap_serializes_camel_case_debug_id() { 68 | const DEBUG_ID: &str = "0123456789abcdef0123456789abcdef"; 69 | let input = format!( 70 | r#"{{ 71 | "version": 3, 72 | "sources": [], 73 | "names": [], 74 | "mappings": "", 75 | "debug_id": "{}" 76 | }}"#, 77 | DEBUG_ID 78 | ); 79 | 80 | let sm = SourceMap::from_reader(input.as_bytes()).unwrap(); 81 | let expected = sm.get_debug_id().expect("debug id parsed").to_string(); 82 | let mut out: Vec = vec![]; 83 | sm.to_writer(&mut out).unwrap(); 84 | let serialized = String::from_utf8(out).unwrap(); 85 | 86 | assert!( 87 | serialized.contains(&format!(r#""debugId":"{}""#, expected)), 88 | "expected camelCase debugId in {}", 89 | serialized 90 | ); 91 | assert!( 92 | !serialized.contains("debug_id"), 93 | "unexpected snake_case key in {}", 94 | serialized 95 | ); 96 | } 97 | -------------------------------------------------------------------------------- /tests/test_namemap.rs: -------------------------------------------------------------------------------- 1 | use sourcemap::{SourceMap, SourceView}; 2 | 3 | #[test] 4 | fn test_basic_name_mapping() { 5 | let input: &[_] = br#"{"version":3,"file":"test.min.js","sources":["test.js"],"names":["makeAFailure","testingStuff","Error","onSuccess","data","onFailure","invoke","cb","failed","test","value"],"mappings":"AAAA,GAAIA,cAAe,WACjB,QAASC,KACP,GAAIA,GAAe,EACnB,MAAM,IAAIC,OAAMD,GAGlB,QAASE,GAAUC,GACjBH,IAGF,QAASI,GAAUD,GACjB,KAAM,IAAIF,OAAM,WAGlB,QAASI,GAAOF,GACd,GAAIG,GAAK,IACT,IAAIH,EAAKI,OAAQ,CACfD,EAAKF,MACA,CACLE,EAAKJ,EAEPI,EAAGH,GAGL,QAASK,KACP,GAAIL,IAAQI,OAAQ,KAAME,MAAO,GACjCJ,GAAOF,GAGT,MAAOK","sourcesContent":["var makeAFailure = (function() {\n function testingStuff() {\n var testingStuff = 42;\n throw new Error(testingStuff);\n }\n\n function onSuccess(data) {\n testingStuff();\n }\n\n function onFailure(data) {\n throw new Error('failed!');\n }\n\n function invoke(data) {\n var cb = null;\n if (data.failed) {\n cb = onFailure;\n } else {\n cb = onSuccess;\n }\n cb(data);\n }\n\n function test() {\n var data = {failed: true, value: 42};\n invoke(data);\n }\n\n return test;\n})();\n"]}"#; 6 | let minified_file = r#"var makeAFailure=function(){function n(){var n=42;throw new Error(n)}function r(r){n()}function e(n){throw new Error("failed!")}function i(n){var i=null;if(n.failed){i=e}else{i=r}i(n)}function u(){var n={failed:true,value:42};i(n)}return u}();"#; 7 | let sv = SourceView::new(minified_file.into()); 8 | let sm = SourceMap::from_reader(input).unwrap(); 9 | 10 | let name = sm.get_original_function_name(0, 107, "e", &sv); 11 | assert_eq!(name, Some("onFailure")); 12 | 13 | // a stacktrae 14 | let locs = &[ 15 | (0, 107, "e", "onFailure"), 16 | (0, 179, "i", "invoke"), 17 | (0, 226, "u", "test"), 18 | ]; 19 | 20 | for &(line, col, minified_name, original_name) in locs { 21 | let name = sm.get_original_function_name(line, col, minified_name, &sv); 22 | assert_eq!(name, Some(original_name)); 23 | } 24 | } 25 | 26 | #[test] 27 | fn test_unicode_mapping() { 28 | let input = r#"{"version":3,"file":"test.min.js","sources":["test.js"],"names":["makeAFailure","onSuccess","data","onFailure","Error","invoke","cb","failed","㮏","value"],"mappings":"AAAA,GAAIA,cAAe,WACjB,QAASC,GAAUC,IAEnB,QAASC,GAAUD,IACjB,WACE,KAAM,IAAIE,OAAM,eAIpB,QAASC,GAAOH,GACd,GAAII,GAAK,IACT,IAAIJ,EAAKK,OAAQ,CACfD,EAAKH,MACA,CACLG,EAAKL,EAEPK,EAAGJ,GAGI,QAASM,KAChB,GAAIN,IAAQK,OAAQ,KAAME,MAAO,GACjCJ,GAAOH,GAGT,MAAOM","sourcesContent":["var makeAFailure = (function() {\n function onSuccess(data) {}\n\n function onFailure(data) {\n (function() {\n throw new Error('failed!');\n })();\n }\n\n function invoke(data) {\n var cb = null;\n if (data.failed) {\n cb = onFailure;\n } else {\n cb = onSuccess;\n }\n cb(data);\n }\n\n /* 😍 */ function 㮏() {\n var data = {failed: true, value: 42};\n invoke(data);\n }\n\n return 㮏;\n})();\n"]}"#.as_bytes(); 29 | let minified_file = r#"var makeAFailure=function(){function n(n){}function e(n){(function(){throw new Error("failed!")})()}function i(i){var r=null;if(i.failed){r=e}else{r=n}r(i)}function r(){var n={failed:true,value:42};i(n)}return r}();"#; 30 | let sv = SourceView::new(minified_file.into()); 31 | let sm = SourceMap::from_reader(input).unwrap(); 32 | 33 | // stacktrace 34 | let locs = &[ 35 | (0, 75, "", None), 36 | (0, 97, "e", Some("onFailure")), 37 | (0, 151, "i", Some("invoke")), 38 | (0, 198, "r", Some("㮏")), 39 | ]; 40 | 41 | for &(line, col, minified_name, original_name_match) in locs { 42 | let name = sm.get_original_function_name(line, col, minified_name, &sv); 43 | assert_eq!(name, original_name_match); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/detector.rs: -------------------------------------------------------------------------------- 1 | use std::io::{BufRead, BufReader, Read}; 2 | use std::str; 3 | 4 | use crate::decoder::{decode_data_url, strip_junk_header, StripHeaderReader}; 5 | use crate::errors::Result; 6 | use crate::jsontypes::MinimalRawSourceMap; 7 | use crate::types::DecodedMap; 8 | 9 | use url::Url; 10 | 11 | /// Represents a reference to a sourcemap 12 | #[derive(PartialEq, Eq, Debug)] 13 | pub enum SourceMapRef { 14 | /// A regular URL reference 15 | Ref(String), 16 | /// A legacy URL reference 17 | LegacyRef(String), 18 | } 19 | 20 | fn resolve_url(ref_url: &str, minified_url: &Url) -> Option { 21 | minified_url.join(ref_url).ok() 22 | } 23 | 24 | impl SourceMapRef { 25 | /// Return the URL of the reference 26 | pub fn get_url(&self) -> &str { 27 | match *self { 28 | SourceMapRef::Ref(ref u) => u.as_str(), 29 | SourceMapRef::LegacyRef(ref u) => u.as_str(), 30 | } 31 | } 32 | 33 | /// Resolves the reference. 34 | /// 35 | /// The given minified URL needs to be the URL of the minified file. The 36 | /// result is the fully resolved URL of where the source map can be located. 37 | pub fn resolve(&self, minified_url: &str) -> Option { 38 | let url = self.get_url(); 39 | if url.starts_with("data:") { 40 | return None; 41 | } 42 | resolve_url(url, &Url::parse(minified_url).ok()?).map(|x| x.to_string()) 43 | } 44 | 45 | /// Resolves the reference against a local file path 46 | /// 47 | /// This is similar to `resolve` but operates on file paths. 48 | #[cfg(any(unix, windows, target_os = "redox"))] 49 | pub fn resolve_path(&self, minified_path: &std::path::Path) -> Option { 50 | let url = self.get_url(); 51 | if url.starts_with("data:") { 52 | return None; 53 | } 54 | resolve_url(url, &Url::from_file_path(minified_path).ok()?) 55 | .and_then(|x| x.to_file_path().ok()) 56 | } 57 | 58 | /// Load an embedded sourcemap if there is a data URL. 59 | pub fn get_embedded_sourcemap(&self) -> Result> { 60 | let url = self.get_url(); 61 | if url.starts_with("data:") { 62 | Ok(Some(decode_data_url(url)?)) 63 | } else { 64 | Ok(None) 65 | } 66 | } 67 | } 68 | 69 | /// Locates a sourcemap reference 70 | /// 71 | /// Given a reader to a JavaScript file this tries to find the correct 72 | /// sourcemap reference comment and return it. 73 | pub fn locate_sourcemap_reference(rdr: R) -> Result> { 74 | for line in BufReader::new(rdr).lines() { 75 | let line = line?; 76 | if line.starts_with("//# sourceMappingURL=") || line.starts_with("//@ sourceMappingURL=") { 77 | let url = str::from_utf8(&line.as_bytes()[21..])?.trim().to_owned(); 78 | if line.starts_with("//@") { 79 | return Ok(Some(SourceMapRef::LegacyRef(url))); 80 | } else { 81 | return Ok(Some(SourceMapRef::Ref(url))); 82 | } 83 | } 84 | } 85 | Ok(None) 86 | } 87 | 88 | /// Locates a sourcemap reference in a slice 89 | /// 90 | /// This is an alternative to `locate_sourcemap_reference` that operates 91 | /// on slices. 92 | pub fn locate_sourcemap_reference_slice(slice: &[u8]) -> Result> { 93 | locate_sourcemap_reference(slice) 94 | } 95 | 96 | fn is_sourcemap_common(rsm: MinimalRawSourceMap) -> bool { 97 | (rsm.version.is_some() || rsm.file.is_some()) 98 | && ((rsm.sources.is_some() 99 | || rsm.source_root.is_some() 100 | || rsm.sources_content.is_some() 101 | || rsm.names.is_some()) 102 | && rsm.mappings.is_some()) 103 | || rsm.sections.is_some() 104 | } 105 | 106 | fn is_sourcemap_impl(rdr: R) -> Result { 107 | let mut rdr = StripHeaderReader::new(rdr); 108 | let mut rdr = BufReader::new(&mut rdr); 109 | let rsm: MinimalRawSourceMap = serde_json::from_reader(&mut rdr)?; 110 | Ok(is_sourcemap_common(rsm)) 111 | } 112 | 113 | fn is_sourcemap_slice_impl(slice: &[u8]) -> Result { 114 | let content = strip_junk_header(slice)?; 115 | let rsm: MinimalRawSourceMap = serde_json::from_slice(content)?; 116 | Ok(is_sourcemap_common(rsm)) 117 | } 118 | 119 | /// Checks if a valid sourcemap can be read from the given reader 120 | pub fn is_sourcemap(rdr: R) -> bool { 121 | is_sourcemap_impl(rdr).unwrap_or(false) 122 | } 123 | 124 | /// Checks if the given byte slice contains a sourcemap 125 | pub fn is_sourcemap_slice(slice: &[u8]) -> bool { 126 | is_sourcemap_slice_impl(slice).unwrap_or(false) 127 | } 128 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::error; 2 | use std::fmt; 3 | use std::io; 4 | use std::str; 5 | use std::string; 6 | 7 | /// Represents results from this library 8 | pub type Result = std::result::Result; 9 | 10 | /// Represents different failure cases 11 | #[derive(Debug)] 12 | pub enum Error { 13 | /// a std::io error 14 | Io(io::Error), 15 | #[cfg(feature = "ram_bundle")] 16 | /// a scroll error 17 | Scroll(scroll::Error), 18 | /// a std::str::Utf8Error 19 | Utf8(str::Utf8Error), 20 | /// a JSON parsing related failure 21 | BadJson(serde_json::Error), 22 | /// a VLQ string was malformed and data was left over 23 | VlqLeftover, 24 | /// a VLQ string was empty and no values could be decoded. 25 | VlqNoValues, 26 | /// Overflow in Vlq handling 27 | VlqOverflow, 28 | /// a mapping segment had an unsupported size 29 | BadSegmentSize(u32), 30 | /// a reference to a non existing source was encountered 31 | BadSourceReference(u32), 32 | /// a reference to a non existing name was encountered 33 | BadNameReference(u32), 34 | /// Indicates that an incompatible sourcemap format was encountered 35 | IncompatibleSourceMap, 36 | /// Indicates an invalid data URL 37 | InvalidDataUrl, 38 | /// Flatten failed 39 | CannotFlatten(String), 40 | /// The magic of a RAM bundle did not match 41 | InvalidRamBundleMagic, 42 | /// The RAM bundle index was malformed 43 | InvalidRamBundleIndex, 44 | /// A RAM bundle entry was invalid 45 | InvalidRamBundleEntry, 46 | /// Tried to operate on a non RAM bundle file 47 | NotARamBundle, 48 | /// Range mapping index is invalid 49 | InvalidRangeMappingIndex(data_encoding::DecodeError), 50 | 51 | InvalidBase64(char), 52 | } 53 | 54 | impl From for Error { 55 | fn from(err: io::Error) -> Error { 56 | Error::Io(err) 57 | } 58 | } 59 | 60 | #[cfg(feature = "ram_bundle")] 61 | impl From for Error { 62 | fn from(err: scroll::Error) -> Self { 63 | Error::Scroll(err) 64 | } 65 | } 66 | 67 | impl From for Error { 68 | fn from(err: string::FromUtf8Error) -> Error { 69 | From::from(err.utf8_error()) 70 | } 71 | } 72 | 73 | impl From for Error { 74 | fn from(err: str::Utf8Error) -> Error { 75 | Error::Utf8(err) 76 | } 77 | } 78 | 79 | impl From for Error { 80 | fn from(err: serde_json::Error) -> Error { 81 | Error::BadJson(err) 82 | } 83 | } 84 | 85 | impl From for Error { 86 | fn from(err: data_encoding::DecodeError) -> Error { 87 | Error::InvalidRangeMappingIndex(err) 88 | } 89 | } 90 | 91 | impl error::Error for Error { 92 | fn cause(&self) -> Option<&dyn error::Error> { 93 | match *self { 94 | Error::Io(ref err) => Some(err), 95 | #[cfg(feature = "ram_bundle")] 96 | Error::Scroll(ref err) => Some(err), 97 | Error::Utf8(ref err) => Some(err), 98 | Error::BadJson(ref err) => Some(err), 99 | _ => None, 100 | } 101 | } 102 | } 103 | 104 | impl fmt::Display for Error { 105 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 106 | match *self { 107 | Error::Io(ref msg) => write!(f, "{msg}"), 108 | Error::Utf8(ref msg) => write!(f, "{msg}"), 109 | Error::BadJson(ref err) => write!(f, "bad json: {err}"), 110 | #[cfg(feature = "ram_bundle")] 111 | Error::Scroll(ref err) => write!(f, "parse error: {err}"), 112 | Error::VlqLeftover => write!(f, "leftover cur/shift in vlq decode"), 113 | Error::VlqNoValues => write!(f, "vlq decode did not produce any values"), 114 | Error::VlqOverflow => write!(f, "vlq decode caused an overflow"), 115 | Error::BadSegmentSize(size) => write!(f, "got {size} segments, expected 4 or 5"), 116 | Error::BadSourceReference(id) => write!(f, "bad reference to source #{id}"), 117 | Error::BadNameReference(id) => write!(f, "bad reference to name #{id}"), 118 | Error::IncompatibleSourceMap => write!(f, "encountered incompatible sourcemap format"), 119 | Error::InvalidDataUrl => write!(f, "the provided data URL is invalid"), 120 | Error::CannotFlatten(ref msg) => { 121 | write!(f, "cannot flatten the indexed sourcemap: {msg}") 122 | } 123 | Error::InvalidRamBundleMagic => write!(f, "invalid magic number for ram bundle"), 124 | Error::InvalidRamBundleIndex => write!(f, "invalid module index in ram bundle"), 125 | Error::InvalidRamBundleEntry => write!(f, "invalid ram bundle module entry"), 126 | Error::NotARamBundle => write!(f, "not a ram bundle"), 127 | Error::InvalidRangeMappingIndex(err) => write!(f, "invalid range mapping index: {err}"), 128 | Error::InvalidBase64(c) => write!(f, "invalid base64 character: {c}"), 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/jsontypes.rs: -------------------------------------------------------------------------------- 1 | use debugid::DebugId; 2 | use serde::de::IgnoredAny; 3 | use serde::{Deserialize, Deserializer, Serialize}; 4 | use serde_json::Value; 5 | use std::fmt::Debug; 6 | 7 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 8 | pub struct RawSectionOffset { 9 | pub line: u32, 10 | pub column: u32, 11 | } 12 | 13 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 14 | pub struct RawSection { 15 | pub offset: RawSectionOffset, 16 | pub url: Option, 17 | pub map: Option>, 18 | } 19 | 20 | #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] 21 | pub struct FacebookScopeMapping { 22 | pub names: Vec, 23 | pub mappings: String, 24 | } 25 | 26 | // Each element here is matching the `sources` of the outer SourceMap. 27 | // It has a list of metadata, the first one of which is a *function map*, 28 | // containing scope information as a nested source map. 29 | // See the decoder in `hermes.rs` for details. 30 | pub type FacebookSources = Option>>>; 31 | 32 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 33 | pub struct RawSourceMap { 34 | pub version: Option, 35 | #[serde(skip_serializing_if = "Option::is_none")] 36 | pub file: Option, 37 | pub sources: Option>>, 38 | #[serde(rename = "sourceRoot", skip_serializing_if = "Option::is_none")] 39 | pub source_root: Option, 40 | #[serde(rename = "sourcesContent", skip_serializing_if = "Option::is_none")] 41 | pub sources_content: Option>>, 42 | #[serde(skip_serializing_if = "Option::is_none")] 43 | pub sections: Option>, 44 | #[serde(skip_serializing_if = "Option::is_none")] 45 | pub names: Option>, 46 | #[serde(rename = "rangeMappings", skip_serializing_if = "Option::is_none")] 47 | pub range_mappings: Option, 48 | #[serde(skip_serializing_if = "Option::is_none")] 49 | pub mappings: Option, 50 | #[serde(rename = "ignoreList", skip_serializing_if = "Option::is_none")] 51 | pub ignore_list: Option>, 52 | #[serde(skip_serializing_if = "Option::is_none")] 53 | pub x_facebook_offsets: Option>>, 54 | #[serde(skip_serializing_if = "Option::is_none")] 55 | pub x_metro_module_paths: Option>, 56 | #[serde(skip_serializing_if = "Option::is_none")] 57 | pub x_facebook_sources: FacebookSources, 58 | #[serde(flatten)] 59 | pub debug_id: DebugIdField, 60 | } 61 | 62 | #[derive(Deserialize)] 63 | pub struct MinimalRawSourceMap { 64 | pub version: Option, 65 | pub file: Option, 66 | pub sources: Option, 67 | #[serde(rename = "sourceRoot")] 68 | pub source_root: Option, 69 | #[serde(rename = "sourcesContent")] 70 | pub sources_content: Option, 71 | pub sections: Option, 72 | pub names: Option, 73 | pub mappings: Option, 74 | } 75 | 76 | /// This struct represents a `RawSourceMap`'s debug ID fields. 77 | /// 78 | /// The reason this exists as a seperate struct is so that we can have custom deserialization 79 | /// logic, which can read both the legacy snake_case debug_id and the new camelCase debugId 80 | /// fields. In case both are provided, the camelCase field takes precedence. 81 | /// 82 | /// The field is always serialized as `debugId`. 83 | #[derive(Serialize, Clone, PartialEq, Debug, Default)] 84 | pub(crate) struct DebugIdField { 85 | #[serde(rename = "debugId", skip_serializing_if = "Option::is_none")] 86 | value: Option, 87 | } 88 | 89 | impl<'de> Deserialize<'de> for DebugIdField { 90 | fn deserialize(deserializer: D) -> Result 91 | where 92 | D: Deserializer<'de>, 93 | { 94 | // We cannot use serde(alias), as that would cause an error when both fields are present. 95 | 96 | #[derive(Deserialize)] 97 | struct Helper { 98 | #[serde(rename = "debugId")] 99 | camel: Option, 100 | #[serde(rename = "debug_id")] 101 | legacy: Option, 102 | } 103 | 104 | let Helper { camel, legacy } = Helper::deserialize(deserializer)?; 105 | Ok(camel.or(legacy).into()) 106 | } 107 | } 108 | 109 | impl From> for DebugIdField { 110 | fn from(value: Option) -> Self { 111 | Self { value } 112 | } 113 | } 114 | 115 | impl From for Option { 116 | fn from(value: DebugIdField) -> Self { 117 | value.value 118 | } 119 | } 120 | 121 | #[cfg(test)] 122 | mod tests { 123 | use super::*; 124 | use serde_json::json; 125 | 126 | fn parse_debug_id(input: &str) -> DebugId { 127 | input.parse().expect("valid debug id") 128 | } 129 | 130 | fn empty_sourcemap() -> RawSourceMap { 131 | serde_json::from_value::(serde_json::json!({})) 132 | .expect("can deserialize empty JSON to RawSourceMap") 133 | } 134 | 135 | #[test] 136 | fn raw_sourcemap_serializes_camel_case_debug_id() { 137 | let camel = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; 138 | let raw = RawSourceMap { 139 | debug_id: Some(parse_debug_id(camel)).into(), 140 | ..empty_sourcemap() 141 | }; 142 | 143 | let value = serde_json::to_value(raw).expect("should serialize without error"); 144 | let obj = value.as_object().expect("should be an object"); 145 | assert!(obj.get("debug_id").is_none()); 146 | assert_eq!(obj.get("debugId"), Some(&json!(parse_debug_id(camel)))); 147 | } 148 | 149 | #[test] 150 | fn raw_sourcemap_prefers_camel_case_on_deserialize() { 151 | let legacy = "ffffffffffffffffffffffffffffffff"; 152 | let camel = "00000000000000000000000000000000"; 153 | let json = serde_json::json!({ 154 | "debug_id": legacy, 155 | "debugId": camel 156 | }); 157 | let raw: RawSourceMap = 158 | serde_json::from_value(json).expect("can deserialize as RawSourceMap"); 159 | let value: Option = raw.debug_id.into(); 160 | assert_eq!(value, Some(parse_debug_id(camel))); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /cli/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::PathBuf; 3 | use std::process; 4 | 5 | use argh::FromArgs; 6 | use sourcemap::{DecodedMap, SourceView, Token}; 7 | 8 | /// Utility for working with source maps. 9 | #[derive(FromArgs, Debug)] 10 | pub struct Cli { 11 | /// the source map to process 12 | #[argh(option, short = 's')] 13 | sourcemap: Option, 14 | /// the minified input file 15 | #[argh(option, short = 'm')] 16 | minified_file: Option, 17 | /// the 0 indexed line number 18 | #[argh(option, short = 'L')] 19 | line0: Option, 20 | /// the 1 indexed line number 21 | #[argh(option, short = 'l')] 22 | line: Option, 23 | /// the 0 indexed column number 24 | #[argh(option, short = 'C')] 25 | column0: Option, 26 | /// the 1 indexed column number 27 | #[argh(option, short = 'c')] 28 | column: Option, 29 | /// the function name that should be mapped 30 | #[argh(option, short = 'f')] 31 | function: Option, 32 | /// dumps all tokens 33 | #[argh(switch)] 34 | dump: bool, 35 | } 36 | 37 | impl Cli { 38 | /// Retrurns the zero indexed line 39 | fn lookup_pos(&self) -> Option<(u32, u32)> { 40 | Some(( 41 | self.line0.unwrap_or_else(|| self.line.map_or(0, |x| x - 1)), 42 | self.column0.or_else(|| self.column.map(|x| x - 1))?, 43 | )) 44 | } 45 | } 46 | 47 | fn bail(msg: &str) -> ! { 48 | eprintln!("error: {}", msg); 49 | process::exit(1); 50 | } 51 | 52 | fn print_token( 53 | sm: &DecodedMap, 54 | token: &Token<'_>, 55 | line: u32, 56 | column: u32, 57 | func: Option<&str>, 58 | sv: Option<&SourceView>, 59 | ) { 60 | if let Some(name) = token.get_name() { 61 | println!(" name: {:?}", name); 62 | } else { 63 | println!(" name: not found"); 64 | } 65 | if let Some(source) = token.get_source() { 66 | println!(" source file: {:?}", source); 67 | } else { 68 | println!(" source file: not found"); 69 | } 70 | println!(" source line: {}", token.get_src_line()); 71 | println!(" source column: {}", token.get_src_col()); 72 | println!(" minified line: {}", token.get_dst_line()); 73 | println!(" minified column: {}", token.get_dst_col()); 74 | if let Some(name) = sm.get_original_function_name(line, column, func, sv) { 75 | println!(" original function: {:?}", name); 76 | } else { 77 | println!(" original function: not found"); 78 | } 79 | if let Some(line) = token 80 | .get_source_view() 81 | .and_then(|sv| sv.get_line(token.get_src_line())) 82 | { 83 | println!(" source line:"); 84 | println!(" {}", line.trim()); 85 | } else if token.get_source_view().is_none() { 86 | println!(" cannot find source"); 87 | } else { 88 | println!(" cannot find source line"); 89 | } 90 | } 91 | 92 | fn main() { 93 | if let Err(err) = execute() { 94 | eprintln!("error: {}", err); 95 | } 96 | } 97 | 98 | fn execute() -> Result<(), Box> { 99 | let args: Cli = argh::from_env(); 100 | 101 | let sv = if let Some(ref path) = args.minified_file { 102 | Some(SourceView::from_string(fs::read_to_string(&path)?)) 103 | } else { 104 | None 105 | }; 106 | 107 | let (sm, sourcemap_path) = match (&args.sourcemap, &sv) { 108 | (&Some(ref path), _) => (None, Some(path.to_path_buf())), 109 | (&None, &Some(ref sv)) => { 110 | if let Some(smref) = sv.sourcemap_reference()? { 111 | if let Some(sm) = smref.get_embedded_sourcemap()? { 112 | (Some(sm), None) 113 | } else { 114 | ( 115 | None, 116 | smref.resolve_path(args.minified_file.as_ref().unwrap()), 117 | ) 118 | } 119 | } else { 120 | bail("missing sourcemap reference"); 121 | } 122 | } 123 | _ => bail("sourcemap not provided"), 124 | }; 125 | 126 | let sm = if let Some(sm) = sm { 127 | sm 128 | } else { 129 | sourcemap::decode_slice(&fs::read(&sourcemap_path.as_ref().unwrap())?)? 130 | }; 131 | 132 | let ty = match sm { 133 | DecodedMap::Regular(..) => "regular", 134 | DecodedMap::Index(..) => "indexed", 135 | DecodedMap::Hermes(..) => "hermes", 136 | }; 137 | if let Some(ref path) = sourcemap_path { 138 | println!("source map path: {:?}", path); 139 | } else { 140 | println!("embedded source map"); 141 | } 142 | println!("source map type: {}", ty); 143 | 144 | // perform a lookup 145 | if let Some((line, column)) = args.lookup_pos() { 146 | println!("lookup line: {}, column: {}:", line, column); 147 | if let Some(token) = sm.lookup_token(line, column) { 148 | print_token( 149 | &sm, 150 | &token, 151 | line, 152 | column, 153 | args.function.as_deref(), 154 | sv.as_ref(), 155 | ); 156 | } else { 157 | println!(" - no match"); 158 | } 159 | } 160 | 161 | // dump all tokens 162 | if args.dump { 163 | println!("tokens:"); 164 | let converted_map; 165 | let token_iter = match sm { 166 | DecodedMap::Regular(ref sm) => sm.tokens(), 167 | DecodedMap::Index(ref sm) => { 168 | converted_map = sm.flatten()?; 169 | converted_map.tokens() 170 | } 171 | DecodedMap::Hermes(ref sm) => sm.tokens(), 172 | }; 173 | for token in token_iter { 174 | println!( 175 | " {}:{} -> {}:{}:{}{}", 176 | token.get_dst_line(), 177 | token.get_dst_col(), 178 | token.get_source().unwrap_or(""), 179 | token.get_src_line(), 180 | token.get_src_col(), 181 | if let Some(name) = token.get_name() { 182 | format!(" [{}]", name) 183 | } else { 184 | "".to_string() 185 | }, 186 | ); 187 | } 188 | } 189 | 190 | Ok(()) 191 | } 192 | -------------------------------------------------------------------------------- /src/vlq.rs: -------------------------------------------------------------------------------- 1 | //! Implements utilities for dealing with the sourcemap vlq encoding. 2 | use crate::errors::{Error, Result}; 3 | 4 | const B64_CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 5 | const B64: [i8; 256] = [ 6 | -1, 7 | -1, 8 | -1, 9 | -1, 10 | -1, 11 | -1, 12 | -1, 13 | -1, 14 | -1, 15 | -1, 16 | -1, 17 | -1, 18 | -1, 19 | -1, 20 | -1, 21 | -1, 22 | -1, 23 | -1, 24 | -1, 25 | -1, 26 | -1, 27 | -1, 28 | -1, 29 | -1, 30 | -1, 31 | -1, 32 | -1, 33 | -1, 34 | -1, 35 | -1, 36 | -1, 37 | -1, 38 | -1, 39 | -1, 40 | -1, 41 | -1, 42 | -1, 43 | -1, 44 | -1, 45 | -1, 46 | -1, 47 | -1, 48 | -1, 49 | 62, 50 | -1, 51 | -1, 52 | -1, 53 | 63, 54 | 52, 55 | 53, 56 | 54, 57 | 55, 58 | 56, 59 | 57, 60 | 58, 61 | 59, 62 | 60, 63 | 61, 64 | -1, 65 | -1, 66 | -1, 67 | -1, 68 | -1, 69 | -1, 70 | -1, 71 | 0, 72 | 1, 73 | 2, 74 | 3, 75 | 4, 76 | 5, 77 | 6, 78 | 7, 79 | 8, 80 | 9, 81 | 10, 82 | 11, 83 | 12, 84 | 13, 85 | 14, 86 | 15, 87 | 16, 88 | 17, 89 | 18, 90 | 19, 91 | 20, 92 | 21, 93 | 22, 94 | 23, 95 | 24, 96 | 25, 97 | -1, 98 | -1, 99 | -1, 100 | -1, 101 | -1, 102 | -1, 103 | 26, 104 | 27, 105 | 28, 106 | 29, 107 | 30, 108 | 31, 109 | 32, 110 | 33, 111 | 34, 112 | 35, 113 | 36, 114 | 37, 115 | 38, 116 | 39, 117 | 40, 118 | 41, 119 | 42, 120 | 43, 121 | 44, 122 | 45, 123 | 46, 124 | 47, 125 | 48, 126 | 49, 127 | 50, 128 | 51, 129 | -1, 130 | -1, 131 | -1, 132 | -1, 133 | -1 - 1, 134 | -1, 135 | -1, 136 | -1, 137 | -1, 138 | -1, 139 | -1, 140 | -1, 141 | -1, 142 | -1, 143 | -1, 144 | -1, 145 | -1, 146 | -1, 147 | -1, 148 | -1, 149 | -1, 150 | -1, 151 | -1, 152 | -1, 153 | -1, 154 | -1, 155 | -1, 156 | -1, 157 | -1, 158 | -1, 159 | -1, 160 | -1, 161 | -1, 162 | -1, 163 | -1, 164 | -1, 165 | -1, 166 | -1, 167 | -1, 168 | -1, 169 | -1, 170 | -1, 171 | -1, 172 | -1, 173 | -1, 174 | -1, 175 | -1, 176 | -1, 177 | -1, 178 | -1, 179 | -1, 180 | -1, 181 | -1, 182 | -1, 183 | -1, 184 | -1, 185 | -1, 186 | -1, 187 | -1, 188 | -1, 189 | -1, 190 | -1, 191 | -1, 192 | -1, 193 | -1, 194 | -1, 195 | -1, 196 | -1, 197 | -1, 198 | -1, 199 | -1, 200 | -1, 201 | -1, 202 | -1, 203 | -1, 204 | -1, 205 | -1, 206 | -1, 207 | -1, 208 | -1, 209 | -1, 210 | -1, 211 | -1, 212 | -1, 213 | -1, 214 | -1, 215 | -1, 216 | -1, 217 | -1, 218 | -1, 219 | -1, 220 | -1, 221 | -1, 222 | -1, 223 | -1, 224 | -1, 225 | -1, 226 | -1, 227 | -1, 228 | -1, 229 | -1, 230 | -1, 231 | -1, 232 | -1, 233 | -1, 234 | -1, 235 | -1, 236 | -1, 237 | -1, 238 | -1, 239 | -1, 240 | -1, 241 | -1, 242 | -1, 243 | -1, 244 | -1, 245 | -1, 246 | -1, 247 | -1, 248 | -1, 249 | -1, 250 | -1, 251 | -1, 252 | -1, 253 | -1, 254 | -1, 255 | -1, 256 | -1, 257 | -1, 258 | -1, 259 | -1, 260 | -1, 261 | -1, 262 | ]; 263 | 264 | /// Parses a VLQ segment into a vector. 265 | pub fn parse_vlq_segment(segment: &str) -> Result> { 266 | let mut rv = vec![]; 267 | 268 | parse_vlq_segment_into(segment, &mut rv)?; 269 | 270 | Ok(rv) 271 | } 272 | 273 | /// Parses a VLQ segment into a pre-allocated `Vec` instead of returning a new allocation. 274 | pub(crate) fn parse_vlq_segment_into(segment: &str, rv: &mut Vec) -> Result<()> { 275 | let mut cur = 0; 276 | let mut shift = 0; 277 | 278 | for c in segment.bytes() { 279 | let enc = i64::from(B64[c as usize]); 280 | let val = enc & 0b11111; 281 | let cont = enc >> 5; 282 | cur += val.checked_shl(shift).ok_or(Error::VlqOverflow)?; 283 | shift += 5; 284 | 285 | if cont == 0 { 286 | let sign = cur & 1; 287 | cur >>= 1; 288 | if sign != 0 { 289 | cur = -cur; 290 | } 291 | rv.push(cur); 292 | cur = 0; 293 | shift = 0; 294 | } 295 | } 296 | 297 | if cur != 0 || shift != 0 { 298 | Err(Error::VlqLeftover) 299 | } else if rv.is_empty() { 300 | Err(Error::VlqNoValues) 301 | } else { 302 | Ok(()) 303 | } 304 | } 305 | 306 | /// Encodes a VLQ segment from a slice. 307 | pub fn generate_vlq_segment(nums: &[i64]) -> Result { 308 | let mut rv = String::new(); 309 | for &num in nums { 310 | encode_vlq(&mut rv, num); 311 | } 312 | Ok(rv) 313 | } 314 | 315 | pub(crate) fn encode_vlq(out: &mut String, num: i64) { 316 | let mut num = if num < 0 { ((-num) << 1) + 1 } else { num << 1 }; 317 | 318 | loop { 319 | let mut digit = num & 0b11111; 320 | num >>= 5; 321 | if num > 0 { 322 | digit |= 1 << 5; 323 | } 324 | out.push(B64_CHARS[digit as usize] as char); 325 | if num == 0 { 326 | break; 327 | } 328 | } 329 | } 330 | 331 | #[cfg(test)] 332 | mod tests { 333 | use super::*; 334 | 335 | #[test] 336 | fn test_vlq_decode() { 337 | let rv = parse_vlq_segment("AAAA").unwrap(); 338 | assert_eq!(rv, vec![0, 0, 0, 0]); 339 | let rv = parse_vlq_segment("GAAIA").unwrap(); 340 | assert_eq!(rv, vec![3, 0, 0, 4, 0]); 341 | } 342 | 343 | #[test] 344 | fn test_vlq_encode() { 345 | let rv = generate_vlq_segment(&[0, 0, 0, 0]).unwrap(); 346 | assert_eq!(rv.as_str(), "AAAA"); 347 | let rv = generate_vlq_segment(&[3, 0, 0, 4, 0]).unwrap(); 348 | assert_eq!(rv.as_str(), "GAAIA"); 349 | } 350 | 351 | #[test] 352 | fn test_overflow() { 353 | match parse_vlq_segment("00000000000000") { 354 | Err(Error::VlqOverflow) => {} 355 | e => { 356 | panic!("Unexpeted result: {:?}", e); 357 | } 358 | } 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 9.3.1 4 | 5 | - Support charset in `decode_data_url` by @coolreader18 in [#137](https://github.com/getsentry/rust-sourcemap/pull/137) 6 | 7 | ## 9.3.0 8 | 9 | ### New Features ✨ 10 | 11 | - feat(debugId): Serialize source maps with `debugId`, not `debug_id` by @szokeasaurusrex in [#134](https://github.com/getsentry/rust-sourcemap/pull/134) 12 | 13 | ### Build / dependencies / internal 🔧 14 | 15 | - chore: Fix 1.88.0 clippy lints by @loewenheim in [#130](https://github.com/getsentry/rust-sourcemap/pull/130) 16 | 17 | ### Other 18 | 19 | - Store SourceView linecache as offsets rather than pointers by @coolreader18 in [#133](https://github.com/getsentry/rust-sourcemap/pull/133) 20 | 21 | ## 9.2.2 22 | 23 | ### Various fixes & improvements 24 | 25 | - fix: Correctly interpret missing line/column numbers (#129) by @loewenheim 26 | - ref: Remove fail macro (#128) by @loewenheim 27 | - perf: Make `adjust_mappings` faster by reducing the type size (#124) by @kdy1 28 | - perf: Optimize `flatten()` even more (#122) by @kdy1 29 | 30 | ## 9.2.1 31 | 32 | ### Various fixes & improvements 33 | 34 | - perf: Make `SourceMapIndex::flatten` more efficient (#121) by @kdy1 35 | 36 | ## 9.2.0 37 | 38 | ### Various fixes & improvements 39 | 40 | - feat: Add ability to adjust SourceMapIndex offset rows (#119) by @szokeasaurusrex 41 | - feat: Enable getting/setting debug ID on DecodedMap (#118) by @szokeasaurusrex 42 | - build: upgrade dependencies (#107) by @paolobarbolini 43 | - build: drop unused `rustc_version` dependency (#108) by @paolobarbolini 44 | - feat: Add `debug_id` field to `SourceMapIndex` (#117) by @szokeasaurusrex 45 | - ref(tests): Move tests under `cfg(test)` (#115) by @szokeasaurusrex 46 | - ref(utils): Fix clippy lint (#116) by @szokeasaurusrex 47 | - fix(Hermes): Use 1-based indexing for lines (#106) by @pablomatiasgomez 48 | 49 | ## 9.1.2 50 | 51 | ### Various fixes & improvements 52 | 53 | - Prefer `"debug_id"` for sourcemap debug IDs (#101) by @loewenheim 54 | 55 | ## 9.1.1 56 | 57 | ### Various fixes & improvements 58 | 59 | - Fixed an error when deserializing sourcemaps with 60 | both `"debugId"` and `"debug_id"` keys (#100) by @loewenheim 61 | 62 | ## 9.1.0 63 | 64 | ### Various fixes & improvements 65 | 66 | - Add support for ignoreList property (#93) by @wbinnssmith 67 | 68 | ## 9.0.1 69 | 70 | ### Various fixes and improvements 71 | 72 | - Debug IDs can be read from the "debugId" field in addition to "debug_id" (#97) by @loewenheim. 73 | 74 | ## 9.0.0 75 | 76 | ### Various fixes and improvements 77 | 78 | - ref: Tokens within a sourcemap are now always sorted by their position in the 79 | minified file (#91) by @loewenheim. 80 | Consequently: 81 | - the type `IndexIter` and the functions `get_index_size`, `index_iter`, 82 | and `idx_from_token` have been deleted; 83 | - the function `sourcemap_from_token` has been turned into the method 84 | `sourcemap` on `Token`; 85 | - the `idx` parameter of `SourceMap::get_token` now has the type `usize`. 86 | 87 | ## 8.0.1 88 | 89 | ### Various fixes & improvements 90 | 91 | - feat: Skip encoding range mappings if it's empty (#86) by @kdy1 92 | 93 | ## 8.0.0 94 | 95 | ### Various fixes & improvements 96 | 97 | - feat: Add support for range mappings proposal (#77) by @kdy1 98 | - perf: using Arc instead of String (#84) by @underfin 99 | - perf: using FxHashMap instead of HashMap (#83) by @underfin 100 | 101 | ## 7.1.1 102 | 103 | ### Various fixes & improvements 104 | 105 | - chore: remove unless change (#82) by @underfin 106 | 107 | ## 7.1.0 108 | 109 | ### Various fixes & improvements 110 | 111 | - refactor: make SourceView implement Sync (#80) by @underfin 112 | - feat: add SourceMap::to_data_url (#81) by @underfin 113 | - feat: Replace `unicode-id` crate with `unicode-id-start` (#78) by @Boshen 114 | 115 | ## 7.0.1 116 | 117 | ### Various fixes and improvements 118 | 119 | - fix: Fixed a bug in index sourcemap flattening (#74) by @loewenheim 120 | 121 | ## 7.0.0 122 | 123 | ### Various fixes and improvements 124 | 125 | - ref: SourceMap::adjust_mappings now works in place (#70) by @loewenheim 126 | 127 | ## 6.4.1 128 | 129 | ### Various fixes & improvements 130 | 131 | - fix: Do not set source_contents if it already exists (#68) by @kamilogorek 132 | 133 | ## 6.4.0 134 | 135 | ### Various fixes and improvements 136 | 137 | - feat: Implement sourcemap composition(#67) by @loewenheim 138 | 139 | ## 6.3.0 140 | 141 | ### Various fixes & improvements 142 | 143 | - feat: Sourcemaps now support debug ids (#66) by @loewenheim 144 | 145 | ## 6.2.3 146 | 147 | ### Various fixes & improvements 148 | 149 | - fix: Correctly handle protocol-only sourceRoot values (#61) by @kamilogorek 150 | 151 | ## 6.2.2 152 | 153 | ### Various fixes & improvements 154 | 155 | - Ensure greatest_lower_bound returns lowest match index (#60) by @jridgewell 156 | - feat: Switch to data-encoding for base64 (#59) by @mitsuhiko 157 | 158 | ## 6.2.1 159 | 160 | ### Various fixes & improvements 161 | 162 | - ref: Update CI definitions (#58) by @Swatinem 163 | - fix: Correctly rewrite SourceMapHermes (#56) by @Swatinem 164 | - Remove regex dependency for faster runtime, and compile (#55) by @willstott101 165 | - Jridgewell index (#54) by @Swatinem 166 | 167 | ## 6.2.0 168 | 169 | **Features**: 170 | 171 | - Add `source_root` support for `SourceMap` and `SourceMapBuilder`, with respective getters/setters and de/serialization. ([#51](https://github.com/getsentry/rust-sourcemap/pull/51)) 172 | 173 | ## 6.1.0 174 | 175 | **Features**: 176 | 177 | - Add a new `get_scope_for_token` method to `SourceMapHermes` as a more flexible alternative to `get_original_function_name`. ([#48](https://github.com/getsentry/rust-sourcemap/pull/48)) 178 | 179 | ## 6.0.2 180 | 181 | **Fixes**: 182 | 183 | - Improve parsing performance by reusing a temporary allocation. [#40](https://github.com/getsentry/rust-sourcemap/pull/40) 184 | 185 | ## 6.0.1 186 | 187 | **Fixes**: 188 | 189 | - Fix compilation errors when targetting wasm. 190 | 191 | ## 6.0.0 192 | 193 | **Breaking Changes**: 194 | 195 | - The `SourceMapRef::Missing` variant was removed in favor of explicit `Option`s. 196 | - The `locate_sourcemap_reference_slice` and `locate_sourcemap_reference` functions now return a `Option`. 197 | - `SourceMapRef::get_url` does _not_ return an `Option` anymore. 198 | 199 | **Features**: 200 | 201 | - Added missing `Clone` and `Debug` impls to a lot of types. 202 | - A lot of new convenience API, including a `DecodedMap::get_original_function_name` method that works across all supported SourceMap types. 203 | -------------------------------------------------------------------------------- /tests/test_decoder.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::io::BufRead; 3 | 4 | use sourcemap::{decode_data_url, DecodedMap, SourceMap, Token}; 5 | 6 | #[test] 7 | fn test_no_header() { 8 | let input: &[_] = br#"[1, 2, 3]"#; 9 | let mut reader = io::BufReader::new(input); 10 | let mut text = String::new(); 11 | reader.read_line(&mut text).ok(); 12 | assert_eq!(text, "[1, 2, 3]"); 13 | } 14 | 15 | #[test] 16 | fn test_no_header_object() { 17 | let input: &[_] = br#"{"x": [1, 2, 3]}"#; 18 | let mut reader = io::BufReader::new(input); 19 | let mut text = String::new(); 20 | reader.read_line(&mut text).ok(); 21 | assert_eq!(text, r#"{"x": [1, 2, 3]}"#); 22 | } 23 | 24 | #[test] 25 | fn test_basic_sourcemap() { 26 | let input: &[_] = br#"{ 27 | "version": 3, 28 | "sources": ["coolstuff.js"], 29 | "names": ["x","alert"], 30 | "mappings": "AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM" 31 | }"#; 32 | let sm = SourceMap::from_reader(input).unwrap(); 33 | let mut iter = sm.tokens().filter(Token::has_name); 34 | assert_eq!( 35 | iter.next().unwrap().to_tuple(), 36 | ("coolstuff.js", 0, 4, Some("x")) 37 | ); 38 | assert_eq!( 39 | iter.next().unwrap().to_tuple(), 40 | ("coolstuff.js", 1, 4, Some("x")) 41 | ); 42 | assert_eq!( 43 | iter.next().unwrap().to_tuple(), 44 | ("coolstuff.js", 2, 2, Some("alert")) 45 | ); 46 | assert!(iter.next().is_none()); 47 | } 48 | 49 | #[test] 50 | fn test_basic_sourcemap_with_root() { 51 | let input: &[_] = br#"{ 52 | "version": 3, 53 | "sources": ["coolstuff.js"], 54 | "sourceRoot": "x", 55 | "names": ["x","alert"], 56 | "mappings": "AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM" 57 | }"#; 58 | let sm = SourceMap::from_reader(input).unwrap(); 59 | let mut iter = sm.tokens().filter(Token::has_name); 60 | assert_eq!( 61 | iter.next().unwrap().to_tuple(), 62 | ("x/coolstuff.js", 0, 4, Some("x")) 63 | ); 64 | assert_eq!( 65 | iter.next().unwrap().to_tuple(), 66 | ("x/coolstuff.js", 1, 4, Some("x")) 67 | ); 68 | assert_eq!( 69 | iter.next().unwrap().to_tuple(), 70 | ("x/coolstuff.js", 2, 2, Some("alert")) 71 | ); 72 | assert!(iter.next().is_none()); 73 | } 74 | 75 | #[test] 76 | fn test_basic_sourcemap_with_absolute_uri_root() { 77 | let input: &[_] = br#"{ 78 | "version": 3, 79 | "sources": ["coolstuff.js", "./evencoolerstuff.js"], 80 | "sourceRoot": "webpack:///", 81 | "names": ["x","alert"], 82 | "mappings": "AAAA,GAAIA,GAAI,EACR,ICAIA,GAAK,EAAG,CACVC,MAAM" 83 | }"#; 84 | let sm = SourceMap::from_reader(input).unwrap(); 85 | let mut iter = sm.tokens().filter(Token::has_name); 86 | assert_eq!( 87 | iter.next().unwrap().to_tuple(), 88 | ("webpack:///coolstuff.js", 0, 4, Some("x")) 89 | ); 90 | assert_eq!( 91 | iter.next().unwrap().to_tuple(), 92 | ("webpack:///./evencoolerstuff.js", 1, 4, Some("x")) 93 | ); 94 | assert_eq!( 95 | iter.next().unwrap().to_tuple(), 96 | ("webpack:///./evencoolerstuff.js", 2, 2, Some("alert")) 97 | ); 98 | assert!(iter.next().is_none()); 99 | } 100 | 101 | #[test] 102 | fn test_basic_sourcemap_source_root_logic() { 103 | let input: &[_] = br#"{ 104 | "version": 3, 105 | "sources": ["coolstuff.js", "/evencoolerstuff.js", "https://awesome.js"], 106 | "sourceRoot": "webpack:///", 107 | "mappings": "" 108 | }"#; 109 | let sm = SourceMap::from_reader(input).unwrap(); 110 | let mut iter = sm.sources(); 111 | assert_eq!(iter.next().unwrap(), "webpack:///coolstuff.js"); 112 | assert_eq!(iter.next().unwrap(), "/evencoolerstuff.js"); 113 | assert_eq!(iter.next().unwrap(), "https://awesome.js"); 114 | assert!(iter.next().is_none()); 115 | } 116 | 117 | #[test] 118 | fn test_sourcemap_data_url() { 119 | let base64 = "\ 120 | eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImNvb2xzdHVmZi5qcyJdLCJzb3VyY2VSb290I\ 121 | joieCIsIm5hbWVzIjpbIngiLCJhbGVydCJdLCJtYXBwaW5ncyI6IkFBQUEsR0FBSUEsR0\ 122 | FBSSxFQUNSLElBQUlBLEdBQUssRUFBRyxDQUNWQyxNQUFNIn0="; 123 | let test_url = |prefix: &str| match decode_data_url(&[prefix, base64].concat()).unwrap() { 124 | DecodedMap::Regular(sm) => { 125 | let mut iter = sm.tokens().filter(Token::has_name); 126 | assert_eq!( 127 | iter.next().unwrap().to_tuple(), 128 | ("x/coolstuff.js", 0, 4, Some("x")) 129 | ); 130 | assert_eq!( 131 | iter.next().unwrap().to_tuple(), 132 | ("x/coolstuff.js", 1, 4, Some("x")) 133 | ); 134 | assert_eq!( 135 | iter.next().unwrap().to_tuple(), 136 | ("x/coolstuff.js", 2, 2, Some("alert")) 137 | ); 138 | assert!(iter.next().is_none()); 139 | } 140 | _ => { 141 | panic!("did not get sourcemap"); 142 | } 143 | }; 144 | test_url("data:application/json;base64,"); 145 | test_url("data:application/json;charset=utf-8;base64,"); 146 | test_url("data:application/json;charset=utf8;base64,"); 147 | } 148 | 149 | #[test] 150 | fn test_sourcemap_nofiles() { 151 | let input: &[_] = br#"{ 152 | "version": 3, 153 | "sources": [null], 154 | "names": ["x","alert"], 155 | "mappings": "AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM" 156 | }"#; 157 | let sm = SourceMap::from_reader(input).unwrap(); 158 | let mut iter = sm.tokens().filter(Token::has_name); 159 | assert_eq!(iter.next().unwrap().to_tuple(), ("", 0, 4, Some("x"))); 160 | assert_eq!(iter.next().unwrap().to_tuple(), ("", 1, 4, Some("x"))); 161 | assert_eq!(iter.next().unwrap().to_tuple(), ("", 2, 2, Some("alert"))); 162 | assert!(iter.next().is_none()); 163 | } 164 | 165 | #[test] 166 | fn test_sourcemap_range_mappings() { 167 | let input: &[_] = br#"{ 168 | "version": 3, 169 | "sources": [null], 170 | "names": ["console","log","ab"], 171 | "mappings": "AACAA,QAAQC,GAAG,CAAC,OAAM,OAAM,QACxBD,QAAQC,GAAG,CAAC,QAEZD,QAAQC,GAAG,CAJD;IAACC,IAAI;AAAI,IAKnBF,QAAQC,GAAG,CAAC,YACZD,QAAQC,GAAG,CAAC", 172 | "rangeMappings": "AAB;;g" 173 | }"#; 174 | let sm = SourceMap::from_reader(input).unwrap(); 175 | 176 | let mut iter = sm.tokens().filter(Token::is_range); 177 | 178 | assert_eq!(sm.tokens().filter(Token::is_range).count(), 2); 179 | 180 | assert_eq!(iter.next().unwrap().to_tuple(), ("", 4, 11, None)); 181 | 182 | assert_eq!(iter.next().unwrap().to_tuple(), ("", 6, 0, Some("console"))); 183 | assert!(iter.next().is_none()); 184 | } 185 | -------------------------------------------------------------------------------- /src/hermes.rs: -------------------------------------------------------------------------------- 1 | use crate::decoder::{decode, decode_regular, decode_slice}; 2 | use crate::encoder::{encode, Encodable}; 3 | use crate::errors::{Error, Result}; 4 | use crate::jsontypes::{FacebookScopeMapping, FacebookSources, RawSourceMap}; 5 | use crate::types::{DecodedMap, RewriteOptions, SourceMap}; 6 | use crate::utils::greatest_lower_bound; 7 | use crate::vlq::parse_vlq_segment_into; 8 | use crate::Token; 9 | use std::io::{Read, Write}; 10 | use std::ops::{Deref, DerefMut}; 11 | 12 | /// These are starting locations of scopes. 13 | /// The `name_index` represents the index into the `HermesFunctionMap.names` vec, 14 | /// which represents the function names/scopes. 15 | #[derive(Debug, Clone, PartialEq)] 16 | pub struct HermesScopeOffset { 17 | line: u32, 18 | column: u32, 19 | name_index: u32, 20 | } 21 | 22 | #[derive(Debug, Clone, PartialEq)] 23 | pub struct HermesFunctionMap { 24 | names: Vec, 25 | mappings: Vec, 26 | } 27 | 28 | /// Represents a `react-native`-style SourceMap, which has additional scope 29 | /// information embedded. 30 | #[derive(Debug, Clone, PartialEq)] 31 | pub struct SourceMapHermes { 32 | pub(crate) sm: SourceMap, 33 | // There should be one `HermesFunctionMap` per each `sources` entry in the main SourceMap. 34 | function_maps: Vec>, 35 | // XXX: right now, I am too lazy to actually serialize the above `function_maps` 36 | // back into json types, so just keep the original json. Might be a bit inefficient, but meh. 37 | raw_facebook_sources: FacebookSources, 38 | } 39 | 40 | impl Deref for SourceMapHermes { 41 | type Target = SourceMap; 42 | 43 | fn deref(&self) -> &Self::Target { 44 | &self.sm 45 | } 46 | } 47 | 48 | impl DerefMut for SourceMapHermes { 49 | fn deref_mut(&mut self) -> &mut Self::Target { 50 | &mut self.sm 51 | } 52 | } 53 | 54 | impl Encodable for SourceMapHermes { 55 | fn as_raw_sourcemap(&self) -> RawSourceMap { 56 | // TODO: need to serialize the `HermesFunctionMap` mappings 57 | let mut rsm = self.sm.as_raw_sourcemap(); 58 | rsm.x_facebook_sources 59 | .clone_from(&self.raw_facebook_sources); 60 | rsm 61 | } 62 | } 63 | 64 | impl SourceMapHermes { 65 | /// Creates a sourcemap from a reader over a JSON stream in UTF-8 66 | /// format. 67 | /// 68 | /// See [`SourceMap::from_reader`](struct.SourceMap.html#method.from_reader) 69 | pub fn from_reader(rdr: R) -> Result { 70 | match decode(rdr)? { 71 | DecodedMap::Hermes(sm) => Ok(sm), 72 | _ => Err(Error::IncompatibleSourceMap), 73 | } 74 | } 75 | 76 | /// Creates a sourcemap from a reader over a JSON byte slice in UTF-8 77 | /// format. 78 | /// 79 | /// See [`SourceMap::from_slice`](struct.SourceMap.html#method.from_slice) 80 | pub fn from_slice(slice: &[u8]) -> Result { 81 | match decode_slice(slice)? { 82 | DecodedMap::Hermes(sm) => Ok(sm), 83 | _ => Err(Error::IncompatibleSourceMap), 84 | } 85 | } 86 | 87 | /// Writes a sourcemap into a writer. 88 | /// 89 | /// See [`SourceMap::to_writer`](struct.SourceMap.html#method.to_writer) 90 | pub fn to_writer(&self, w: W) -> Result<()> { 91 | encode(self, w) 92 | } 93 | 94 | /// Given a bytecode offset, this will find the enclosing scopes function 95 | /// name. 96 | pub fn get_original_function_name(&self, bytecode_offset: u32) -> Option<&str> { 97 | let token = self.sm.lookup_token(0, bytecode_offset)?; 98 | 99 | self.get_scope_for_token(token) 100 | } 101 | 102 | /// Resolves the name of the enclosing function for the given [`Token`]. 103 | pub fn get_scope_for_token(&self, token: Token) -> Option<&str> { 104 | let function_map = self 105 | .function_maps 106 | .get(token.get_src_id() as usize)? 107 | .as_ref()?; 108 | 109 | // Find the closest mapping, just like here: 110 | // https://github.com/facebook/metro/blob/63b523eb20e7bdf62018aeaf195bb5a3a1a67f36/packages/metro-symbolicate/src/SourceMetadataMapConsumer.js#L204-L231 111 | // Mappings use 1-based index for lines, and 0-based index for cols, as seen here: 112 | // https://github.com/facebook/metro/blob/f2d80cebe66d3c64742f67259f41da26e83a0d8d/packages/metro/src/Server/symbolicate.js#L58-L60 113 | let (_mapping_idx, mapping) = greatest_lower_bound( 114 | &function_map.mappings, 115 | &(token.get_src_line() + 1, token.get_src_col()), 116 | |o| (o.line, o.column), 117 | )?; 118 | function_map 119 | .names 120 | .get(mapping.name_index as usize) 121 | .map(|n| n.as_str()) 122 | } 123 | 124 | /// This rewrites the sourcemap according to the provided rewrite 125 | /// options. 126 | /// 127 | /// See [`SourceMap::rewrite`](struct.SourceMap.html#method.rewrite) 128 | pub fn rewrite(self, options: &RewriteOptions<'_>) -> Result { 129 | let Self { 130 | sm, 131 | mut function_maps, 132 | mut raw_facebook_sources, 133 | } = self; 134 | 135 | let (sm, mapping) = sm.rewrite_with_mapping(options)?; 136 | 137 | if function_maps.len() >= mapping.len() { 138 | function_maps = mapping 139 | .iter() 140 | .map(|idx| function_maps[*idx as usize].take()) 141 | .collect(); 142 | raw_facebook_sources = raw_facebook_sources.map(|mut sources| { 143 | mapping 144 | .into_iter() 145 | .map(|idx| sources[idx as usize].take()) 146 | .collect() 147 | }); 148 | } 149 | 150 | Ok(Self { 151 | sm, 152 | function_maps, 153 | raw_facebook_sources, 154 | }) 155 | } 156 | } 157 | 158 | pub fn decode_hermes(mut rsm: RawSourceMap) -> Result { 159 | let x_facebook_sources = rsm 160 | .x_facebook_sources 161 | .take() 162 | .ok_or(Error::IncompatibleSourceMap)?; 163 | 164 | // This is basically the logic from here: 165 | // https://github.com/facebook/metro/blob/63b523eb20e7bdf62018aeaf195bb5a3a1a67f36/packages/metro-symbolicate/src/SourceMetadataMapConsumer.js#L182-L202 166 | 167 | let mut nums = Vec::with_capacity(4); 168 | 169 | let function_maps = x_facebook_sources 170 | .iter() 171 | .map(|v| { 172 | let FacebookScopeMapping { 173 | names, 174 | mappings: raw_mappings, 175 | } = v.as_ref()?.iter().next()?; 176 | 177 | let mut mappings = vec![]; 178 | let mut line = 1; 179 | let mut name_index = 0; 180 | 181 | for line_mapping in raw_mappings.split(';') { 182 | if line_mapping.is_empty() { 183 | continue; 184 | } 185 | 186 | let mut column = 0; 187 | 188 | for mapping in line_mapping.split(',') { 189 | if mapping.is_empty() { 190 | continue; 191 | } 192 | 193 | nums.clear(); 194 | parse_vlq_segment_into(mapping, &mut nums).ok()?; 195 | let mut nums = nums.iter().copied(); 196 | 197 | column = (i64::from(column) + nums.next()?) as u32; 198 | name_index = (i64::from(name_index) + nums.next().unwrap_or(0)) as u32; 199 | line = (i64::from(line) + nums.next().unwrap_or(0)) as u32; 200 | mappings.push(HermesScopeOffset { 201 | column, 202 | line, 203 | name_index, 204 | }); 205 | } 206 | } 207 | Some(HermesFunctionMap { 208 | names: names.clone(), 209 | mappings, 210 | }) 211 | }) 212 | .collect(); 213 | 214 | let sm = decode_regular(rsm)?; 215 | Ok(SourceMapHermes { 216 | sm, 217 | function_maps, 218 | raw_facebook_sources: Some(x_facebook_sources), 219 | }) 220 | } 221 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::iter; 3 | 4 | fn split_path(path: &str) -> Vec<&str> { 5 | let mut last_idx = 0; 6 | let mut rv = vec![]; 7 | for (idx, _) in path.match_indices(&['/', '\\'][..]) { 8 | rv.push(&path[last_idx..idx]); 9 | last_idx = idx; 10 | } 11 | if last_idx < path.len() { 12 | rv.push(&path[last_idx..]); 13 | } 14 | rv 15 | } 16 | 17 | fn is_abs_path(s: &str) -> bool { 18 | if s.starts_with('/') { 19 | return true; 20 | } else if s.len() > 3 { 21 | let b = s.as_bytes(); 22 | if b[1] == b':' 23 | && (b[2] == b'/' || b[2] == b'\\') 24 | && ((b[0] >= b'a' && b[0] <= b'z') || (b[0] >= b'A' && b[0] <= b'Z')) 25 | { 26 | return true; 27 | } 28 | } 29 | false 30 | } 31 | 32 | fn find_common_prefix_of_sorted_vec<'a>(items: &'a [Cow<'a, [&'a str]>]) -> Option<&'a [&'a str]> { 33 | if items.is_empty() { 34 | return None; 35 | } 36 | 37 | let shortest = &items[0]; 38 | let mut max_idx = None; 39 | for seq in items.iter() { 40 | let mut seq_max_idx = None; 41 | for (idx, &comp) in shortest.iter().enumerate() { 42 | if seq.get(idx) != Some(&comp) { 43 | break; 44 | } 45 | seq_max_idx = Some(idx); 46 | } 47 | if max_idx.is_none() || seq_max_idx < max_idx { 48 | max_idx = seq_max_idx; 49 | } 50 | } 51 | 52 | if let Some(max_idx) = max_idx { 53 | Some(&shortest[..=max_idx]) 54 | } else { 55 | None 56 | } 57 | } 58 | 59 | pub fn find_common_prefix<'a, I: Iterator>(iter: I) -> Option { 60 | let mut items: Vec> = iter 61 | .filter(|x| is_abs_path(x)) 62 | .map(|x| Cow::Owned(split_path(x))) 63 | .collect(); 64 | items.sort_by_key(|x| x.len()); 65 | 66 | if let Some(slice) = find_common_prefix_of_sorted_vec(&items) { 67 | let rv = slice.join(""); 68 | if !rv.is_empty() && &rv != "/" { 69 | return Some(rv); 70 | } 71 | } 72 | 73 | None 74 | } 75 | 76 | /// Helper function to calculate the path from a base file to a target file. 77 | /// 78 | /// This is intended to caculate the path from a minified JavaScript file 79 | /// to a sourcemap if they are both on the same server. 80 | /// 81 | /// Example: 82 | /// 83 | /// ``` 84 | /// # use sourcemap::make_relative_path; 85 | /// assert_eq!(&make_relative_path( 86 | /// "/foo/bar/baz.js", "/foo/baz.map"), "../baz.map"); 87 | /// ``` 88 | pub fn make_relative_path(base: &str, target: &str) -> String { 89 | let target_path: Vec<_> = target 90 | .split(&['/', '\\'][..]) 91 | .filter(|x| !x.is_empty()) 92 | .collect(); 93 | let mut base_path: Vec<_> = base 94 | .split(&['/', '\\'][..]) 95 | .filter(|x| !x.is_empty()) 96 | .collect(); 97 | base_path.pop(); 98 | 99 | let mut items = vec![ 100 | Cow::Borrowed(target_path.as_slice()), 101 | Cow::Borrowed(base_path.as_slice()), 102 | ]; 103 | items.sort_by_key(|x| x.len()); 104 | 105 | let prefix = find_common_prefix_of_sorted_vec(&items) 106 | .map(|x| x.len()) 107 | .unwrap_or(0); 108 | let mut rel_list: Vec<_> = iter::repeat_n("../", base_path.len() - prefix).collect(); 109 | rel_list.extend_from_slice(&target_path[prefix..]); 110 | if rel_list.is_empty() { 111 | ".".into() 112 | } else { 113 | rel_list.join("") 114 | } 115 | } 116 | 117 | pub fn greatest_lower_bound<'a, T, K: Ord, F: Fn(&'a T) -> K>( 118 | slice: &'a [T], 119 | key: &K, 120 | map: F, 121 | ) -> Option<(usize, &'a T)> { 122 | let mut idx = match slice.binary_search_by_key(key, &map) { 123 | Ok(index) => index, 124 | Err(index) => { 125 | // If there is no match, then we know for certain that the index is where we should 126 | // insert a new token, and that the token directly before is the greatest lower bound. 127 | return slice.get(index.checked_sub(1)?).map(|res| (index, res)); 128 | } 129 | }; 130 | 131 | // If we get an exact match, then we need to continue looking at previous tokens to see if 132 | // they also match. We use a linear search because the number of exact matches is generally 133 | // very small, and almost certainly smaller than the number of tokens before the index. 134 | for i in (0..idx).rev() { 135 | if map(&slice[i]) == *key { 136 | idx = i; 137 | } else { 138 | break; 139 | } 140 | } 141 | slice.get(idx).map(|res| (idx, res)) 142 | } 143 | 144 | #[cfg(test)] 145 | mod tests { 146 | use super::*; 147 | 148 | #[test] 149 | fn test_is_abs_path() { 150 | assert!(is_abs_path("C:\\foo.txt")); 151 | assert!(is_abs_path("d:/foo.txt")); 152 | assert!(!is_abs_path("foo.txt")); 153 | assert!(is_abs_path("/foo.txt")); 154 | assert!(is_abs_path("/")); 155 | } 156 | 157 | #[test] 158 | fn test_split_path() { 159 | assert_eq!(split_path("/foo/bar/baz"), &["", "/foo", "/bar", "/baz"]); 160 | } 161 | 162 | #[test] 163 | fn test_find_common_prefix() { 164 | let rv = find_common_prefix(vec!["/foo/bar/baz", "/foo/bar/baz/blah"].into_iter()); 165 | assert_eq!(rv, Some("/foo/bar/baz".into())); 166 | 167 | let rv = find_common_prefix(vec!["/foo/bar/baz", "/foo/bar/baz/blah", "/meh"].into_iter()); 168 | assert_eq!(rv, None); 169 | 170 | let rv = find_common_prefix(vec!["/foo/bar/baz", "/foo/bar/baz/blah", "/foo"].into_iter()); 171 | assert_eq!(rv, Some("/foo".into())); 172 | 173 | let rv = find_common_prefix(vec!["/foo/bar/baz", "/foo/bar/baz/blah", "foo"].into_iter()); 174 | assert_eq!(rv, Some("/foo/bar/baz".into())); 175 | 176 | let rv = find_common_prefix( 177 | vec!["/foo/bar/baz", "/foo/bar/baz/blah", "/blah", "foo"].into_iter(), 178 | ); 179 | assert_eq!(rv, None); 180 | 181 | let rv = find_common_prefix( 182 | vec!["/foo/bar/baz", "/foo/bar/baz/blah", "/blah", "foo"].into_iter(), 183 | ); 184 | assert_eq!(rv, None); 185 | } 186 | 187 | #[test] 188 | fn test_make_relative_path() { 189 | assert_eq!( 190 | &make_relative_path("/foo/bar/baz.js", "/foo/bar/baz.map"), 191 | "baz.map" 192 | ); 193 | assert_eq!( 194 | &make_relative_path("/foo/bar/.", "/foo/bar/baz.map"), 195 | "baz.map" 196 | ); 197 | assert_eq!( 198 | &make_relative_path("/foo/bar/baz.js", "/foo/baz.map"), 199 | "../baz.map" 200 | ); 201 | assert_eq!(&make_relative_path("foo.txt", "foo.js"), "foo.js"); 202 | assert_eq!(&make_relative_path("blah/foo.txt", "foo.js"), "../foo.js"); 203 | } 204 | 205 | #[test] 206 | fn test_greatest_lower_bound() { 207 | let cmp = |&(i, _id)| i; 208 | 209 | let haystack = vec![(1, 1)]; 210 | assert_eq!(greatest_lower_bound(&haystack, &1, cmp).unwrap().1, &(1, 1)); 211 | assert_eq!(greatest_lower_bound(&haystack, &2, cmp).unwrap().1, &(1, 1)); 212 | assert_eq!(greatest_lower_bound(&haystack, &0, cmp), None); 213 | 214 | let haystack = vec![(1, 1), (1, 2)]; 215 | assert_eq!(greatest_lower_bound(&haystack, &1, cmp).unwrap().1, &(1, 1)); 216 | assert_eq!(greatest_lower_bound(&haystack, &2, cmp).unwrap().1, &(1, 2)); 217 | assert_eq!(greatest_lower_bound(&haystack, &0, cmp), None); 218 | 219 | let haystack = vec![(1, 1), (1, 2), (1, 3)]; 220 | assert_eq!(greatest_lower_bound(&haystack, &1, cmp).unwrap().1, &(1, 1)); 221 | assert_eq!(greatest_lower_bound(&haystack, &2, cmp).unwrap().1, &(1, 3)); 222 | assert_eq!(greatest_lower_bound(&haystack, &0, cmp), None); 223 | 224 | let haystack = vec![(1, 1), (1, 2), (1, 3), (1, 4)]; 225 | assert_eq!(greatest_lower_bound(&haystack, &1, cmp).unwrap().1, &(1, 1)); 226 | assert_eq!(greatest_lower_bound(&haystack, &2, cmp).unwrap().1, &(1, 4)); 227 | assert_eq!(greatest_lower_bound(&haystack, &0, cmp), None); 228 | 229 | let haystack = vec![(1, 1), (1, 2), (1, 3), (1, 4), (1, 5)]; 230 | assert_eq!(greatest_lower_bound(&haystack, &1, cmp).unwrap().1, &(1, 1)); 231 | assert_eq!(greatest_lower_bound(&haystack, &2, cmp).unwrap().1, &(1, 5)); 232 | assert_eq!(greatest_lower_bound(&haystack, &0, cmp), None); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /tests/test_index.rs: -------------------------------------------------------------------------------- 1 | use sourcemap::{DecodedMap, SourceMapIndex}; 2 | use std::collections::HashMap; 3 | 4 | #[test] 5 | fn test_basic_indexed_sourcemap() { 6 | let input: &[_] = br#"{ 7 | "version": 3, 8 | "file": "min.js", 9 | "sections": [ 10 | { 11 | "offset": { 12 | "line": 0, 13 | "column": 0 14 | }, 15 | "map": { 16 | "version":3, 17 | "sources":["file1.js"], 18 | "names":["add","a","b"], 19 | "mappings":"AAAA,QAASA,KAAIC,EAAGC,GACf,YACA,OAAOD,GAAIC", 20 | "file":"file1.min.js" 21 | } 22 | }, 23 | { 24 | "offset": { 25 | "line": 1, 26 | "column": 1 27 | }, 28 | "map": { 29 | "version":3, 30 | "sources":["file2.js"], 31 | "names":["multiply","a","b","divide","add","c","e","Raven","captureException"], 32 | "mappings":"AAAA,QAASA,UAASC,EAAGC,GACpB,YACA,OAAOD,GAAIC,EAEZ,QAASC,QAAOF,EAAGC,GAClB,YACA,KACC,MAAOF,UAASI,IAAIH,EAAGC,GAAID,EAAGC,GAAKG,EAClC,MAAOC,GACRC,MAAMC,iBAAiBF", 33 | "file":"file2.min.js" 34 | } 35 | } 36 | ] 37 | }"#; 38 | 39 | let f1 = "function add(a, b) {\n \"use strict\";\n \ 40 | return a + b; // fôo\n}"; 41 | let f2 = "function multiply(a, b) {\n \"use strict\";\n return a * b;\n}\nfunction divide(a, \ 42 | b) {\n \"use strict\";\n try {\n return multiply(add(a, b), a, b) / c;\n } catch \ 43 | (e) {\n Raven.captureException(e);\n }\n}"; 44 | 45 | let mut raw_files = HashMap::new(); 46 | raw_files.insert("file1.js", f1); 47 | raw_files.insert("file2.js", f2); 48 | 49 | let mut files: HashMap<_, Vec<_>> = HashMap::new(); 50 | files.insert("file1.js", f1.lines().collect()); 51 | files.insert("file2.js", f2.lines().collect()); 52 | 53 | let mut ism = SourceMapIndex::from_reader(input).unwrap(); 54 | assert!(!ism.is_for_ram_bundle()); 55 | 56 | for section_id in 0..ism.get_section_count() { 57 | let section = ism.get_section_mut(section_id).unwrap(); 58 | let map = match section.get_sourcemap_mut().unwrap() { 59 | DecodedMap::Regular(sm) => sm, 60 | _ => panic!("Invalid section type!"), 61 | }; 62 | let contents = { 63 | let filename = map.get_source(0).unwrap(); 64 | raw_files[filename] 65 | }; 66 | map.set_source_contents(0, Some(contents)); 67 | } 68 | let flat_map = ism.flatten().unwrap(); 69 | 70 | let mut out: Vec = vec![]; 71 | flat_map.to_writer(&mut out).ok(); 72 | println!("{}", String::from_utf8(out).unwrap()); 73 | 74 | for token in flat_map.tokens() { 75 | let src = &files[token.get_source().unwrap()]; 76 | if let Some(name) = token.get_name() { 77 | let line = src[token.get_src_line() as usize]; 78 | let idx = token.get_src_col() as usize; 79 | let span = &line[idx..idx + name.len()]; 80 | assert_eq!(span, name); 81 | } 82 | } 83 | 84 | for (src_id, filename) in flat_map.sources().enumerate() { 85 | let ref_contents = &files[filename]; 86 | let contents: Vec<_> = flat_map 87 | .get_source_contents(src_id as u32) 88 | .unwrap_or_else(|| panic!("no source for {}", filename)) 89 | .lines() 90 | .collect(); 91 | assert_eq!(&contents, ref_contents); 92 | 93 | let view = flat_map 94 | .get_source_view(src_id as u32) 95 | .unwrap_or_else(|| panic!("no source for {}", filename)); 96 | assert_eq!(&view.lines().collect::>(), ref_contents); 97 | } 98 | 99 | assert_eq!( 100 | ism.lookup_token(0, 0).unwrap().to_tuple(), 101 | ("file1.js", 0, 0, None) 102 | ); 103 | assert_eq!( 104 | ism.lookup_token(1, 0).unwrap().to_tuple(), 105 | ("file1.js", 2, 12, Some("b")) 106 | ); 107 | assert_eq!( 108 | ism.lookup_token(1, 1).unwrap().to_tuple(), 109 | ("file2.js", 0, 0, None) 110 | ); 111 | assert_eq!( 112 | ism.lookup_token(1, 8).unwrap().to_tuple(), 113 | ("file2.js", 0, 0, None) 114 | ); 115 | assert_eq!( 116 | ism.lookup_token(1, 9).unwrap().to_tuple(), 117 | ("file2.js", 0, 9, Some("multiply")) 118 | ); 119 | } 120 | 121 | #[test] 122 | fn test_indexed_sourcemap_for_react_native() { 123 | let input: &[_] = br#"{ 124 | "version": 3, 125 | "file": "bla", 126 | "sections": [ 127 | { 128 | "offset": { 129 | "line": 0, 130 | "column": 0 131 | }, 132 | "map": { 133 | "version":3, 134 | "sources":["file1.js"], 135 | "names":["add","a","b"], 136 | "mappings":"AAAA,QAASA,KAAIC,EAAGC,GACf,YACA,OAAOD,GAAIC", 137 | "file":"file1.min.js" 138 | } 139 | }, 140 | { 141 | "offset": { 142 | "line": 1, 143 | "column": 0 144 | }, 145 | "map": { 146 | "version":3, 147 | "sources":["file2.js"], 148 | "names":["multiply","a","b","divide","add","c","e","Raven","captureException"], 149 | "mappings":"AAAA,QAASA,UAASC,EAAGC,GACpB,YACA,OAAOD,GAAIC,EAEZ,QAASC,QAAOF,EAAGC,GAClB,YACA,KACC,MAAOF,UAASI,IAAIH,EAAGC,GAAID,EAAGC,GAAKG,EAClC,MAAOC,GACRC,MAAMC,iBAAiBF", 150 | "file":"file2.min.js" 151 | } 152 | } 153 | ], 154 | "x_facebook_offsets": [], 155 | "x_metro_module_paths": [] 156 | }"#; 157 | 158 | let ism = SourceMapIndex::from_reader(input).unwrap(); 159 | assert!(ism.is_for_ram_bundle()); 160 | } 161 | 162 | #[test] 163 | fn test_flatten_indexed_sourcemap_with_ignore_list() { 164 | let input: &[_] = br#"{ 165 | "version": 3, 166 | "file": "bla", 167 | "sections": [ 168 | { 169 | "offset": { 170 | "line": 0, 171 | "column": 0 172 | }, 173 | "map": { 174 | "version":3, 175 | "sources":["file1.js"], 176 | "names":["add","a","b"], 177 | "mappings":"AAAA,QAASA,KAAIC,EAAGC,GACf,YACA,OAAOD,GAAIC", 178 | "file":"file1.min.js" 179 | } 180 | }, 181 | { 182 | "offset": { 183 | "line": 1, 184 | "column": 0 185 | }, 186 | "map": { 187 | "version":3, 188 | "sources":["file2.js"], 189 | "names":["multiply","a","b","divide","add","c","e","Raven","captureException"], 190 | "mappings":"AAAA,QAASA,UAASC,EAAGC,GACpB,YACA,OAAOD,GAAIC,EAEZ,QAASC,QAAOF,EAAGC,GAClB,YACA,KACC,MAAOF,UAASI,IAAIH,EAAGC,GAAID,EAAGC,GAAKG,EAClC,MAAOC,GACRC,MAAMC,iBAAiBF", 191 | "file":"file2.min.js", 192 | "ignoreList": [0] 193 | } 194 | } 195 | ] 196 | }"#; 197 | 198 | let ism = SourceMapIndex::from_reader(input).unwrap(); 199 | assert_eq!( 200 | ism.flatten() 201 | .unwrap() 202 | .ignore_list() 203 | .copied() 204 | .collect::>(), 205 | vec![1] 206 | ); 207 | } 208 | 209 | #[test] 210 | fn test_sourcemap_index_serializes_camel_case_debug_id() { 211 | const DEBUG_ID: &str = "fedcba9876543210fedcba9876543210"; 212 | let input = format!( 213 | r#"{{ 214 | "version": 3, 215 | "file": "bundle.js", 216 | "sections": [], 217 | "debugId": "{}" 218 | }}"#, 219 | DEBUG_ID 220 | ); 221 | 222 | let smi = SourceMapIndex::from_reader(input.as_bytes()).unwrap(); 223 | let mut out = Vec::new(); 224 | smi.to_writer(&mut out).unwrap(); 225 | let serialized = String::from_utf8(out).unwrap(); 226 | 227 | assert!( 228 | serialized.contains(r#""debugId":"#), 229 | "expected camelCase debugId in {}", 230 | serialized 231 | ); 232 | assert!( 233 | !serialized.contains("debug_id"), 234 | "unexpected snake_case key in {}", 235 | serialized 236 | ); 237 | } 238 | -------------------------------------------------------------------------------- /src/encoder.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use bitvec::field::BitField; 4 | use bitvec::order::Lsb0; 5 | use bitvec::view::BitView; 6 | use serde_json::Value; 7 | 8 | use crate::errors::Result; 9 | use crate::jsontypes::{RawSection, RawSectionOffset, RawSourceMap}; 10 | use crate::types::{DecodedMap, SourceMap, SourceMapIndex}; 11 | use crate::vlq::encode_vlq; 12 | 13 | pub trait Encodable { 14 | fn as_raw_sourcemap(&self) -> RawSourceMap; 15 | } 16 | 17 | pub fn encode(sm: &M, mut w: W) -> Result<()> { 18 | let ty = sm.as_raw_sourcemap(); 19 | serde_json::to_writer(&mut w, &ty)?; 20 | Ok(()) 21 | } 22 | 23 | fn encode_vlq_diff(out: &mut String, a: u32, b: u32) { 24 | encode_vlq(out, i64::from(a) - i64::from(b)) 25 | } 26 | 27 | fn encode_rmi(out: &mut Vec, data: &[u8]) { 28 | fn encode_byte(b: u8) -> u8 { 29 | match b { 30 | 0..=25 => b + b'A', 31 | 26..=51 => b + b'a' - 26, 32 | 52..=61 => b + b'0' - 52, 33 | 62 => b'+', 34 | 63 => b'/', 35 | _ => panic!("invalid byte"), 36 | } 37 | } 38 | 39 | let bits = data.view_bits::(); 40 | 41 | // trim zero at the end 42 | let mut last = 0; 43 | for (idx, bit) in bits.iter().enumerate() { 44 | if *bit { 45 | last = idx; 46 | } 47 | } 48 | let bits = &bits[..last + 1]; 49 | 50 | for byte in bits.chunks(6) { 51 | let byte = byte.load::(); 52 | 53 | let encoded = encode_byte(byte); 54 | 55 | out.push(encoded); 56 | } 57 | } 58 | 59 | fn serialize_range_mappings(sm: &SourceMap) -> Option { 60 | let mut buf = Vec::new(); 61 | let mut prev_line = 0; 62 | let mut had_rmi = false; 63 | let mut empty = true; 64 | 65 | let mut idx_of_first_in_line = 0; 66 | 67 | let mut rmi_data = Vec::::new(); 68 | 69 | for (idx, token) in sm.tokens().enumerate() { 70 | if token.is_range() { 71 | had_rmi = true; 72 | empty = false; 73 | 74 | let num = idx - idx_of_first_in_line; 75 | 76 | rmi_data.resize(rmi_data.len() + 2, 0); 77 | 78 | let rmi_bits = rmi_data.view_bits_mut::(); 79 | rmi_bits.set(num, true); 80 | } 81 | 82 | while token.get_dst_line() != prev_line { 83 | if had_rmi { 84 | encode_rmi(&mut buf, &rmi_data); 85 | rmi_data.clear(); 86 | } 87 | 88 | buf.push(b';'); 89 | prev_line += 1; 90 | had_rmi = false; 91 | idx_of_first_in_line = idx; 92 | } 93 | } 94 | if empty { 95 | return None; 96 | } 97 | 98 | if had_rmi { 99 | encode_rmi(&mut buf, &rmi_data); 100 | } 101 | 102 | Some(String::from_utf8(buf).expect("invalid utf8")) 103 | } 104 | 105 | fn serialize_mappings(sm: &SourceMap) -> String { 106 | let mut rv = String::new(); 107 | // dst == minified == generated 108 | let mut prev_dst_line = 0; 109 | let mut prev_dst_col = 0; 110 | let mut prev_src_line = 0; 111 | let mut prev_src_col = 0; 112 | let mut prev_name_id = 0; 113 | let mut prev_src_id = 0; 114 | 115 | for (idx, token) in sm.tokens().enumerate() { 116 | if token.get_dst_line() != prev_dst_line { 117 | prev_dst_col = 0; 118 | while token.get_dst_line() != prev_dst_line { 119 | rv.push(';'); 120 | prev_dst_line += 1; 121 | } 122 | } else if idx > 0 { 123 | if Some(&token) == sm.get_token(idx - 1).as_ref() { 124 | continue; 125 | } 126 | rv.push(','); 127 | } 128 | 129 | encode_vlq_diff(&mut rv, token.get_dst_col(), prev_dst_col); 130 | prev_dst_col = token.get_dst_col(); 131 | 132 | if token.has_source() { 133 | encode_vlq_diff(&mut rv, token.get_src_id(), prev_src_id); 134 | prev_src_id = token.get_src_id(); 135 | encode_vlq_diff(&mut rv, token.get_src_line(), prev_src_line); 136 | prev_src_line = token.get_src_line(); 137 | encode_vlq_diff(&mut rv, token.get_src_col(), prev_src_col); 138 | prev_src_col = token.get_src_col(); 139 | if token.has_name() { 140 | encode_vlq_diff(&mut rv, token.get_name_id(), prev_name_id); 141 | prev_name_id = token.get_name_id(); 142 | } 143 | } 144 | } 145 | 146 | rv 147 | } 148 | 149 | impl Encodable for SourceMap { 150 | fn as_raw_sourcemap(&self) -> RawSourceMap { 151 | let mut have_contents = false; 152 | let contents = self 153 | .source_contents() 154 | .map(|contents| { 155 | if let Some(contents) = contents { 156 | have_contents = true; 157 | Some(contents.to_string()) 158 | } else { 159 | None 160 | } 161 | }) 162 | .collect(); 163 | RawSourceMap { 164 | version: Some(3), 165 | file: self.get_file().map(|x| Value::String(x.to_string())), 166 | sources: Some(self.sources.iter().map(|x| Some(x.to_string())).collect()), 167 | source_root: self.get_source_root().map(|x| x.to_string()), 168 | sources_content: if have_contents { Some(contents) } else { None }, 169 | sections: None, 170 | names: Some(self.names().map(|x| Value::String(x.to_string())).collect()), 171 | range_mappings: serialize_range_mappings(self), 172 | mappings: Some(serialize_mappings(self)), 173 | ignore_list: if self.ignore_list.is_empty() { 174 | None 175 | } else { 176 | Some(self.ignore_list.iter().cloned().collect()) 177 | }, 178 | x_facebook_offsets: None, 179 | x_metro_module_paths: None, 180 | x_facebook_sources: None, 181 | debug_id: self.get_debug_id().into(), 182 | } 183 | } 184 | } 185 | 186 | impl Encodable for SourceMapIndex { 187 | fn as_raw_sourcemap(&self) -> RawSourceMap { 188 | RawSourceMap { 189 | version: Some(3), 190 | file: self.get_file().map(|x| Value::String(x.to_string())), 191 | sources: None, 192 | source_root: None, 193 | sources_content: None, 194 | sections: Some( 195 | self.sections() 196 | .map(|section| RawSection { 197 | offset: RawSectionOffset { 198 | line: section.get_offset_line(), 199 | column: section.get_offset_col(), 200 | }, 201 | url: section.get_url().map(str::to_owned), 202 | map: section 203 | .get_sourcemap() 204 | .map(|sm| Box::new(sm.as_raw_sourcemap())), 205 | }) 206 | .collect(), 207 | ), 208 | names: None, 209 | range_mappings: None, 210 | mappings: None, 211 | ignore_list: None, 212 | x_facebook_offsets: None, 213 | x_metro_module_paths: None, 214 | x_facebook_sources: None, 215 | debug_id: self.debug_id().into(), 216 | } 217 | } 218 | } 219 | 220 | impl Encodable for DecodedMap { 221 | fn as_raw_sourcemap(&self) -> RawSourceMap { 222 | match *self { 223 | DecodedMap::Regular(ref sm) => sm.as_raw_sourcemap(), 224 | DecodedMap::Index(ref smi) => smi.as_raw_sourcemap(), 225 | DecodedMap::Hermes(ref smh) => smh.as_raw_sourcemap(), 226 | } 227 | } 228 | } 229 | 230 | #[cfg(test)] 231 | mod tests { 232 | use super::*; 233 | 234 | #[test] 235 | fn test_encode_rmi() { 236 | fn encode(indices: &[usize]) -> String { 237 | let mut out = vec![]; 238 | 239 | // Fill with zeros while testing 240 | let mut data = vec![0; 256]; 241 | 242 | let bits = data.view_bits_mut::(); 243 | for &i in indices { 244 | bits.set(i, true); 245 | } 246 | 247 | encode_rmi(&mut out, &data); 248 | String::from_utf8(out).unwrap() 249 | } 250 | 251 | // This is 0-based index 252 | assert_eq!(encode(&[12]), "AAB"); 253 | assert_eq!(encode(&[5]), "g"); 254 | assert_eq!(encode(&[0, 11]), "Bg"); 255 | } 256 | 257 | #[test] 258 | fn test_encode_sourcemap_index_no_debug_id() { 259 | let smi = SourceMapIndex::new(Some("test.js".into()), vec![]); 260 | let raw = smi.as_raw_sourcemap(); 261 | 262 | assert_eq!( 263 | raw, 264 | RawSourceMap { 265 | version: Some(3), 266 | file: Some("test.js".into()), 267 | sources: None, 268 | source_root: None, 269 | sources_content: None, 270 | sections: Some(vec![]), 271 | names: None, 272 | range_mappings: None, 273 | mappings: None, 274 | ignore_list: None, 275 | x_facebook_offsets: None, 276 | x_metro_module_paths: None, 277 | x_facebook_sources: None, 278 | debug_id: None.into(), 279 | } 280 | ); 281 | } 282 | 283 | #[test] 284 | fn test_encode_sourcemap_index_debug_id() { 285 | const DEBUG_ID: &str = "0123456789abcdef0123456789abcdef"; 286 | 287 | let smi = SourceMapIndex::new(Some("test.js".into()), vec![]) 288 | .with_debug_id(Some(DEBUG_ID.parse().expect("valid debug id"))); 289 | 290 | let raw = smi.as_raw_sourcemap(); 291 | assert_eq!( 292 | raw, 293 | RawSourceMap { 294 | version: Some(3), 295 | file: Some("test.js".into()), 296 | sources: None, 297 | source_root: None, 298 | sources_content: None, 299 | sections: Some(vec![]), 300 | names: None, 301 | range_mappings: None, 302 | mappings: None, 303 | ignore_list: None, 304 | x_facebook_offsets: None, 305 | x_metro_module_paths: None, 306 | x_facebook_sources: None, 307 | debug_id: Some(DEBUG_ID.parse().expect("valid debug id")).into(), 308 | } 309 | ); 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /src/builder.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(unix, windows, target_os = "redox")), allow(unused_imports))] 2 | 3 | use std::collections::BTreeSet; 4 | use std::env; 5 | use std::fs; 6 | use std::io::Read; 7 | use std::path::{Path, PathBuf}; 8 | use std::sync::Arc; 9 | 10 | use debugid::DebugId; 11 | use rustc_hash::FxHashMap; 12 | use url::Url; 13 | 14 | use crate::errors::Result; 15 | use crate::types::{RawToken, SourceMap, Token}; 16 | 17 | /// Helper for sourcemap generation 18 | /// 19 | /// This helper exists because generating and modifying `SourceMap` 20 | /// objects is generally not very comfortable. As a general aid this 21 | /// type can help. 22 | pub struct SourceMapBuilder { 23 | file: Option>, 24 | name_map: FxHashMap, u32>, 25 | names: Vec>, 26 | tokens: Vec, 27 | source_map: FxHashMap, u32>, 28 | source_root: Option>, 29 | sources: Vec>, 30 | source_contents: Vec>>, 31 | sources_mapping: Vec, 32 | ignore_list: BTreeSet, 33 | debug_id: Option, 34 | } 35 | 36 | #[cfg(any(unix, windows, target_os = "redox"))] 37 | fn resolve_local_reference(base: &Url, reference: &str) -> Option { 38 | let url = match base.join(reference) { 39 | Ok(url) => { 40 | if url.scheme() != "file" { 41 | return None; 42 | } 43 | url 44 | } 45 | Err(_) => { 46 | return None; 47 | } 48 | }; 49 | 50 | url.to_file_path().ok() 51 | } 52 | 53 | impl SourceMapBuilder { 54 | /// Creates a new source map builder and sets the file. 55 | pub fn new(file: Option<&str>) -> SourceMapBuilder { 56 | SourceMapBuilder { 57 | file: file.map(Into::into), 58 | name_map: FxHashMap::default(), 59 | names: vec![], 60 | tokens: vec![], 61 | source_map: FxHashMap::default(), 62 | source_root: None, 63 | sources: vec![], 64 | source_contents: vec![], 65 | sources_mapping: vec![], 66 | ignore_list: BTreeSet::default(), 67 | debug_id: None, 68 | } 69 | } 70 | 71 | /// Sets the debug id for the sourcemap (optional) 72 | pub fn set_debug_id(&mut self, debug_id: Option) { 73 | self.debug_id = debug_id; 74 | } 75 | 76 | /// Sets the file for the sourcemap (optional) 77 | pub fn set_file>>(&mut self, value: Option) { 78 | self.file = value.map(Into::into); 79 | } 80 | 81 | /// Returns the currently set file. 82 | pub fn get_file(&self) -> Option<&str> { 83 | self.file.as_deref() 84 | } 85 | 86 | /// Sets a new value for the source_root. 87 | pub fn set_source_root>>(&mut self, value: Option) { 88 | self.source_root = value.map(Into::into); 89 | } 90 | 91 | /// Returns the embedded source_root in case there is one. 92 | pub fn get_source_root(&self) -> Option<&str> { 93 | self.source_root.as_deref() 94 | } 95 | 96 | /// Registers a new source with the builder and returns the source ID. 97 | pub fn add_source(&mut self, src: &str) -> u32 { 98 | self.add_source_with_id(src, !0) 99 | } 100 | 101 | fn add_source_with_id(&mut self, src: &str, old_id: u32) -> u32 { 102 | let count = self.sources.len() as u32; 103 | let id = *self.source_map.entry(src.into()).or_insert(count); 104 | if id == count { 105 | self.sources.push(src.into()); 106 | self.sources_mapping.push(old_id); 107 | } 108 | id 109 | } 110 | 111 | /// Changes the source name for an already set source. 112 | pub fn set_source(&mut self, src_id: u32, src: &str) { 113 | assert!(src_id != !0, "Cannot set sources for tombstone source id"); 114 | self.sources[src_id as usize] = src.into(); 115 | } 116 | 117 | /// Looks up a source name for an ID. 118 | pub fn get_source(&self, src_id: u32) -> Option<&str> { 119 | self.sources.get(src_id as usize).map(|x| &x[..]) 120 | } 121 | 122 | pub fn add_to_ignore_list(&mut self, src_id: u32) { 123 | self.ignore_list.insert(src_id); 124 | } 125 | 126 | /// Sets the source contents for an already existing source. 127 | pub fn set_source_contents(&mut self, src_id: u32, contents: Option<&str>) { 128 | assert!(src_id != !0, "Cannot set sources for tombstone source id"); 129 | if self.sources.len() > self.source_contents.len() { 130 | self.source_contents.resize(self.sources.len(), None); 131 | } 132 | self.source_contents[src_id as usize] = contents.map(Into::into); 133 | } 134 | 135 | /// Returns the current source contents for a source. 136 | pub fn get_source_contents(&self, src_id: u32) -> Option<&str> { 137 | self.source_contents 138 | .get(src_id as usize) 139 | .and_then(|x| x.as_ref().map(|x| &x[..])) 140 | } 141 | 142 | /// Checks if a given source ID has source contents available. 143 | pub fn has_source_contents(&self, src_id: u32) -> bool { 144 | self.get_source_contents(src_id).is_some() 145 | } 146 | 147 | /// Loads source contents from locally accessible files if referenced 148 | /// accordingly. Returns the number of loaded source contents 149 | #[cfg(any(unix, windows, target_os = "redox"))] 150 | pub fn load_local_source_contents(&mut self, base_path: Option<&Path>) -> Result { 151 | let mut abs_path = env::current_dir()?; 152 | if let Some(path) = base_path { 153 | abs_path.push(path); 154 | } 155 | let base_url = Url::from_directory_path(&abs_path).unwrap(); 156 | 157 | let mut to_read = vec![]; 158 | for (source, &src_id) in self.source_map.iter() { 159 | if self.has_source_contents(src_id) { 160 | continue; 161 | } 162 | if let Some(path) = resolve_local_reference(&base_url, source) { 163 | to_read.push((src_id, path)); 164 | } 165 | } 166 | 167 | let rv = to_read.len(); 168 | for (src_id, path) in to_read { 169 | if let Ok(mut f) = fs::File::open(path) { 170 | let mut contents = String::new(); 171 | if f.read_to_string(&mut contents).is_ok() { 172 | self.set_source_contents(src_id, Some(&contents)); 173 | } 174 | } 175 | } 176 | 177 | Ok(rv) 178 | } 179 | 180 | /// Registers a name with the builder and returns the name ID. 181 | pub fn add_name(&mut self, name: &str) -> u32 { 182 | let count = self.names.len() as u32; 183 | let id = *self.name_map.entry(name.into()).or_insert(count); 184 | if id == count { 185 | self.names.push(name.into()); 186 | } 187 | id 188 | } 189 | 190 | /// Adds a new mapping to the builder. 191 | #[allow(clippy::too_many_arguments)] 192 | pub fn add( 193 | &mut self, 194 | dst_line: u32, 195 | dst_col: u32, 196 | src_line: u32, 197 | src_col: u32, 198 | source: Option<&str>, 199 | name: Option<&str>, 200 | is_range: bool, 201 | ) -> RawToken { 202 | self.add_with_id( 203 | dst_line, dst_col, src_line, src_col, source, !0, name, is_range, 204 | ) 205 | } 206 | 207 | #[allow(clippy::too_many_arguments)] 208 | fn add_with_id( 209 | &mut self, 210 | dst_line: u32, 211 | dst_col: u32, 212 | src_line: u32, 213 | src_col: u32, 214 | source: Option<&str>, 215 | source_id: u32, 216 | name: Option<&str>, 217 | is_range: bool, 218 | ) -> RawToken { 219 | let src_id = match source { 220 | Some(source) => self.add_source_with_id(source, source_id), 221 | None => !0, 222 | }; 223 | let name_id = match name { 224 | Some(name) => self.add_name(name), 225 | None => !0, 226 | }; 227 | let raw = RawToken { 228 | dst_line, 229 | dst_col, 230 | src_line, 231 | src_col, 232 | src_id, 233 | name_id, 234 | is_range, 235 | }; 236 | self.tokens.push(raw); 237 | raw 238 | } 239 | 240 | /// Adds a new mapping to the builder. 241 | #[allow(clippy::too_many_arguments)] 242 | pub fn add_raw( 243 | &mut self, 244 | dst_line: u32, 245 | dst_col: u32, 246 | src_line: u32, 247 | src_col: u32, 248 | source: Option, 249 | name: Option, 250 | is_range: bool, 251 | ) -> RawToken { 252 | let src_id = source.unwrap_or(!0); 253 | let name_id = name.unwrap_or(!0); 254 | let raw = RawToken { 255 | dst_line, 256 | dst_col, 257 | src_line, 258 | src_col, 259 | src_id, 260 | name_id, 261 | is_range, 262 | }; 263 | self.tokens.push(raw); 264 | raw 265 | } 266 | 267 | /// Shortcut for adding a new mapping based of an already existing token, 268 | /// optionally removing the name. 269 | pub fn add_token(&mut self, token: &Token<'_>, with_name: bool) -> RawToken { 270 | let name = if with_name { token.get_name() } else { None }; 271 | self.add_with_id( 272 | token.get_dst_line(), 273 | token.get_dst_col(), 274 | token.get_src_line(), 275 | token.get_src_col(), 276 | token.get_source(), 277 | token.get_src_id(), 278 | name, 279 | token.is_range(), 280 | ) 281 | } 282 | 283 | /// Strips common prefixes from the sources in the builder 284 | pub fn strip_prefixes>(&mut self, prefixes: &[S]) { 285 | for source in self.sources.iter_mut() { 286 | for prefix in prefixes { 287 | let mut prefix = prefix.as_ref().to_string(); 288 | if !prefix.ends_with('/') { 289 | prefix.push('/'); 290 | } 291 | if source.starts_with(&prefix) { 292 | *source = source[prefix.len()..].into(); 293 | break; 294 | } 295 | } 296 | } 297 | } 298 | 299 | pub(crate) fn take_mapping(&mut self) -> Vec { 300 | std::mem::take(&mut self.sources_mapping) 301 | } 302 | 303 | /// Converts the builder into a sourcemap. 304 | pub fn into_sourcemap(self) -> SourceMap { 305 | let contents = if !self.source_contents.is_empty() { 306 | Some(self.source_contents) 307 | } else { 308 | None 309 | }; 310 | 311 | let mut sm = SourceMap::new(self.file, self.tokens, self.names, self.sources, contents); 312 | sm.set_source_root(self.source_root); 313 | sm.set_debug_id(self.debug_id); 314 | for ignored_src_id in self.ignore_list { 315 | sm.add_to_ignore_list(ignored_src_id); 316 | } 317 | 318 | sm 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /src/sourceview.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::str; 3 | use std::sync::Arc; 4 | use std::sync::Mutex; 5 | 6 | use if_chain::if_chain; 7 | 8 | use crate::detector::{locate_sourcemap_reference_slice, SourceMapRef}; 9 | use crate::errors::Result; 10 | use crate::js_identifiers::{get_javascript_token, is_valid_javascript_identifier}; 11 | use crate::types::Token; 12 | 13 | /// An iterator that iterates over tokens in reverse. 14 | pub struct RevTokenIter<'view, 'map> { 15 | sv: &'view SourceView, 16 | token: Option>, 17 | source_line: Option<(&'view str, usize, usize, usize)>, 18 | } 19 | 20 | impl<'view, 'map> Iterator for RevTokenIter<'view, 'map> { 21 | type Item = (Token<'map>, Option<&'view str>); 22 | 23 | fn next(&mut self) -> Option<(Token<'map>, Option<&'view str>)> { 24 | let token = self.token.take()?; 25 | let idx = token.idx; 26 | 27 | if idx > 0 { 28 | self.token = token.sm.get_token(idx - 1); 29 | } 30 | 31 | // if we are going to the same line as we did last iteration, we don't have to scan 32 | // up to it again. For normal sourcemaps this should mean we only ever go to the 33 | // line once. 34 | let (source_line, last_char_offset, last_byte_offset) = if_chain! { 35 | if let Some((source_line, dst_line, last_char_offset, 36 | last_byte_offset)) = self.source_line; 37 | 38 | if dst_line == token.get_dst_line() as usize; 39 | then { 40 | (source_line, last_char_offset, last_byte_offset) 41 | } else { 42 | if let Some(source_line) = self.sv.get_line(token.get_dst_line()) { 43 | (source_line, !0, !0) 44 | } else { 45 | // if we can't find the line, return am empty one 46 | ("", !0, !0) 47 | } 48 | } 49 | }; 50 | 51 | // find the byte offset where our token starts 52 | let byte_offset = if last_byte_offset == !0 { 53 | let mut off = 0; 54 | let mut idx = 0; 55 | for c in source_line.chars() { 56 | if idx >= token.get_dst_col() as usize { 57 | break; 58 | } 59 | off += c.len_utf8(); 60 | idx += c.len_utf16(); 61 | } 62 | off 63 | } else { 64 | let chars_to_move = last_char_offset - token.get_dst_col() as usize; 65 | let mut new_offset = last_byte_offset; 66 | let mut idx = 0; 67 | for c in source_line 68 | .get(..last_byte_offset) 69 | .unwrap_or("") 70 | .chars() 71 | .rev() 72 | { 73 | if idx >= chars_to_move { 74 | break; 75 | } 76 | new_offset -= c.len_utf8(); 77 | idx += c.len_utf16(); 78 | } 79 | new_offset 80 | }; 81 | 82 | // remember where we were 83 | self.source_line = Some(( 84 | source_line, 85 | token.get_dst_line() as usize, 86 | token.get_dst_col() as usize, 87 | byte_offset, 88 | )); 89 | 90 | // in case we run out of bounds here we reset the cache 91 | if byte_offset >= source_line.len() { 92 | self.source_line = None; 93 | Some((token, None)) 94 | } else { 95 | Some(( 96 | token, 97 | source_line 98 | .get(byte_offset..) 99 | .and_then(get_javascript_token), 100 | )) 101 | } 102 | } 103 | } 104 | 105 | pub struct Lines<'a> { 106 | sv: &'a SourceView, 107 | idx: u32, 108 | } 109 | 110 | impl<'a> Iterator for Lines<'a> { 111 | type Item = &'a str; 112 | 113 | fn next(&mut self) -> Option<&'a str> { 114 | if let Some(line) = self.sv.get_line(self.idx) { 115 | self.idx += 1; 116 | Some(line) 117 | } else { 118 | None 119 | } 120 | } 121 | } 122 | 123 | /// Provides efficient access to minified sources. 124 | /// 125 | /// This type is used to implement fairly efficient source mapping 126 | /// operations. 127 | pub struct SourceView { 128 | source: Arc, 129 | line_end_offsets: Mutex>, 130 | } 131 | 132 | impl Clone for SourceView { 133 | fn clone(&self) -> SourceView { 134 | SourceView { 135 | source: self.source.clone(), 136 | line_end_offsets: Mutex::new(vec![]), 137 | } 138 | } 139 | } 140 | 141 | impl fmt::Debug for SourceView { 142 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 143 | f.debug_struct("SourceView") 144 | .field("source", &self.source()) 145 | .finish() 146 | } 147 | } 148 | 149 | impl PartialEq for SourceView { 150 | fn eq(&self, other: &Self) -> bool { 151 | self.source == other.source 152 | } 153 | } 154 | 155 | impl SourceView { 156 | /// Creates an optimized view of a given source. 157 | pub fn new(source: Arc) -> SourceView { 158 | SourceView { 159 | source, 160 | line_end_offsets: Mutex::new(vec![]), 161 | } 162 | } 163 | 164 | /// Creates an optimized view from a given source string 165 | pub fn from_string(source: String) -> SourceView { 166 | SourceView { 167 | source: source.into(), 168 | line_end_offsets: Mutex::new(vec![]), 169 | } 170 | } 171 | 172 | /// Returns a requested minified line. 173 | pub fn get_line(&self, idx: u32) -> Option<&str> { 174 | let idx = idx as usize; 175 | 176 | let get_from_line_ends = |line_ends: &[LineEndOffset]| { 177 | let end = line_ends.get(idx)?.to_end_index(); 178 | let start = if idx == 0 { 179 | 0 180 | } else { 181 | line_ends[idx - 1].to_start_index() 182 | }; 183 | Some(&self.source[start..end]) 184 | }; 185 | 186 | let mut line_ends = self 187 | .line_end_offsets 188 | .lock() 189 | .unwrap_or_else(|e| e.into_inner()); 190 | 191 | if let Some(line) = get_from_line_ends(&line_ends) { 192 | return Some(line); 193 | } 194 | 195 | // check whether we've processed the entire string - the end of the 196 | // last-processed line would be the same as the end of the string 197 | if line_ends 198 | .last() 199 | .is_some_and(|i| i.to_end_index() == self.source.len()) 200 | { 201 | return None; 202 | } 203 | 204 | let mut rest_offset = line_ends.last().map_or(0, |i| i.to_start_index()); 205 | let mut rest = &self.source[rest_offset..]; 206 | let mut done = false; 207 | 208 | while !done { 209 | let line_term = if let Some(idx) = rest.find(['\n', '\r']) { 210 | rest_offset += idx; 211 | rest = &rest[idx..]; 212 | if rest.starts_with("\r\n") { 213 | LineTerminator::CrLf 214 | } else { 215 | LineTerminator::LfOrCr 216 | } 217 | } else { 218 | rest_offset += rest.len(); 219 | rest = &rest[rest.len()..]; 220 | done = true; 221 | LineTerminator::Eof 222 | }; 223 | 224 | line_ends.push(LineEndOffset::new(rest_offset, line_term)); 225 | rest_offset += line_term as usize; 226 | rest = &rest[line_term as usize..]; 227 | if let Some(line) = get_from_line_ends(&line_ends) { 228 | return Some(line); 229 | } 230 | } 231 | 232 | None 233 | } 234 | 235 | /// Returns a line slice. 236 | /// 237 | /// Note that columns are indexed as JavaScript WTF-16 columns. 238 | pub fn get_line_slice(&self, line: u32, col: u32, span: u32) -> Option<&str> { 239 | self.get_line(line).and_then(|line| { 240 | let mut off = 0; 241 | let mut idx = 0; 242 | let mut char_iter = line.chars().peekable(); 243 | 244 | while let Some(&c) = char_iter.peek() { 245 | if idx >= col as usize { 246 | break; 247 | } 248 | char_iter.next(); 249 | off += c.len_utf8(); 250 | idx += c.len_utf16(); 251 | } 252 | 253 | let mut off_end = off; 254 | for c in char_iter { 255 | if idx >= (col + span) as usize { 256 | break; 257 | } 258 | off_end += c.len_utf8(); 259 | idx += c.len_utf16(); 260 | } 261 | 262 | if idx < ((col + span) as usize) { 263 | None 264 | } else { 265 | line.get(off..off_end) 266 | } 267 | }) 268 | } 269 | 270 | /// Returns an iterator over all lines. 271 | pub fn lines(&self) -> Lines<'_> { 272 | Lines { sv: self, idx: 0 } 273 | } 274 | 275 | /// Returns the source. 276 | pub fn source(&self) -> &str { 277 | &self.source 278 | } 279 | 280 | fn rev_token_iter<'this, 'map>(&'this self, token: Token<'map>) -> RevTokenIter<'this, 'map> { 281 | RevTokenIter { 282 | sv: self, 283 | token: Some(token), 284 | source_line: None, 285 | } 286 | } 287 | 288 | /// Given a token and minified function name this attemps to resolve the 289 | /// name to an original function name. 290 | /// 291 | /// This invokes some guesswork and requires access to the original minified 292 | /// source. This will not yield proper results for anonymous functions or 293 | /// functions that do not have clear function names. (For instance it's 294 | /// recommended that dotted function names are not passed to this 295 | /// function). 296 | pub fn get_original_function_name<'map>( 297 | &self, 298 | token: Token<'map>, 299 | minified_name: &str, 300 | ) -> Option<&'map str> { 301 | if !is_valid_javascript_identifier(minified_name) { 302 | return None; 303 | } 304 | 305 | let mut iter = self.rev_token_iter(token).take(128).peekable(); 306 | 307 | while let Some((token, original_identifier)) = iter.next() { 308 | if_chain! { 309 | if original_identifier == Some(minified_name); 310 | if let Some(item) = iter.peek(); 311 | if item.1 == Some("function"); 312 | then { 313 | return token.get_name(); 314 | } 315 | } 316 | } 317 | 318 | None 319 | } 320 | 321 | /// Returns the number of lines. 322 | pub fn line_count(&self) -> usize { 323 | self.get_line(!0); 324 | self.line_end_offsets.lock().unwrap().len() 325 | } 326 | 327 | /// Returns the source map reference in the source view. 328 | pub fn sourcemap_reference(&self) -> Result> { 329 | locate_sourcemap_reference_slice(self.source.as_bytes()) 330 | } 331 | } 332 | 333 | /// A wrapper around an index that stores a [`LineTerminator`] in its 2 lowest bits. 334 | // We use `u64` instead of `usize` in order to not lose data when bit-packing 335 | // on 32-bit targets. 336 | #[derive(Clone, Copy)] 337 | struct LineEndOffset(u64); 338 | 339 | #[derive(Clone, Copy)] 340 | enum LineTerminator { 341 | Eof = 0, 342 | LfOrCr = 1, 343 | CrLf = 2, 344 | } 345 | 346 | impl LineEndOffset { 347 | fn new(index: usize, line_end: LineTerminator) -> Self { 348 | let shifted = (index as u64) << 2; 349 | 350 | Self(shifted | line_end as u64) 351 | } 352 | 353 | /// Return the index of the end of this line. 354 | fn to_end_index(self) -> usize { 355 | (self.0 >> 2) as usize 356 | } 357 | 358 | /// Return the index of the start of the next line. 359 | fn to_start_index(self) -> usize { 360 | self.to_end_index() + (self.0 & 0b11) as usize 361 | } 362 | } 363 | 364 | #[cfg(test)] 365 | mod tests { 366 | use super::*; 367 | 368 | #[test] 369 | #[allow(clippy::cognitive_complexity)] 370 | fn test_minified_source_view() { 371 | let view = SourceView::new("a\nb\nc".into()); 372 | assert_eq!(view.get_line(0), Some("a")); 373 | assert_eq!(view.get_line(0), Some("a")); 374 | assert_eq!(view.get_line(2), Some("c")); 375 | assert_eq!(view.get_line(1), Some("b")); 376 | assert_eq!(view.get_line(3), None); 377 | 378 | assert_eq!(view.line_count(), 3); 379 | 380 | let view = SourceView::new("a\r\nb\r\nc".into()); 381 | assert_eq!(view.get_line(0), Some("a")); 382 | assert_eq!(view.get_line(0), Some("a")); 383 | assert_eq!(view.get_line(2), Some("c")); 384 | assert_eq!(view.get_line(1), Some("b")); 385 | assert_eq!(view.get_line(3), None); 386 | 387 | assert_eq!(view.line_count(), 3); 388 | 389 | let view = SourceView::new("abc👌def\nblah".into()); 390 | assert_eq!(view.get_line_slice(0, 0, 3), Some("abc")); 391 | assert_eq!(view.get_line_slice(0, 3, 1), Some("👌")); 392 | assert_eq!(view.get_line_slice(0, 3, 2), Some("👌")); 393 | assert_eq!(view.get_line_slice(0, 3, 3), Some("👌d")); 394 | assert_eq!(view.get_line_slice(0, 0, 4), Some("abc👌")); 395 | assert_eq!(view.get_line_slice(0, 0, 5), Some("abc👌")); 396 | assert_eq!(view.get_line_slice(0, 0, 6), Some("abc👌d")); 397 | assert_eq!(view.get_line_slice(1, 0, 4), Some("blah")); 398 | assert_eq!(view.get_line_slice(1, 0, 5), None); 399 | assert_eq!(view.get_line_slice(1, 0, 12), None); 400 | 401 | let view = SourceView::new("a\nb\nc\n".into()); 402 | assert_eq!(view.get_line(0), Some("a")); 403 | assert_eq!(view.get_line(1), Some("b")); 404 | assert_eq!(view.get_line(2), Some("c")); 405 | assert_eq!(view.get_line(3), Some("")); 406 | assert_eq!(view.get_line(4), None); 407 | 408 | fn is_send() {} 409 | fn is_sync() {} 410 | is_send::(); 411 | is_sync::(); 412 | } 413 | } 414 | -------------------------------------------------------------------------------- /tests/fixtures/react-native-hermes/output.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["node_modules/metro/src/lib/polyfills/require.js","node_modules/react-native/Libraries/polyfills/console.js","node_modules/react-native/Libraries/polyfills/error-guard.js","node_modules/react-native/Libraries/polyfills/Object.es7.js","input.js","module.js"],"names":["__DEV__","Object","metroRequire","metroImportDefault","metroImportAll","unpackModuleId","modules","EMPTY","createHotReloadingObject","verboseNamesToModuleIds","global","console","initializingModuleIds","module","guardedLoadModule","Error","exports","hasOwnProperty","inGuard","loadModuleImplementation","ID_MASK_SHIFT","moduleId","LOCAL_ID_MASK","value","moduleDefinersBySegmentID","unknownModuleError","RefreshRuntime","registerExportsForReactRefresh","moduleThrewError","hot","Set","pendingModuleIDs","isReactRefreshBoundary","performFullRefresh","runUpdatedModule","updatedID","shouldInvalidateReactRefreshBoundary","reactRefreshTimeout","setTimeout","Refresh","window","hasExports","getRefreshBoundarySignature","moduleID","LOG_LEVELS","Array","level","INSPECTOR_LEVELS","INSPECTOR_FRAMES_TO_SKIP","groupStack","groupFormat","inspect","element","OBJECT_COLUMN_NAME","rows","columnWidths","stringRows","space","repeat","cell","Math","msg","GROUP_OPEN","GROUP_PAD","GROUP_CLOSE","stylizeNoColor","formatValue","hash","formatPrimitive","arrayToHash","isError","isFunction","isRegExp","isDate","isArray","RegExp","Date","formatError","formatArray","reduceToSingleString","braces","formatProperty","ctx","recurseTimes","visibleKeys","array","isUndefined","isString","isNumber","isBoolean","isNull","JSON","String","output","name","base","numLinesEst","prev","arg","isObject","objectToString","e","originalConsole","methodName","reactNativeMethod","_globalHandler","_inGuard","ErrorUtils","fun","context","guardName","TypeError"],"mappings":"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","x_facebook_sources":[[{"names":["","global.$RefreshReg$","global.$RefreshSig$","","clear","define","metroRequire","initializingModuleIds.slice.map$argument_0","metroImportDefault","metroImportAll","guardedLoadModule","unpackModuleId","packModuleId","registerSegment","loadModuleImplementation","unknownModuleError","moduleThrewError","metroRequire.Systrace.beginEvent","metroRequire.Systrace.endEvent","metroRequire.getModules","createHotReloadingObject","hot.accept","hot.dispose","metroHotUpdateModule","parentIDs.forEach$argument_0","setTimeout$argument_0","runUpdatedModule","performFullRefresh","isReactRefreshBoundary","shouldInvalidateReactRefreshBoundary","getRefreshBoundarySignature","registerExportsForReactRefresh"],"mappings":"AAA;wBCyB,QD;wBEE,MC,YH;AIG;CJM;AKO;CL2C;AME;aCyB,2DD;CNe;AQE;CRoB;ASI;CTmC;AUK;CViB;AWK;CXO;AYI;CZE;AaK;CbE;AcE;8BbqE;SaE;CduD;AeE;CfU;AgBE;ChBK;gBiBI,QjB;ckBC,QlB;4BmBG;GnBE;iCoBE;cCK;ODG;eEC;OFE;GpBG;+BuBI;wBCgG,2CD;yCEgG;SFI;GvBG;2B0BE;G1BuE;6B2BE;G3BkB;+B4BE;G5BmC;6C6BE;G7BmB;oC8BE;G9B2B;uC+BE;G/BqB"}],[{"names":["","","inspect","stylizeNoColor","arrayToHash","array.forEach$argument_0","formatValue","keys.map$argument_0","formatPrimitive","formatError","formatArray","keys.forEach$argument_0","formatProperty","str.split.map$argument_0","reduceToSingleString","output.reduce$argument_0","isArray","isBoolean","isNull","isNullOrUndefined","isNumber","isString","isSymbol","isUndefined","isRegExp","isObject","isDate","isError","isFunction","objectToString","hasOwnProperty","getNativeLogFunction","Array.prototype.map.call$argument_1","repeat","Array.apply.map$argument_0","consoleTablePolyfill","columns.forEach$argument_0","joinRow","row.map$argument_0","columnWidths.map$argument_0","groupFormat","consoleGroupPolyfill","consoleGroupCollapsedPolyfill","consoleGroupEndPolyfill","consoleAssertPolyfill","Object.keys.forEach$argument_0","methodName","forEach$argument_0","consoleLoggingStub"],"mappings":"AAA;iBCiB;ECwB;GDO;EEE;GFE;EGE;kBCG;KDE;GHG;EKE;wBC6F;ODS;GLM;EOE;GPgB;EQE;GRE;ESE;iBCkB;KDM;GTE;EWE;mBC4B;eDE;qBCQ;iBDE;GX0B;EaE;+BCE;KDI;Gbc;EeI;GfE;EgBE;GhBE;EiBE;GjBE;EkBE;GlBE;EmBE;GnBE;EoBE;GpBE;EqBE;GrBE;EsBE;GtBE;EuBE;GvBE;EwBE;GxBE;EyBE;GzBE;E0BE;G1BK;E2BE;G3BE;E4BE;G5BE;E6BE;G7BE;CDG;A+BmB;S9BC;yB+BM;S/BE;G8BuB;C/BC;AiCE;yCCC;GDE;CjCC;AmCE;kBCwB;GDQ;EEI;wBCC;KDG;GFG;oCIE;GJE;CnCc;AwCQ;CxCG;AyCE;CzCG;A0CE;C1CG;A2CE;C3CG;A4CE;C5CI;iC6CmC;8BCG;SDW;K7CE;c+CY;8BDE;SCE;K/CE;8BgDG,gChD"}],[{"names":["","onError","ErrorUtils.setGlobalHandler","ErrorUtils.getGlobalHandler","ErrorUtils.reportError","ErrorUtils.reportFatalError","ErrorUtils.applyWithGuard","ErrorUtils.applyWithGuardIfNeeded","ErrorUtils.inGuard","ErrorUtils.guard","guarded"],"mappings":"AAA;mCCqB;CDK;EEW;GFE;EGC;GHE;EIC;GJE;EKC;GLG;EMC;GNmB;EOC;GPY;EQC;GRE;ESC;ICY;KDQ;GTG"}],[{"names":["","","entries","values"],"mappings":"AAA;CCW;qBCU;KDa;oBEQ;KFa;CDE"}],[{"names":[""],"mappings":"AAA"}],[{"names":["","foo"],"mappings":"AAA,OC;CDE"}]]} -------------------------------------------------------------------------------- /src/decoder.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::io::{BufReader, Read}; 3 | 4 | use bitvec::field::BitField; 5 | use bitvec::order::Lsb0; 6 | use bitvec::vec::BitVec; 7 | use serde_json::Value; 8 | 9 | use crate::errors::{Error, Result}; 10 | use crate::hermes::decode_hermes; 11 | use crate::jsontypes::RawSourceMap; 12 | use crate::types::{DecodedMap, RawToken, SourceMap, SourceMapIndex, SourceMapSection}; 13 | use crate::vlq::parse_vlq_segment_into; 14 | 15 | #[derive(PartialEq, Eq)] 16 | enum HeaderState { 17 | Undecided, 18 | Junk, 19 | AwaitingNewline, 20 | PastHeader, 21 | } 22 | 23 | pub struct StripHeaderReader { 24 | r: R, 25 | header_state: HeaderState, 26 | } 27 | 28 | impl StripHeaderReader { 29 | pub fn new(reader: R) -> StripHeaderReader { 30 | StripHeaderReader { 31 | r: reader, 32 | header_state: HeaderState::Undecided, 33 | } 34 | } 35 | } 36 | 37 | fn is_junk_json(byte: u8) -> bool { 38 | byte == b')' || byte == b']' || byte == b'}' || byte == b'\'' 39 | } 40 | 41 | impl Read for StripHeaderReader { 42 | #[inline(always)] 43 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 44 | if self.header_state == HeaderState::PastHeader { 45 | return self.r.read(buf); 46 | } 47 | self.strip_head_read(buf) 48 | } 49 | } 50 | 51 | impl StripHeaderReader { 52 | fn strip_head_read(&mut self, buf: &mut [u8]) -> io::Result { 53 | let mut backing = vec![0; buf.len()]; 54 | let local_buf: &mut [u8] = &mut backing; 55 | 56 | loop { 57 | let read = self.r.read(local_buf)?; 58 | if read == 0 { 59 | return Ok(0); 60 | } 61 | for (offset, &byte) in local_buf[0..read].iter().enumerate() { 62 | self.header_state = match self.header_state { 63 | HeaderState::Undecided => { 64 | if is_junk_json(byte) { 65 | HeaderState::Junk 66 | } else { 67 | buf[..read].copy_from_slice(&local_buf[..read]); 68 | self.header_state = HeaderState::PastHeader; 69 | return Ok(read); 70 | } 71 | } 72 | HeaderState::Junk => { 73 | if byte == b'\r' { 74 | HeaderState::AwaitingNewline 75 | } else if byte == b'\n' { 76 | HeaderState::PastHeader 77 | } else { 78 | HeaderState::Junk 79 | } 80 | } 81 | HeaderState::AwaitingNewline => { 82 | if byte == b'\n' { 83 | HeaderState::PastHeader 84 | } else { 85 | Err(io::Error::new( 86 | io::ErrorKind::InvalidData, 87 | "expected newline", 88 | ))? 89 | } 90 | } 91 | HeaderState::PastHeader => { 92 | let rem = read - offset; 93 | buf[..rem].copy_from_slice(&local_buf[offset..read]); 94 | return Ok(rem); 95 | } 96 | }; 97 | } 98 | } 99 | } 100 | } 101 | 102 | pub fn strip_junk_header(slice: &[u8]) -> io::Result<&[u8]> { 103 | if slice.is_empty() || !is_junk_json(slice[0]) { 104 | return Ok(slice); 105 | } 106 | let mut need_newline = false; 107 | for (idx, &byte) in slice.iter().enumerate() { 108 | if need_newline && byte != b'\n' { 109 | Err(io::Error::new( 110 | io::ErrorKind::InvalidData, 111 | "expected newline", 112 | ))? 113 | } else if is_junk_json(byte) { 114 | continue; 115 | } else if byte == b'\r' { 116 | need_newline = true; 117 | } else if byte == b'\n' { 118 | return Ok(&slice[idx..]); 119 | } 120 | } 121 | Ok(&slice[slice.len()..]) 122 | } 123 | 124 | /// Decodes range mappping bitfield string into index 125 | fn decode_rmi(rmi_str: &str, val: &mut BitVec) -> Result<()> { 126 | val.clear(); 127 | val.resize(rmi_str.len() * 6, false); 128 | 129 | for (idx, &byte) in rmi_str.as_bytes().iter().enumerate() { 130 | let byte = match byte { 131 | b'A'..=b'Z' => byte - b'A', 132 | b'a'..=b'z' => byte - b'a' + 26, 133 | b'0'..=b'9' => byte - b'0' + 52, 134 | b'+' => 62, 135 | b'/' => 63, 136 | _ => { 137 | return Err(Error::InvalidBase64(byte as char)); 138 | } 139 | }; 140 | 141 | val[6 * idx..6 * (idx + 1)].store_le::(byte); 142 | } 143 | 144 | Ok(()) 145 | } 146 | 147 | pub fn decode_regular(rsm: RawSourceMap) -> Result { 148 | let mut dst_col; 149 | 150 | // Source IDs, lines, columns, and names are "running" values. 151 | // Each token (except the first) contains the delta from the previous value. 152 | let mut running_src_id = 0; 153 | let mut running_src_line = 0; 154 | let mut running_src_col = 0; 155 | let mut running_name_id = 0; 156 | 157 | let names = rsm.names.unwrap_or_default(); 158 | let sources = rsm.sources.unwrap_or_default(); 159 | let range_mappings = rsm.range_mappings.unwrap_or_default(); 160 | let mappings = rsm.mappings.unwrap_or_default(); 161 | let allocation_size = mappings.matches(&[',', ';'][..]).count() + 10; 162 | let mut tokens = Vec::with_capacity(allocation_size); 163 | 164 | let mut nums = Vec::with_capacity(6); 165 | let mut rmi = BitVec::new(); 166 | 167 | for (dst_line, (line, rmi_str)) in mappings 168 | .split(';') 169 | .zip(range_mappings.split(';').chain(std::iter::repeat(""))) 170 | .enumerate() 171 | { 172 | if line.is_empty() { 173 | continue; 174 | } 175 | 176 | dst_col = 0; 177 | 178 | decode_rmi(rmi_str, &mut rmi)?; 179 | 180 | for (line_index, segment) in line.split(',').enumerate() { 181 | if segment.is_empty() { 182 | continue; 183 | } 184 | 185 | nums.clear(); 186 | parse_vlq_segment_into(segment, &mut nums)?; 187 | match nums.len() { 188 | 1 | 4 | 5 => {} 189 | _ => return Err(Error::BadSegmentSize(nums.len() as u32)), 190 | } 191 | 192 | dst_col = (i64::from(dst_col) + nums[0]) as u32; 193 | 194 | // The source file , source line, source column, and name 195 | // may not be present in the current token. We use `u32::MAX` 196 | // as the placeholder for missing values. 197 | let mut current_src_id = !0; 198 | let mut current_src_line = !0; 199 | let mut current_src_col = !0; 200 | let mut current_name_id = !0; 201 | 202 | if nums.len() > 1 { 203 | running_src_id = (i64::from(running_src_id) + nums[1]) as u32; 204 | 205 | if running_src_id >= sources.len() as u32 { 206 | return Err(Error::BadSourceReference(running_src_id)); 207 | } 208 | 209 | running_src_line = (i64::from(running_src_line) + nums[2]) as u32; 210 | running_src_col = (i64::from(running_src_col) + nums[3]) as u32; 211 | 212 | current_src_id = running_src_id; 213 | current_src_line = running_src_line; 214 | current_src_col = running_src_col; 215 | 216 | if nums.len() > 4 { 217 | running_name_id = (i64::from(running_name_id) + nums[4]) as u32; 218 | if running_name_id >= names.len() as u32 { 219 | return Err(Error::BadNameReference(running_name_id)); 220 | } 221 | current_name_id = running_name_id; 222 | } 223 | } 224 | 225 | let is_range = rmi.get(line_index).map(|v| *v).unwrap_or_default(); 226 | 227 | tokens.push(RawToken { 228 | dst_line: dst_line as u32, 229 | dst_col, 230 | src_line: current_src_line, 231 | src_col: current_src_col, 232 | src_id: current_src_id, 233 | name_id: current_name_id, 234 | is_range, 235 | }); 236 | } 237 | } 238 | 239 | let sources = sources 240 | .into_iter() 241 | .map(Option::unwrap_or_default) 242 | .map(Into::into) 243 | .collect(); 244 | 245 | // apparently we can encounter some non string types in real world 246 | // sourcemaps :( 247 | let names = names 248 | .into_iter() 249 | .map(|val| match val { 250 | Value::String(s) => s.into(), 251 | Value::Number(num) => num.to_string().into(), 252 | _ => "".into(), 253 | }) 254 | .collect::>(); 255 | 256 | // file sometimes is not a string for unexplicable reasons 257 | let file = rsm.file.map(|val| match val { 258 | Value::String(s) => s.into(), 259 | _ => "".into(), 260 | }); 261 | 262 | let source_content = rsm 263 | .sources_content 264 | .map(|x| x.into_iter().map(|v| v.map(Into::into)).collect::>()); 265 | 266 | let mut sm = SourceMap::new(file, tokens, names, sources, source_content); 267 | sm.set_source_root(rsm.source_root); 268 | sm.set_debug_id(rsm.debug_id.into()); 269 | if let Some(ignore_list) = rsm.ignore_list { 270 | for idx in ignore_list { 271 | sm.add_to_ignore_list(idx); 272 | } 273 | } 274 | 275 | Ok(sm) 276 | } 277 | 278 | fn decode_index(rsm: RawSourceMap) -> Result { 279 | let mut sections = vec![]; 280 | 281 | for mut raw_section in rsm.sections.unwrap_or_default() { 282 | sections.push(SourceMapSection::new( 283 | (raw_section.offset.line, raw_section.offset.column), 284 | raw_section.url, 285 | match raw_section.map.take() { 286 | Some(map) => Some(decode_common(*map)?), 287 | None => None, 288 | }, 289 | )); 290 | } 291 | 292 | sections.sort_by_key(SourceMapSection::get_offset); 293 | 294 | // file sometimes is not a string for unexplicable reasons 295 | let file = rsm.file.map(|val| match val { 296 | Value::String(s) => s, 297 | _ => "".into(), 298 | }); 299 | 300 | Ok(SourceMapIndex::new_ram_bundle_compatible( 301 | file, 302 | sections, 303 | rsm.x_facebook_offsets, 304 | rsm.x_metro_module_paths, 305 | ) 306 | .with_debug_id(rsm.debug_id.into())) 307 | } 308 | 309 | fn decode_common(rsm: RawSourceMap) -> Result { 310 | Ok(if rsm.sections.is_some() { 311 | DecodedMap::Index(decode_index(rsm)?) 312 | } else if rsm.x_facebook_sources.is_some() { 313 | DecodedMap::Hermes(decode_hermes(rsm)?) 314 | } else { 315 | DecodedMap::Regular(decode_regular(rsm)?) 316 | }) 317 | } 318 | 319 | /// Decodes a sourcemap or sourcemap index from a reader 320 | /// 321 | /// This supports both sourcemaps and sourcemap indexes unless the 322 | /// specialized methods on the individual types. 323 | pub fn decode(rdr: R) -> Result { 324 | let mut rdr = StripHeaderReader::new(rdr); 325 | let mut rdr = BufReader::new(&mut rdr); 326 | let rsm: RawSourceMap = serde_json::from_reader(&mut rdr)?; 327 | decode_common(rsm) 328 | } 329 | 330 | /// Decodes a sourcemap or sourcemap index from a byte slice 331 | /// 332 | /// This supports both sourcemaps and sourcemap indexes unless the 333 | /// specialized methods on the individual types. 334 | pub fn decode_slice(slice: &[u8]) -> Result { 335 | let content = strip_junk_header(slice)?; 336 | let rsm: RawSourceMap = serde_json::from_slice(content)?; 337 | decode_common(rsm) 338 | } 339 | 340 | /// Loads a sourcemap from a data URL. 341 | /// 342 | /// The URL should match the regex 343 | /// `data:application/json;(charset=utf-?8;)?base64,(?.+)`. 344 | pub fn decode_data_url(url: &str) -> Result { 345 | let rest = url 346 | .strip_prefix("data:application/json") 347 | .ok_or(Error::InvalidDataUrl)?; 348 | let rest = match rest.strip_prefix(";charset=") { 349 | Some(rest) => rest 350 | .strip_prefix("utf-8") 351 | .or_else(|| rest.strip_prefix("utf8")) 352 | .ok_or(Error::InvalidDataUrl)?, 353 | None => rest, 354 | }; 355 | let data_b64 = rest.strip_prefix(";base64,").ok_or(Error::InvalidDataUrl)?; 356 | let data = data_encoding::BASE64 357 | .decode(data_b64.as_bytes()) 358 | .map_err(|_| Error::InvalidDataUrl)?; 359 | decode_slice(&data[..]) 360 | } 361 | 362 | #[cfg(test)] 363 | mod tests { 364 | use super::*; 365 | use std::io::{self, BufRead}; 366 | 367 | #[test] 368 | fn test_strip_header() { 369 | let input: &[_] = b")]}garbage\r\n[1, 2, 3]"; 370 | let mut reader = io::BufReader::new(StripHeaderReader::new(input)); 371 | let mut text = String::new(); 372 | reader.read_line(&mut text).ok(); 373 | assert_eq!(text, "[1, 2, 3]"); 374 | } 375 | 376 | #[test] 377 | fn test_bad_newline() { 378 | let input: &[_] = b")]}'\r[1, 2, 3]"; 379 | let mut reader = io::BufReader::new(StripHeaderReader::new(input)); 380 | let mut text = String::new(); 381 | match reader.read_line(&mut text) { 382 | Err(err) => { 383 | assert_eq!(err.kind(), io::ErrorKind::InvalidData); 384 | } 385 | Ok(_) => { 386 | panic!("Expected failure"); 387 | } 388 | } 389 | } 390 | 391 | #[test] 392 | fn test_decode_rmi() { 393 | fn decode(rmi_str: &str) -> Vec { 394 | let mut out = bitvec::bitvec![u8, Lsb0; 0; 0]; 395 | decode_rmi(rmi_str, &mut out).expect("failed to decode"); 396 | 397 | let mut res = vec![]; 398 | for (idx, bit) in out.iter().enumerate() { 399 | if *bit { 400 | res.push(idx); 401 | } 402 | } 403 | res 404 | } 405 | 406 | // This is 0-based index of the bits 407 | assert_eq!(decode("AAB"), vec![12]); 408 | assert_eq!(decode("g"), vec![5]); 409 | assert_eq!(decode("Bg"), vec![0, 11]); 410 | } 411 | 412 | #[test] 413 | fn test_decode_sourcemap_index_no_debug_id() { 414 | let raw = RawSourceMap { 415 | version: Some(3), 416 | file: Some("test.js".into()), 417 | sources: None, 418 | source_root: None, 419 | sources_content: None, 420 | sections: Some(vec![]), 421 | names: None, 422 | range_mappings: None, 423 | mappings: None, 424 | ignore_list: None, 425 | x_facebook_offsets: None, 426 | x_metro_module_paths: None, 427 | x_facebook_sources: None, 428 | debug_id: None.into(), 429 | }; 430 | 431 | let decoded = decode_common(raw).expect("should decoded"); 432 | assert_eq!( 433 | decoded, 434 | DecodedMap::Index(SourceMapIndex::new(Some("test.js".into()), vec![])) 435 | ); 436 | } 437 | 438 | #[test] 439 | fn test_decode_sourcemap_index_debug_id() { 440 | const DEBUG_ID: &str = "0123456789abcdef0123456789abcdef"; 441 | 442 | let raw = RawSourceMap { 443 | version: Some(3), 444 | file: Some("test.js".into()), 445 | sources: None, 446 | source_root: None, 447 | sources_content: None, 448 | sections: Some(vec![]), 449 | names: None, 450 | range_mappings: None, 451 | mappings: None, 452 | ignore_list: None, 453 | x_facebook_offsets: None, 454 | x_metro_module_paths: None, 455 | x_facebook_sources: None, 456 | debug_id: Some(DEBUG_ID.parse().expect("valid debug id")).into(), 457 | }; 458 | 459 | let decoded = decode_common(raw).expect("should decode"); 460 | assert_eq!( 461 | decoded, 462 | DecodedMap::Index( 463 | SourceMapIndex::new(Some("test.js".into()), vec![]) 464 | .with_debug_id(Some(DEBUG_ID.parse().expect("valid debug id"))) 465 | ) 466 | ); 467 | } 468 | } 469 | -------------------------------------------------------------------------------- /tests/fixtures/ram_bundle/indexed_bundle_1/basic.jsbundle.map: -------------------------------------------------------------------------------- 1 | {"sections":[{"map":{"sections":[{"map":{"version":3,"sources":["__prelude__"],"sourcesContent":["var __BUNDLE_START_TIME__=this.nativePerformanceNow?nativePerformanceNow():Date.now(),__DEV__=false,process=this.process||{};process.env=process.env||{};process.env.NODE_ENV=process.env.NODE_ENV||\"production\";"],"names":[],"mappings":""},"offset":{"line":0,"column":0}},{"map":{"version":3,"sources":["/Users/anton/reps/tmp/ram-bundles-test/metro-test/node_modules/metro/src/lib/polyfills/require.js"],"sourcesContent":["/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * @polyfill\n *\n * @format\n */\n\"use strict\";\n/* eslint-disable no-bitwise */\n\nglobal.__r = metroRequire;\nglobal.__d = define;\nglobal.__c = clear;\nglobal.__registerSegment = registerSegment;\nvar modules = clear(); // Don't use a Symbol here, it would pull in an extra polyfill with all sorts of\n// additional stuff (e.g. Array.from).\n\nconst EMPTY = {};\nconst _ref = {},\n hasOwnProperty = _ref.hasOwnProperty;\n\nfunction clear() {\n modules = Object.create(null); // We return modules here so that we can assign an initial value to modules\n // when defining it. Otherwise, we would have to do \"let modules = null\",\n // which will force us to add \"nullthrows\" everywhere.\n\n return modules;\n}\n\nif (__DEV__) {\n var verboseNamesToModuleIds = Object.create(null);\n var initializingModuleIds = [];\n}\n\nfunction define(factory, moduleId, dependencyMap) {\n if (modules[moduleId] != null) {\n if (__DEV__) {\n // (We take `inverseDependencies` from `arguments` to avoid an unused\n // named parameter in `define` in production.\n const inverseDependencies = arguments[4]; // If the module has already been defined and the define method has been\n // called with inverseDependencies, we can hot reload it.\n\n if (inverseDependencies) {\n global.__accept(moduleId, factory, dependencyMap, inverseDependencies);\n }\n } // prevent repeated calls to `global.nativeRequire` to overwrite modules\n // that are already loaded\n\n return;\n }\n\n modules[moduleId] = {\n dependencyMap,\n factory,\n hasError: false,\n importedAll: EMPTY,\n importedDefault: EMPTY,\n isInitialized: false,\n publicModule: {\n exports: {}\n }\n };\n\n if (__DEV__) {\n // HMR\n modules[moduleId].hot = createHotReloadingObject(); // DEBUGGABLE MODULES NAMES\n // we take `verboseName` from `arguments` to avoid an unused named parameter\n // in `define` in production.\n\n const verboseName = arguments[3];\n\n if (verboseName) {\n modules[moduleId].verboseName = verboseName;\n verboseNamesToModuleIds[verboseName] = moduleId;\n }\n }\n}\n\nfunction metroRequire(moduleId) {\n if (__DEV__ && typeof moduleId === \"string\") {\n const verboseName = moduleId;\n moduleId = verboseNamesToModuleIds[verboseName];\n\n if (moduleId == null) {\n throw new Error(`Unknown named module: \"${verboseName}\"`);\n } else {\n console.warn(\n `Requiring module \"${verboseName}\" by name is only supported for ` +\n \"debugging purposes and will BREAK IN PRODUCTION!\"\n );\n }\n } //$FlowFixMe: at this point we know that moduleId is a number\n\n const moduleIdReallyIsNumber = moduleId;\n\n if (__DEV__) {\n const initializingIndex = initializingModuleIds.indexOf(\n moduleIdReallyIsNumber\n );\n\n if (initializingIndex !== -1) {\n const cycle = initializingModuleIds\n .slice(initializingIndex)\n .map(id => modules[id].verboseName); // We want to show A -> B -> A:\n\n cycle.push(cycle[0]);\n console.warn(\n `Require cycle: ${cycle.join(\" -> \")}\\n\\n` +\n \"Require cycles are allowed, but can result in uninitialized values. \" +\n \"Consider refactoring to remove the need for a cycle.\"\n );\n }\n }\n\n const module = modules[moduleIdReallyIsNumber];\n return module && module.isInitialized\n ? module.publicModule.exports\n : guardedLoadModule(moduleIdReallyIsNumber, module);\n}\n\nfunction metroImportDefault(moduleId) {\n if (__DEV__ && typeof moduleId === \"string\") {\n const verboseName = moduleId;\n moduleId = verboseNamesToModuleIds[verboseName];\n } //$FlowFixMe: at this point we know that moduleId is a number\n\n const moduleIdReallyIsNumber = moduleId;\n\n if (\n modules[moduleIdReallyIsNumber] &&\n modules[moduleIdReallyIsNumber].importedDefault !== EMPTY\n ) {\n return modules[moduleIdReallyIsNumber].importedDefault;\n }\n\n const exports = metroRequire(moduleIdReallyIsNumber);\n const importedDefault =\n exports && exports.__esModule ? exports.default : exports;\n return (modules[moduleIdReallyIsNumber].importedDefault = importedDefault);\n}\n\nmetroRequire.importDefault = metroImportDefault;\n\nfunction metroImportAll(moduleId) {\n if (__DEV__ && typeof moduleId === \"string\") {\n const verboseName = moduleId;\n moduleId = verboseNamesToModuleIds[verboseName];\n } //$FlowFixMe: at this point we know that moduleId is a number\n\n const moduleIdReallyIsNumber = moduleId;\n\n if (\n modules[moduleIdReallyIsNumber] &&\n modules[moduleIdReallyIsNumber].importedAll !== EMPTY\n ) {\n return modules[moduleIdReallyIsNumber].importedAll;\n }\n\n const exports = metroRequire(moduleIdReallyIsNumber);\n let importedAll;\n\n if (exports && exports.__esModule) {\n importedAll = exports;\n } else {\n importedAll = {}; // Refrain from using Object.assign, it has to work in ES3 environments.\n\n if (exports) {\n for (const key in exports) {\n if (hasOwnProperty.call(exports, key)) {\n importedAll[key] = exports[key];\n }\n }\n }\n\n importedAll.default = exports;\n }\n\n return (modules[moduleIdReallyIsNumber].importedAll = importedAll);\n}\n\nmetroRequire.importAll = metroImportAll;\nlet inGuard = false;\n\nfunction guardedLoadModule(moduleId, module) {\n if (!inGuard && global.ErrorUtils) {\n inGuard = true;\n let returnValue;\n\n try {\n returnValue = loadModuleImplementation(moduleId, module);\n } catch (e) {\n global.ErrorUtils.reportFatalError(e);\n }\n\n inGuard = false;\n return returnValue;\n } else {\n return loadModuleImplementation(moduleId, module);\n }\n}\n\nconst ID_MASK_SHIFT = 16;\nconst LOCAL_ID_MASK = ~0 >>> ID_MASK_SHIFT;\n\nfunction unpackModuleId(moduleId) {\n const segmentId = moduleId >>> ID_MASK_SHIFT;\n const localId = moduleId & LOCAL_ID_MASK;\n return {\n segmentId,\n localId\n };\n}\n\nmetroRequire.unpackModuleId = unpackModuleId;\n\nfunction packModuleId(value) {\n return (value.segmentId << ID_MASK_SHIFT) + value.localId;\n}\n\nmetroRequire.packModuleId = packModuleId;\nconst hooks = [];\n\nfunction registerHook(cb) {\n const hook = {\n cb\n };\n hooks.push(hook);\n return {\n release: () => {\n for (let i = 0; i < hooks.length; ++i) {\n if (hooks[i] === hook) {\n hooks.splice(i, 1);\n break;\n }\n }\n }\n };\n}\n\nmetroRequire.registerHook = registerHook;\nconst moduleDefinersBySegmentID = [];\n\nfunction registerSegment(segmentID, moduleDefiner) {\n moduleDefinersBySegmentID[segmentID] = moduleDefiner;\n}\n\nfunction loadModuleImplementation(moduleId, module) {\n if (!module && moduleDefinersBySegmentID.length > 0) {\n const _unpackModuleId = unpackModuleId(moduleId),\n segmentId = _unpackModuleId.segmentId,\n localId = _unpackModuleId.localId;\n\n const definer = moduleDefinersBySegmentID[segmentId];\n\n if (definer != null) {\n definer(localId);\n module = modules[moduleId];\n }\n }\n\n const nativeRequire = global.nativeRequire;\n\n if (!module && nativeRequire) {\n const _unpackModuleId2 = unpackModuleId(moduleId),\n segmentId = _unpackModuleId2.segmentId,\n localId = _unpackModuleId2.localId;\n\n nativeRequire(localId, segmentId);\n module = modules[moduleId];\n }\n\n if (!module) {\n throw unknownModuleError(moduleId);\n }\n\n if (module.hasError) {\n throw moduleThrewError(moduleId, module.error);\n } // `metroRequire` calls into the require polyfill itself are not analyzed and\n // replaced so that they use numeric module IDs.\n // The systrace module will expose itself on the metroRequire function so that\n // it can be used here.\n // TODO(davidaurelio) Scan polyfills for dependencies, too (t9759686)\n\n if (__DEV__) {\n var Systrace = metroRequire.Systrace;\n } // We must optimistically mark module as initialized before running the\n // factory to keep any require cycles inside the factory from causing an\n // infinite require loop.\n\n module.isInitialized = true;\n const _module = module,\n factory = _module.factory,\n dependencyMap = _module.dependencyMap;\n\n if (__DEV__) {\n initializingModuleIds.push(moduleId);\n }\n\n try {\n if (__DEV__) {\n // $FlowFixMe: we know that __DEV__ is const and `Systrace` exists\n Systrace.beginEvent(\"JS_require_\" + (module.verboseName || moduleId));\n }\n\n const moduleObject = module.publicModule;\n\n if (__DEV__) {\n if (module.hot) {\n moduleObject.hot = module.hot;\n }\n }\n\n moduleObject.id = moduleId;\n\n if (hooks.length > 0) {\n for (let i = 0; i < hooks.length; ++i) {\n hooks[i].cb(moduleId, moduleObject);\n }\n } // keep args in sync with with defineModuleCode in\n // metro/src/Resolver/index.js\n // and metro/src/ModuleGraph/worker.js\n\n factory(\n global,\n metroRequire,\n metroImportDefault,\n metroImportAll,\n moduleObject,\n moduleObject.exports,\n dependencyMap\n ); // avoid removing factory in DEV mode as it breaks HMR\n\n if (!__DEV__) {\n // $FlowFixMe: This is only sound because we never access `factory` again\n module.factory = undefined;\n module.dependencyMap = undefined;\n }\n\n if (__DEV__) {\n // $FlowFixMe: we know that __DEV__ is const and `Systrace` exists\n Systrace.endEvent();\n }\n\n return moduleObject.exports;\n } catch (e) {\n module.hasError = true;\n module.error = e;\n module.isInitialized = false;\n module.publicModule.exports = undefined;\n throw e;\n } finally {\n if (__DEV__) {\n if (initializingModuleIds.pop() !== moduleId) {\n throw new Error(\n \"initializingModuleIds is corrupt; something is terribly wrong\"\n );\n }\n }\n }\n}\n\nfunction unknownModuleError(id) {\n let message = 'Requiring unknown module \"' + id + '\".';\n\n if (__DEV__) {\n message +=\n \"If you are sure the module is there, try restarting Metro Bundler. \" +\n \"You may also want to run `yarn`, or `npm install` (depending on your environment).\";\n }\n\n return Error(message);\n}\n\nfunction moduleThrewError(id, error) {\n const displayName = (__DEV__ && modules[id] && modules[id].verboseName) || id;\n return Error(\n 'Requiring module \"' + displayName + '\", which threw an exception: ' + error\n );\n}\n\nif (__DEV__) {\n metroRequire.Systrace = {\n beginEvent: () => {},\n endEvent: () => {}\n };\n\n metroRequire.getModules = () => {\n return modules;\n }; // HOT MODULE RELOADING\n\n var createHotReloadingObject = function() {\n const hot = {\n acceptCallback: null,\n accept: callback => {\n hot.acceptCallback = callback;\n },\n disposeCallback: null,\n dispose: callback => {\n hot.disposeCallback = callback;\n }\n };\n return hot;\n };\n\n const metroAcceptAll = function(\n dependentModules,\n inverseDependencies,\n patchedModules\n ) {\n if (!dependentModules || dependentModules.length === 0) {\n return true;\n }\n\n const notAccepted = dependentModules.filter(\n module =>\n !metroAccept(\n module,\n /*factory*/\n undefined,\n /*dependencyMap*/\n undefined,\n inverseDependencies,\n patchedModules\n )\n );\n const parents = [];\n\n for (let i = 0; i < notAccepted.length; i++) {\n // if the module has no parents then the change cannot be hot loaded\n if (inverseDependencies[notAccepted[i]].length === 0) {\n return false;\n }\n\n parents.push.apply(parents, inverseDependencies[notAccepted[i]]);\n }\n\n return parents.length == 0;\n };\n\n const metroAccept = function(\n id,\n factory,\n dependencyMap,\n inverseDependencies\n ) {\n let patchedModules =\n arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};\n\n if (id in patchedModules) {\n // Do not patch the same module more that once during an update.\n return true;\n }\n\n patchedModules[id] = true;\n const mod = modules[id];\n\n if (!mod && factory) {\n // New modules are going to be handled by the define() method.\n return true;\n }\n\n const hot = mod.hot;\n\n if (!hot) {\n console.warn(\n \"Cannot accept module because Hot Module Replacement \" +\n \"API was not installed.\"\n );\n return false;\n }\n\n if (hot.disposeCallback) {\n try {\n hot.disposeCallback();\n } catch (error) {\n console.error(\n `Error while calling dispose handler for module ${id}: `,\n error\n );\n }\n } // replace and initialize factory\n\n if (factory) {\n mod.factory = factory;\n }\n\n if (dependencyMap) {\n mod.dependencyMap = dependencyMap;\n }\n\n mod.hasError = false;\n mod.isInitialized = false;\n metroRequire(id);\n\n if (hot.acceptCallback) {\n try {\n hot.acceptCallback();\n return true;\n } catch (error) {\n console.error(\n `Error while calling accept handler for module ${id}: `,\n error\n );\n }\n } // need to have inverseDependencies to bubble up accept\n\n if (!inverseDependencies) {\n throw new Error(\"Undefined `inverseDependencies`\");\n } // accept parent modules recursively up until all siblings are accepted\n\n return metroAcceptAll(\n inverseDependencies[id],\n inverseDependencies,\n patchedModules\n );\n };\n\n global.__accept = metroAccept;\n}\n"],"names":["global","__r","metroRequire","__d","factory","moduleId","dependencyMap","modules","hasError","importedAll","EMPTY","importedDefault","isInitialized","publicModule","exports","__c","clear","__registerSegment","segmentID","moduleDefiner","moduleDefinersBySegmentID","hasOwnProperty","Object","create","moduleIdReallyIsNumber","module","guardedLoadModule","metroImportDefault","__esModule","default","metroImportAll","key","call","importDefault","importAll","inGuard","ErrorUtils","returnValue","loadModuleImplementation","e","reportFatalError","ID_MASK_SHIFT","LOCAL_ID_MASK","unpackModuleId","segmentId","localId","packModuleId","value","hooks","registerHook","cb","hook","push","release","i","length","splice","_unpackModuleId","definer","nativeRequire","_unpackModuleId2","unknownModuleError","moduleThrewError","error","_module","moduleObject","id","undefined","Error"],"mappings":"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"},"offset":{"line":1,"column":0}},{"map":{"version":3,"sources":["require-/Users/anton/reps/tmp/ram-bundles-test/metro-test/src/index.js"],"sourcesContent":[""],"names":[],"mappings":""},"offset":{"line":2,"column":0}},{"map":{"version":3,"sources":["source-map"],"sourcesContent":[""],"names":[],"mappings":""},"offset":{"line":3,"column":0}}],"version":3},"offset":{"line":0,"column":123}},{"map":{"version":3,"sources":["/Users/anton/reps/tmp/ram-bundles-test/metro-test/src/index.js"],"sourcesContent":["const f = require(\"./other\");\nconst isWindows = require(\"is-windows\"); // 3rd party package\n\nconsole.log(\"inside index.js\");\nconsole.log(f(\"world\"));\nconsole.log(\"Is Windows OS: \" + isWindows());\n"],"names":["f","require","d","isWindows","console","log"],"mappings":"yCAAA,MAAMA,EAAIC,EAAOC,EAAA,IACXC,EAAYF,EAAOC,EAAA,IAEzBE,QAAQC,IAAI,mBACZD,QAAQC,IAAIL,EAAE,UACdI,QAAQC,IAAI,kBAAoBF"},"offset":{"line":4,"column":28}},{"map":{"version":3,"sources":["/Users/anton/reps/tmp/ram-bundles-test/metro-test/src/other.js"],"sourcesContent":["console.log('inside other.js');\nmodule.exports = s => \"hello, \" + s;\n"],"names":["console","log","module","exports","s"],"mappings":"yCAAAA,QAAQC,IAAI,mBACZC,EAAOC,QAAUC,CAAAA,GAAK,UAAYA"},"offset":{"line":5,"column":28}},{"map":{"version":3,"sources":["/Users/anton/reps/tmp/ram-bundles-test/metro-test/node_modules/is-windows/index.js"],"sourcesContent":["/*!\n * is-windows \n *\n * Copyright © 2015-2018, Jon Schlinkert.\n * Released under the MIT License.\n */\n\n(function(factory) {\n if (exports && typeof exports === 'object' && typeof module !== 'undefined') {\n module.exports = factory();\n } else if (typeof define === 'function' && define.amd) {\n define([], factory);\n } else if (typeof window !== 'undefined') {\n window.isWindows = factory();\n } else if (typeof global !== 'undefined') {\n global.isWindows = factory();\n } else if (typeof self !== 'undefined') {\n self.isWindows = factory();\n } else {\n this.isWindows = factory();\n }\n})(function() {\n 'use strict';\n return function isWindows() {\n return process && (process.platform === 'win32' || /^(msys|cygwin)$/.test(process.env.OSTYPE));\n };\n});\n"],"names":["factory","exports","module","define","amd","window","isWindows","global","self","this","process","platform","test","env","OSTYPE"],"mappings":"2CAOA,SAAUA,GACJC,GAA8B,iBAAZA,QAA0C,IAAXC,EACnDA,EAAOD,QAAUD,IACU,mBAAXG,QAAyBA,OAAOC,IAChDD,UAAWH,GACgB,oBAAXK,OAChBA,OAAOC,UAAYN,SACQ,IAAXO,EAChBA,EAAOD,UAAYN,IACM,oBAATQ,KAChBA,KAAKF,UAAYN,IAEjBS,KAAKH,UAAYN,KAZrB,CAcG,WAED,OAAO,WACL,OAAOU,UAAiC,UAArBA,QAAQC,UAAwB,kBAAkBC,KAAKF,QAAQG,IAAIC"},"offset":{"line":6,"column":28}}],"version":3,"x_facebook_offsets":[4,null,null,5,6],"x_metro_module_paths":["/Users/anton/reps/tmp/ram-bundles-test/metro-test/src/index.js","__prelude__","/Users/anton/reps/tmp/ram-bundles-test/metro-test/node_modules/metro/src/lib/polyfills/require.js","/Users/anton/reps/tmp/ram-bundles-test/metro-test/src/other.js","/Users/anton/reps/tmp/ram-bundles-test/metro-test/node_modules/is-windows/index.js","require-/Users/anton/reps/tmp/ram-bundles-test/metro-test/src/index.js","source-map"]} --------------------------------------------------------------------------------