├── .eslintrc.js ├── .gitattributes ├── .github ├── codecov.yml └── workflows │ ├── cargo.yml │ └── nodejs.yml ├── .gitignore ├── .gitmodules ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bench ├── .gitignore ├── README.md ├── angular-min-source-map.js ├── bench-dom-bindings.js ├── bench.html ├── bench.js ├── function-size.r ├── package.json ├── plot.r ├── scalajs-runtime-sourcemap.js ├── section-size.r ├── self-source-map.js ├── size.r ├── stats.js └── webpack.config.js ├── lib ├── array-set.js ├── base64-vlq.js ├── base64.js ├── binary-search.js ├── mapping-list.js ├── mappings.wasm ├── read-wasm-browser.js ├── read-wasm.js ├── source-map-consumer.js ├── source-map-generator.js ├── source-node.js ├── url-browser.js ├── url.js ├── util.js └── wasm.js ├── package-lock.json ├── package.json ├── source-map.d.ts ├── source-map.js ├── test ├── run-tests.js ├── test-api.js ├── test-array-set.js ├── test-base64-vlq.js ├── test-base64.js ├── test-binary-search.js ├── test-dog-fooding.js ├── test-nested-consumer-usage.js ├── test-source-map-consumer.js ├── test-source-map-generator.js ├── test-source-node.js ├── test-spec-tests.js ├── test-util.js └── util.js └── wasm-mappings ├── .gitignore ├── CONTRIBUTING.md ├── Cargo.toml ├── README.md ├── benches ├── bench.rs └── part-of-scala-js-source-map ├── ci └── script.sh ├── source-map-mappings-wasm-api ├── .gitignore ├── Cargo.toml ├── build.py ├── src │ └── lib.rs └── who-calls.py ├── src ├── comparators.rs └── lib.rs └── tests ├── quickcheck.rs └── tests.rs /.eslintrc.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | env: { 5 | node: true, 6 | es6: true, 7 | }, 8 | 9 | extends: ["prettier"], 10 | 11 | parserOptions: { 12 | ecmaVersion: 9, 13 | }, 14 | 15 | globals: { 16 | fetch: false, 17 | WebAssembly: false, 18 | }, 19 | 20 | rules: { 21 | // Always require spacing around a single line block 22 | "block-spacing": "error", 23 | 24 | // Warn about cyclomatic complexity in functions. 25 | // XXX Get this down to 20? 26 | complexity: ["error", 25], 27 | 28 | // Functions must always return something or nothing 29 | "consistent-return": "error", 30 | 31 | // Require braces around blocks that start a new line 32 | curly: ["error", "multi-line"], 33 | 34 | // Encourage the use of dot notation whenever possible. 35 | "dot-notation": "error", 36 | 37 | // Always require a trailing EOL 38 | "eol-last": "error", 39 | 40 | // Unix linebreaks 41 | "linebreak-style": ["error", "unix"], 42 | 43 | // Don't enforce the maximum depth that blocks can be nested. The complexity 44 | // rule is a better rule to check this. 45 | "max-depth": "off", 46 | 47 | // Maximum depth callbacks can be nested. 48 | "max-nested-callbacks": ["error", 10], 49 | 50 | // Always require parenthesis for new calls 51 | "new-parens": "error", 52 | 53 | // Use [] instead of Array() 54 | "no-array-constructor": "error", 55 | 56 | // Disallow use of arguments.caller or arguments.callee. 57 | "no-caller": "error", 58 | 59 | // Disallow modifying variables of class declarations. 60 | "no-class-assign": "error", 61 | 62 | // Disallow assignment operators in conditional statements 63 | "no-cond-assign": "error", 64 | 65 | // Disallow modifying variables that are declared using const. 66 | "no-const-assign": "error", 67 | 68 | // Disallow control characters in regular expressions. 69 | "no-control-regex": "error", 70 | 71 | // Disallow the use of debugger 72 | "no-debugger": "error", 73 | 74 | // Disallow deleting variables 75 | "no-delete-var": "error", 76 | 77 | // No duplicate arguments in function declarations 78 | "no-dupe-args": "error", 79 | 80 | // Disallow duplicate class members. 81 | "no-dupe-class-members": "error", 82 | 83 | // No duplicate keys in object declarations 84 | "no-dupe-keys": "error", 85 | 86 | // No duplicate cases in switch statements 87 | "no-duplicate-case": "error", 88 | 89 | // If an if block ends with a return no need for an else block 90 | "no-else-return": "error", 91 | 92 | // No empty statements 93 | "no-empty": ["error", { allowEmptyCatch: true }], 94 | 95 | // No empty character classes in regex 96 | "no-empty-character-class": "error", 97 | 98 | // Disallow empty destructuring 99 | "no-empty-pattern": "error", 100 | 101 | // Disallow eval and setInteral/setTimeout with strings 102 | "no-eval": "error", 103 | 104 | // No assigning to exception variable 105 | "no-ex-assign": "error", 106 | 107 | // Disallow unnecessary calls to .bind() 108 | "no-extra-bind": "error", 109 | 110 | // No using !! where casting to boolean is already happening 111 | "no-extra-boolean-cast": "error", 112 | 113 | // No overwriting defined functions 114 | "no-func-assign": "error", 115 | 116 | // Disallow eval and setInteral/setTimeout with strings 117 | "no-implied-eval": "error", 118 | 119 | // No invalid regular expressions 120 | "no-invalid-regexp": "error", 121 | 122 | // No odd whitespace characters 123 | "no-irregular-whitespace": "error", 124 | 125 | // Disallow the use of the __iterator__ property 126 | "no-iterator": "error", 127 | 128 | // No labels 129 | "no-labels": "error", 130 | 131 | // Disallow unnecessary nested blocks 132 | "no-lone-blocks": "error", 133 | 134 | // No single if block inside an else block 135 | "no-lonely-if": "error", 136 | 137 | // No unnecessary spacing 138 | "no-multi-spaces": [ 139 | "error", 140 | { 141 | exceptions: { 142 | ArrayExpression: true, 143 | AssignmentExpression: true, 144 | ObjectExpression: true, 145 | VariableDeclarator: true, 146 | }, 147 | }, 148 | ], 149 | 150 | // No reassigning native JS objects 151 | "no-native-reassign": "error", 152 | 153 | // Nested ternary statements are confusing 154 | "no-nested-ternary": "error", 155 | 156 | // Use {} instead of new Object() 157 | "no-new-object": "error", 158 | 159 | // Dissallow use of new wrappers 160 | "no-new-wrappers": "error", 161 | 162 | // No Math() or JSON() 163 | "no-obj-calls": "error", 164 | 165 | // No octal literals 166 | "no-octal": "error", 167 | 168 | // No redeclaring variables 169 | "no-redeclare": "error", 170 | 171 | // Disallow multiple spaces in regular expressions 172 | "no-regex-spaces": "error", 173 | 174 | // Disallows unnecessary `return await ...`. 175 | "no-return-await": "error", 176 | 177 | // Disallow assignments where both sides are exactly the same 178 | "no-self-assign": "error", 179 | 180 | // No unnecessary comparisons 181 | "no-self-compare": "error", 182 | 183 | // No declaring variables from an outer scope 184 | "no-shadow": "error", 185 | 186 | // No declaring variables that hide things like arguments 187 | "no-shadow-restricted-names": "error", 188 | 189 | // Disallow sparse arrays 190 | "no-sparse-arrays": "error", 191 | 192 | // Disallow tabs. 193 | "no-tabs": "error", 194 | 195 | // No using undeclared variables 196 | "no-undef": "error", 197 | 198 | // Error on newline where a semicolon is needed 199 | "no-unexpected-multiline": "error", 200 | 201 | // Disallow the use of Boolean literals in conditional expressions. 202 | "no-unneeded-ternary": "error", 203 | 204 | // No unreachable statements 205 | "no-unreachable": "error", 206 | 207 | // Disallow control flow statements in finally blocks 208 | "no-unsafe-finally": "error", 209 | 210 | // No (!foo in bar) or (!object instanceof Class) 211 | "no-unsafe-negation": "error", 212 | 213 | // No declaring variables that are never used 214 | "no-unused-vars": [ 215 | "error", 216 | { 217 | args: "none", 218 | vars: "local", 219 | }, 220 | ], 221 | 222 | // No using variables before defined 223 | "no-use-before-define": ["error", "nofunc"], 224 | 225 | // Disallow unnecessary .call() and .apply() 226 | "no-useless-call": "error", 227 | 228 | // Don't concatenate string literals together (unless they span multiple 229 | // lines) 230 | "no-useless-concat": "error", 231 | 232 | // Disallow redundant return statements 233 | "no-useless-return": "error", 234 | 235 | // Use const or let instead of var 236 | "no-var": "error", 237 | 238 | // No using with 239 | "no-with": "error", 240 | 241 | // Require object-literal shorthand with ES6 method syntax 242 | "object-shorthand": ["error", "always", { avoidQuotes: true }], 243 | 244 | // Use const instead of let where possible 245 | "prefer-const": "error", 246 | 247 | // Require space before blocks 248 | "space-before-blocks": "error", 249 | 250 | // Requires or disallows a whitespace (space or tab) beginning a comment 251 | "spaced-comment": "error", 252 | 253 | // No comparisons to NaN 254 | "use-isnan": "error", 255 | 256 | // Only check typeof against valid results 257 | "valid-typeof": "error", 258 | 259 | "max-len": ["error", { code: 120, ignoreUrls: true }], 260 | }, 261 | }; 262 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | bench/scalajs-runtime-sourcemap.js binary 2 | wasm-mappings/benches/part-of-scala-js-source-map -diff -merge 3 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | comment: 2 | require_changes: true 3 | 4 | coverage: 5 | status: 6 | project: 7 | default: 8 | informational: true 9 | patch: 10 | default: 11 | informational: true 12 | -------------------------------------------------------------------------------- /.github/workflows/cargo.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | branches: [master] 4 | paths: 5 | - wasm-mappings/** 6 | - .github/workflows/cargo.yml 7 | push: 8 | branches: [master] 9 | workflow_dispatch: 10 | 11 | name: WASM tests 12 | 13 | defaults: 14 | run: 15 | working-directory: ./wasm-mappings 16 | 17 | jobs: 18 | build_and_test: 19 | name: WASM mappings 20 | runs-on: ubuntu-latest 21 | strategy: 22 | matrix: 23 | toolchain: [stable, beta, nightly] 24 | job: [test, bench, wasm] 25 | exclude: 26 | - toolchain: stable 27 | job: bench 28 | - toolchain: stable 29 | job: wasm 30 | - toolchain: beta 31 | job: bench 32 | - toolchain: beta 33 | job: wasm 34 | steps: 35 | - uses: actions/checkout@v2 36 | - uses: actions-rs/toolchain@v1 37 | with: 38 | toolchain: ${{ matrix.toolchain }} 39 | override: true 40 | default: true 41 | - run: JOB=${{ matrix.job }} ./ci/script.sh 42 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node.js 2 | 3 | on: 4 | pull_request: 5 | branches: [master] 6 | push: 7 | branches: [master] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [12, 14, 16, 18] 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | - uses: actions/setup-node@v3 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | - run: npm ci 24 | - run: npm test 25 | 26 | lint: 27 | runs-on: ubuntu-latest 28 | 29 | steps: 30 | - uses: actions/checkout@v3 31 | - uses: actions/setup-node@v3 32 | - run: npm ci 33 | - run: npm run lint 34 | - run: npm run toc 35 | - run: git diff --stat --exit-code 36 | - run: npx prettier --check . 37 | - run: npm run coverage 38 | - run: npx c8 report --reporter=lcov 39 | - uses: codecov/codecov-action@v3 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.log 3 | .idea 4 | .DS_Store 5 | node_modules/* 6 | build/ 7 | bench/*.svg 8 | bench/dist/ 9 | bench/node_modules 10 | coverage/ 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test/source-map-tests"] 2 | path = test/source-map-tests 3 | url = https://github.com/tc39/source-map-tests.git 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | bench/ 2 | coverage/ 3 | node_modules/ 4 | target/ 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "endOfLine": "lf", 4 | "printWidth": 80, 5 | "tabWidth": 2, 6 | "trailingComma": "es5" 7 | } 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 0.8.0-beta.0 4 | 5 | ### Breaking changes 6 | 7 | - [#350](https://github.com/mozilla/source-map/pull/350) - 8 | Change browser detection logic for WASM loading. 9 | - [#363](https://github.com/mozilla/source-map/pull/363) - 10 | Change WASM loading detection to rely on `package.json#browser` field. 11 | - [#362](https://github.com/mozilla/source-map/pull/362) - 12 | Remove the `dist/` bundle. 13 | - [#371](https://github.com/mozilla/source-map/pull/371) - 14 | Reimplement sourcemap URL processing using the WHATWG URL API. 15 | 16 | ### Nonbreaking changes: 17 | 18 | - [#339](https://github.com/mozilla/source-map/pull/339) - 19 | Allow initializing the consumer `mappings.wasm` file as an `ArrayBuffer`. 20 | 21 | ### Internal Improvements: 22 | 23 | - [#347](https://github.com/mozilla/source-map/pull/347) - 24 | Improve tests. 25 | - [#352](https://github.com/mozilla/source-map/pull/352) - 26 | Improve documentation. 27 | - [#361](https://github.com/mozilla/source-map/pull/361) - 28 | Use newer Webpack CLI when bundling. 29 | - [#364](https://github.com/mozilla/source-map/pull/364) - 30 | Convert `IndexedSourceMapConsumer` implementation to pass more through 31 | to `BasicSourceMapConsumer`. 32 | - [#366](https://github.com/mozilla/source-map/pull/366) - 33 | Normalize internal URL representation to be easier to follow. 34 | - [#341](https://github.com/mozilla/source-map/pull/341) - 35 | Use async functions to simplify `SourceMapConsumer.with` implementation. 36 | 37 | ## 0.7.3 38 | 39 | - Fix a bug where nested uses of `SourceMapConsumer` could result in a 40 | `TypeError`. [#338](https://github.com/mozilla/source-map/issues/338) 41 | [#330](https://github.com/mozilla/source-map/issues/330) 42 | [#319](https://github.com/mozilla/source-map/issues/319) 43 | 44 | ## 0.7.2 45 | 46 | - Another 3x speed up in `SourceMapConsumer`. Read about it here: 47 | http://fitzgeraldnick.com/2018/02/26/speed-without-wizardry.html 48 | 49 | ## 0.7.1 50 | 51 | - Updated TypeScript typings. [#321][] 52 | 53 | [#321]: https://github.com/mozilla/source-map/pull/321 54 | 55 | ## 0.7.0 56 | 57 | - `SourceMapConsumer` now uses WebAssembly, and is **much** faster! Read about 58 | it here: 59 | https://hacks.mozilla.org/2018/01/oxidizing-source-maps-with-rust-and-webassembly/ 60 | 61 | - **Breaking change:** `new SourceMapConsumer` now returns a `Promise` object 62 | that resolves to the newly constructed `SourceMapConsumer` instance, rather 63 | than returning the new instance immediately. 64 | 65 | - **Breaking change:** when you're done using a `SourceMapConsumer` instance, 66 | you must call `SourceMapConsumer.prototype.destroy` on it. After calling 67 | `destroy`, you must not use the instance again. 68 | 69 | - **Breaking change:** `SourceMapConsumer` used to be able to handle lines, 70 | columns numbers and source and name indices up to `2^53 - 1` (aka 71 | `Number.MAX_SAFE_INTEGER`). It can now only handle them up to `2^32 - 1`. 72 | 73 | - **Breaking change:** The `source-map` library now uses modern ECMAScript-isms: 74 | `let`, arrow functions, `async`, etc. Use Babel to compile it down to 75 | ECMAScript 5 if you need to support older JavaScript environments. 76 | 77 | - **Breaking change:** Drop support for Node < 8. If you want to support older 78 | versions of node, please use v0.6 or below. 79 | 80 | ## 0.5.6 81 | 82 | - Fix for regression when people were using numbers as names in source maps. See 83 | #236. 84 | 85 | ## 0.5.5 86 | 87 | - Fix "regression" of unsupported, implementation behavior that half the world 88 | happens to have come to depend on. See #235. 89 | 90 | - Fix regression involving function hoisting in SpiderMonkey. See #233. 91 | 92 | ## 0.5.4 93 | 94 | - Large performance improvements to source-map serialization. See #228 and #229. 95 | 96 | ## 0.5.3 97 | 98 | - Do not include unnecessary distribution files. See 99 | commit ef7006f8d1647e0a83fdc60f04f5a7ca54886f86. 100 | 101 | ## 0.5.2 102 | 103 | - Include browser distributions of the library in package.json's `files`. See 104 | issue #212. 105 | 106 | ## 0.5.1 107 | 108 | - Fix latent bugs in IndexedSourceMapConsumer.prototype.\_parseMappings. See 109 | ff05274becc9e6e1295ed60f3ea090d31d843379. 110 | 111 | ## 0.5.0 112 | 113 | - Node 0.8 is no longer supported. 114 | 115 | - Use webpack instead of dryice for bundling. 116 | 117 | - Big speedups serializing source maps. See pull request #203. 118 | 119 | - Fix a bug with `SourceMapConsumer.prototype.sourceContentFor` and sources that 120 | explicitly start with the source root. See issue #199. 121 | 122 | ## 0.4.4 123 | 124 | - Fix an issue where using a `SourceMapGenerator` after having created a 125 | `SourceMapConsumer` from it via `SourceMapConsumer.fromSourceMap` failed. See 126 | issue #191. 127 | 128 | - Fix an issue with where `SourceMapGenerator` would mistakenly consider 129 | different mappings as duplicates of each other and avoid generating them. See 130 | issue #192. 131 | 132 | ## 0.4.3 133 | 134 | - A very large number of performance improvements, particularly when parsing 135 | source maps. Collectively about 75% of time shaved off of the source map 136 | parsing benchmark! 137 | 138 | - Fix a bug in `SourceMapConsumer.prototype.allGeneratedPositionsFor` and fuzzy 139 | searching in the presence of a column option. See issue #177. 140 | 141 | - Fix a bug with joining a source and its source root when the source is above 142 | the root. See issue #182. 143 | 144 | - Add the `SourceMapConsumer.prototype.hasContentsOfAllSources` method to 145 | determine when all sources' contents are inlined into the source map. See 146 | issue #190. 147 | 148 | ## 0.4.2 149 | 150 | - Add an `.npmignore` file so that the benchmarks aren't pulled down by 151 | dependent projects. Issue #169. 152 | 153 | - Add an optional `column` argument to 154 | `SourceMapConsumer.prototype.allGeneratedPositionsFor` and better handle lines 155 | with no mappings. Issues #172 and #173. 156 | 157 | ## 0.4.1 158 | 159 | - Fix accidentally defining a global variable. #170. 160 | 161 | ## 0.4.0 162 | 163 | - The default direction for fuzzy searching was changed back to its original 164 | direction. See #164. 165 | 166 | - There is now a `bias` option you can supply to `SourceMapConsumer` to control 167 | the fuzzy searching direction. See #167. 168 | 169 | - About an 8% speed up in parsing source maps. See #159. 170 | 171 | - Added a benchmark for parsing and generating source maps. 172 | 173 | ## 0.3.0 174 | 175 | - Change the default direction that searching for positions fuzzes when there is 176 | not an exact match. See #154. 177 | 178 | - Support for environments using json2.js for JSON serialization. See #156. 179 | 180 | ## 0.2.0 181 | 182 | - Support for consuming "indexed" source maps which do not have any remote 183 | sections. See pull request #127. This introduces a minor backwards 184 | incompatibility if you are monkey patching `SourceMapConsumer.prototype` 185 | methods. 186 | 187 | ## 0.1.43 188 | 189 | - Performance improvements for `SourceMapGenerator` and `SourceNode`. See issue 190 | #148 for some discussion and issues #150, #151, and #152 for implementations. 191 | 192 | ## 0.1.42 193 | 194 | - Fix an issue where `SourceNode`s from different versions of the source-map 195 | library couldn't be used in conjunction with each other. See issue #142. 196 | 197 | ## 0.1.41 198 | 199 | - Fix a bug with getting the source content of relative sources with a "./" 200 | prefix. See issue #145 and [Bug 1090768](bugzil.la/1090768). 201 | 202 | - Add the `SourceMapConsumer.prototype.computeColumnSpans` method to compute the 203 | column span of each mapping. 204 | 205 | - Add the `SourceMapConsumer.prototype.allGeneratedPositionsFor` method to find 206 | all generated positions associated with a given original source and line. 207 | 208 | ## 0.1.40 209 | 210 | - Performance improvements for parsing source maps in SourceMapConsumer. 211 | 212 | ## 0.1.39 213 | 214 | - Fix a bug where setting a source's contents to null before any source content 215 | had been set before threw a TypeError. See issue #131. 216 | 217 | ## 0.1.38 218 | 219 | - Fix a bug where finding relative paths from an empty path were creating 220 | absolute paths. See issue #129. 221 | 222 | ## 0.1.37 223 | 224 | - Fix a bug where if the source root was an empty string, relative source paths 225 | would turn into absolute source paths. Issue #124. 226 | 227 | ## 0.1.36 228 | 229 | - Allow the `names` mapping property to be an empty string. Issue #121. 230 | 231 | ## 0.1.35 232 | 233 | - A third optional parameter was added to `SourceNode.fromStringWithSourceMap` 234 | to specify a path that relative sources in the second parameter should be 235 | relative to. Issue #105. 236 | 237 | - If no file property is given to a `SourceMapGenerator`, then the resulting 238 | source map will no longer have a `null` file property. The property will 239 | simply not exist. Issue #104. 240 | 241 | - Fixed a bug where consecutive newlines were ignored in `SourceNode`s. 242 | Issue #116. 243 | 244 | ## 0.1.34 245 | 246 | - Make `SourceNode` work with windows style ("\r\n") newlines. Issue #103. 247 | 248 | - Fix bug involving source contents and the 249 | `SourceMapGenerator.prototype.applySourceMap`. Issue #100. 250 | 251 | ## 0.1.33 252 | 253 | - Fix some edge cases surrounding path joining and URL resolution. 254 | 255 | - Add a third parameter for relative path to 256 | `SourceMapGenerator.prototype.applySourceMap`. 257 | 258 | - Fix issues with mappings and EOLs. 259 | 260 | ## 0.1.32 261 | 262 | - Fixed a bug where SourceMapConsumer couldn't handle negative relative columns 263 | (issue 92). 264 | 265 | - Fixed test runner to actually report number of failed tests as its process 266 | exit code. 267 | 268 | - Fixed a typo when reporting bad mappings (issue 87). 269 | 270 | ## 0.1.31 271 | 272 | - Delay parsing the mappings in SourceMapConsumer until queried for a source 273 | location. 274 | 275 | - Support Sass source maps (which at the time of writing deviate from the spec 276 | in small ways) in SourceMapConsumer. 277 | 278 | ## 0.1.30 279 | 280 | - Do not join source root with a source, when the source is a data URI. 281 | 282 | - Extend the test runner to allow running single specific test files at a time. 283 | 284 | - Performance improvements in `SourceNode.prototype.walk` and 285 | `SourceMapConsumer.prototype.eachMapping`. 286 | 287 | - Source map browser builds will now work inside Workers. 288 | 289 | - Better error messages when attempting to add an invalid mapping to a 290 | `SourceMapGenerator`. 291 | 292 | ## 0.1.29 293 | 294 | - Allow duplicate entries in the `names` and `sources` arrays of source maps 295 | (usually from TypeScript) we are parsing. Fixes github issue 72. 296 | 297 | ## 0.1.28 298 | 299 | - Skip duplicate mappings when creating source maps from SourceNode; github 300 | issue 75. 301 | 302 | ## 0.1.27 303 | 304 | - Don't throw an error when the `file` property is missing in SourceMapConsumer, 305 | we don't use it anyway. 306 | 307 | ## 0.1.26 308 | 309 | - Fix SourceNode.fromStringWithSourceMap for empty maps. Fixes github issue 70. 310 | 311 | ## 0.1.25 312 | 313 | - Make compatible with browserify 314 | 315 | ## 0.1.24 316 | 317 | - Fix issue with absolute paths and `file://` URIs. See 318 | https://bugzilla.mozilla.org/show_bug.cgi?id=885597 319 | 320 | ## 0.1.23 321 | 322 | - Fix issue with absolute paths and sourcesContent, github issue 64. 323 | 324 | ## 0.1.22 325 | 326 | - Ignore duplicate mappings in SourceMapGenerator. Fixes github issue 21. 327 | 328 | ## 0.1.21 329 | 330 | - Fixed handling of sources that start with a slash so that they are relative to 331 | the source root's host. 332 | 333 | ## 0.1.20 334 | 335 | - Fixed github issue #43: absolute URLs aren't joined with the source root 336 | anymore. 337 | 338 | ## 0.1.19 339 | 340 | - Using Travis CI to run tests. 341 | 342 | ## 0.1.18 343 | 344 | - Fixed a bug in the handling of sourceRoot. 345 | 346 | ## 0.1.17 347 | 348 | - Added SourceNode.fromStringWithSourceMap. 349 | 350 | ## 0.1.16 351 | 352 | - Added missing documentation. 353 | 354 | - Fixed the generating of empty mappings in SourceNode. 355 | 356 | ## 0.1.15 357 | 358 | - Added SourceMapGenerator.applySourceMap. 359 | 360 | ## 0.1.14 361 | 362 | - The sourceRoot is now handled consistently. 363 | 364 | ## 0.1.13 365 | 366 | - Added SourceMapGenerator.fromSourceMap. 367 | 368 | ## 0.1.12 369 | 370 | - SourceNode now generates empty mappings too. 371 | 372 | ## 0.1.11 373 | 374 | - Added name support to SourceNode. 375 | 376 | ## 0.1.10 377 | 378 | - Added sourcesContent support to the customer and generator. 379 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Participation Guidelines 2 | 3 | This repository is governed by Mozilla's code of conduct and etiquette guidelines. 4 | For more details, please read the 5 | [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). 6 | 7 | ## How to Report 8 | 9 | For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page. 10 | 11 | 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for your interest in contributing to this library! Contributions are 4 | very appreciated. 5 | 6 | If you want help or mentorship, reach out to us in a GitHub issue, 7 | or over Matrix in [source-map on mozilla.org](https://matrix.to/#/#source-map:mozilla.org) 8 | and introduce yourself. 9 | 10 | --- 11 | 12 | ## Table of Contents 13 | 14 | 15 | 16 | 17 | - [Filing Issues](#filing-issues) 18 | - [Building From Source](#building-from-source) 19 | - [Submitting Pull Requests](#submitting-pull-requests) 20 | - [Running Tests](#running-tests) 21 | - [Writing New Tests](#writing-new-tests) 22 | - [Checking code coverage](#checking-code-coverage) 23 | - [Updating the `lib/mappings.wasm` WebAssembly Module](#updating-the-libmappingswasm-webassembly-module) 24 | 25 | 26 | 27 | ## Filing Issues 28 | 29 | If you are filing an issue for a bug or other misbehavior, please provide: 30 | 31 | - **A test case.** The more minimal the better, but sometimes a larger test case 32 | cannot be helped. This should be in the form of a gist, node script, 33 | repository, etc. 34 | 35 | - **Steps to reproduce the bug.** The more exact and specific the better. 36 | 37 | - **The result you expected.** 38 | 39 | - **The actual result.** 40 | 41 | ## Building From Source 42 | 43 | Install Node.js `12` or greater and then run 44 | 45 | $ git clone https://github.com/mozilla/source-map.git 46 | $ cd source-map/ 47 | $ npm install 48 | 49 | Next, run 50 | 51 | $ npm run build 52 | 53 | This will create the following files: 54 | 55 | - `dist/source-map.js` - The plain browser build. 56 | 57 | ## Submitting Pull Requests 58 | 59 | Make sure that tests pass locally before creating a pull request. 60 | 61 | Use a feature branch and pull request for each change, with logical commits. If 62 | your reviewer asks you to make changes before the pull request is accepted, 63 | fixup your existing commit(s) rather than adding follow up commits, and then 64 | force push to the remote branch to update the pull request. 65 | 66 | ## Running Tests 67 | 68 | The test suite is written for node.js. Install node.js `12` or greater and 69 | then run the tests with `npm test`: 70 | 71 | ```shell 72 | $ npm test 73 | > source-map@0.7.3 test /Users/fitzgen/src/source-map 74 | > node test/run-tests.js 75 | 76 | 77 | 137 / 137 tests passed. 78 | ``` 79 | 80 | ## Writing New Tests 81 | 82 | To add new tests, create a new file named `test/test-your-new-test-name.js` and 83 | export your test functions with names that start with "test", for example: 84 | 85 | ```js 86 | exports["test issue #123: doing the foo bar"] = function (assert) { 87 | ... 88 | }; 89 | ``` 90 | 91 | The new tests will be located and run automatically when you run the full test 92 | suite. 93 | 94 | The `assert` argument is a cut down version of node's assert module. You have 95 | access to the following assertion functions: 96 | 97 | - `doesNotThrow` 98 | 99 | - `equal` 100 | 101 | - `ok` 102 | 103 | - `strictEqual` 104 | 105 | - `throws` 106 | 107 | (The reason for the restricted set of test functions is because we need the 108 | tests to run inside Firefox's test suite as well and Firefox has a shimmed 109 | version of the assert module.) 110 | 111 | There are additional test utilities and helpers in `./test/util.js` which you 112 | can use as well: 113 | 114 | ```js 115 | var util = require("./util"); 116 | ``` 117 | 118 | ## Checking code coverage 119 | 120 | It's fun to find ways to test lines of code that aren't visited by the tests yet. 121 | 122 | ```shell 123 | $ npm run coverage 124 | $ open coverage/index.html 125 | ``` 126 | 127 | This will allow you to browse to red sections of the code that need more attention. 128 | 129 | ## Updating the `lib/mappings.wasm` WebAssembly Module 130 | 131 | Ensure that you have the Rust toolchain installed: 132 | 133 | ``` 134 | $ curl https://sh.rustup.rs -sSf | sh 135 | ``` 136 | 137 | The `wasm32-unknown-unknown` target is nightly-only at the time of writing. Use 138 | `rustup` to ensure you have it installed: 139 | 140 | ``` 141 | $ rustup toolchain install nightly 142 | $ rustup target add wasm32-unknown-unknown --toolchain nightly 143 | ``` 144 | 145 | Move to wasm sources folder: 146 | 147 | ``` 148 | $ cd wasm-mappings/ 149 | ``` 150 | 151 | Make sure the crate's tests pass: 152 | 153 | ``` 154 | $ cargo test 155 | ``` 156 | 157 | Ensure that you have the following wasm post-processing tools installed: 158 | 159 | - `wasm-nm`: https://github.com/fitzgen/wasm-nm 160 | - `wasm-gc`: https://github.com/alexcrichton/wasm-gc 161 | - `wasm-snip`: https://github.com/fitzgen/wasm-snip 162 | - `wasm-opt`: https://github.com/WebAssembly/binaryen 163 | 164 | (These dependencies should automatically be installed by cargo) 165 | 166 | Build Rust crate as a `.wasm` file: 167 | 168 | ``` 169 | $ cd source-map-mappings-wasm-api/ 170 | $ ./build.py -o ../../lib/mappings.wasm 171 | ``` 172 | 173 | See further information in [wasm-mappings/CONTRIBUTING.md]. 174 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2009-2011, Mozilla Foundation and contributors 3 | All 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 met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * Neither the names of the Mozilla Foundation nor the names of project 16 | contributors may be used to endorse or promote products derived from this 17 | software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /bench/.gitignore: -------------------------------------------------------------------------------- 1 | *.csv 2 | -------------------------------------------------------------------------------- /bench/README.md: -------------------------------------------------------------------------------- 1 | # Benchmarking 2 | 3 | This directory contains helpers for benchmarking the `mozilla/source-map` 4 | library. 5 | 6 | Ensure that you have built the library, as these benchmarks rely on 7 | `dist/source-map.js`. See the main README.md for detais on building. 8 | 9 | Run a local webserver from the root of the repository: 10 | 11 | ``` 12 | $ cd source-map/ 13 | $ python2 -m SimpleHTTPServer # or `python3 -m http.server` 14 | ``` 15 | 16 | Open 17 | [http://localhost:8000/bench/bench.html](http://localhost:8000/bench/bench.html) 18 | in your browser. 19 | 20 | Open `bench.html` in a browser and click on the appropriate button. 21 | -------------------------------------------------------------------------------- /bench/bench-dom-bindings.js: -------------------------------------------------------------------------------- 1 | sourceMap.SourceMapConsumer.initialize({ 2 | "lib/mappings.wasm": "../lib/mappings.wasm?bust_cache=" + String(Math.random()).replace(/0\./, "") 3 | }); 4 | 5 | function bindRange(labelId, updater) { 6 | const label = document.getElementById(labelId); 7 | const input = label.querySelector("input"); 8 | 9 | input.addEventListener("input", e => { 10 | e.preventDefault(); 11 | updater(input.value); 12 | }); 13 | 14 | updater(input.value); 15 | } 16 | 17 | bindRange("warm-up-iters", input => { 18 | const value = parseInt(input, 10); 19 | WARM_UP_ITERATIONS = value; 20 | }); 21 | 22 | bindRange("bench-iters", input => { 23 | const value = parseInt(input, 10); 24 | BENCH_ITERATIONS = value; 25 | }); 26 | 27 | const whichMap = document.getElementById("input-map"); 28 | const multiplyBy = document.getElementById("multiply-size-by"); 29 | 30 | var testSourceMap = SCALA_JS_RUNTIME_SOURCE_MAP; 31 | 32 | const updateTestSourceMap = () => { 33 | const origMap = window[whichMap.value]; 34 | testSourceMap = JSON.parse(JSON.stringify(origMap)); 35 | 36 | const factor = parseInt(multiplyBy.value, 10); 37 | if (factor === 1) { 38 | return; 39 | } 40 | 41 | const mappings = new Array(factor); 42 | mappings.fill(origMap.mappings); 43 | testSourceMap.mappings = mappings.join(";"); 44 | 45 | for (let i = 0; i < factor; i++) { 46 | testSourceMap.sources.splice(testSourceMap.sources.length, 0, ...origMap.sources); 47 | testSourceMap.names.splice(testSourceMap.names.length, 0, ...origMap.names); 48 | } 49 | }; 50 | updateTestSourceMap(); 51 | 52 | whichMap.addEventListener("input", e => { 53 | e.preventDefault(); 54 | updateTestSourceMap(); 55 | }); 56 | 57 | multiplyBy.addEventListener("input", e => { 58 | e.preventDefault(); 59 | updateTestSourceMap(); 60 | }); 61 | 62 | var implAndBrowser = ""; 63 | 64 | const implAndBrowserInput = document.getElementById("impl-and-browser"); 65 | const updateImplAndBrowser = () => { 66 | implAndBrowser = implAndBrowserInput.value; 67 | }; 68 | implAndBrowserInput.addEventListener("input", updateImplAndBrowser); 69 | updateImplAndBrowser(); 70 | 71 | // Run a benchmark when the given button is clicked and display results in the 72 | // given element. 73 | function benchOnClick(button, results, benchName, bencher) { 74 | button.addEventListener( 75 | "click", 76 | async function(e) { 77 | e.preventDefault(); 78 | 79 | const buttons = [...document.querySelectorAll("button")]; 80 | buttons.forEach(b => b.setAttribute("disabled", true)); 81 | results.innerHTML = ""; 82 | await new Promise(r => requestAnimationFrame(r)); 83 | 84 | var stats = await bencher(); 85 | 86 | buttons.forEach(b => b.removeAttribute("disabled")); 87 | 88 | const csv = stats.xs 89 | .map(x => `"${implAndBrowser}",${testSourceMap.mappings.length},"${benchName}",${x}`) 90 | .join("\n"); 91 | 92 | results.innerHTML = ` 93 | 94 | 95 | 96 | 97 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 |
SamplesTotal (${stats.unit}) 98 | Mean (${stats.unit}) 99 | Standard Deviation (${stats.unit}) 100 |
${stats.samples()}${stats.total().toFixed(2)}${stats.mean().toFixed(2)}${stats.stddev().toFixed(2)}
111 |
${csv}
112 | `; 113 | }, 114 | false 115 | ); 116 | } 117 | 118 | for (let bench of Object.keys(benchmarks)) { 119 | const hr = document.createElement("hr"); 120 | document.body.appendChild(hr); 121 | 122 | const button = document.createElement("button"); 123 | button.innerHTML = `

${bench}

`; 124 | document.body.appendChild(button); 125 | 126 | const results = document.createElement("div"); 127 | document.body.appendChild(results); 128 | 129 | benchOnClick(button, results, bench, benchmarks[bench]); 130 | } 131 | -------------------------------------------------------------------------------- /bench/bench.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Benchmark Source Maps 5 | 6 | 22 | 23 | 24 |

Benchmark mozilla/source-map

25 | 26 |
27 |

28 | 32 |

33 |

34 | 38 |

39 |

40 | 54 |

55 |

56 | 60 |

61 |

62 | 66 |

67 |
68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /bench/bench.js: -------------------------------------------------------------------------------- 1 | function noop() {} 2 | 3 | if (typeof console === "undefined") { 4 | console = {}; 5 | } 6 | if (!console.time) { 7 | console.time = console.timeEnd = noop; 8 | } 9 | if (!console.profile) { 10 | console.profile = console.profileEnd = noop; 11 | } 12 | 13 | // Ensure that benchmarks don't get optimized away by calling this blackbox 14 | // function in your benchmark's action. 15 | var __benchmarkResults = []; 16 | var benchmarkBlackbox = [].push.bind(__benchmarkResults); 17 | 18 | const now = 19 | typeof window === "object" && window.performance && window.performance.now 20 | ? () => window.performance.now() 21 | : () => now(); 22 | 23 | const yieldForTick = 24 | typeof setTimeout === "function" ? () => new Promise(resolve => setTimeout(resolve, 1)) : () => Promise.resolve(); 25 | 26 | // Benchmark running an action n times. 27 | async function benchmark(setup, action, tearDown = () => {}) { 28 | __benchmarkResults = []; 29 | 30 | console.time("setup"); 31 | await setup(); 32 | console.timeEnd("setup"); 33 | 34 | // Warm up the JIT. 35 | console.time("warmup"); 36 | for (let i = 0; i < WARM_UP_ITERATIONS; i++) { 37 | await action(); 38 | await yieldForTick(); 39 | } 40 | console.timeEnd("warmup"); 41 | 42 | const stats = new Stats("ms"); 43 | 44 | for (let i = 0; i < BENCH_ITERATIONS; i++) { 45 | console.time("iteration"); 46 | const thisIterationStart = now(); 47 | await action(); 48 | stats.take(now() - thisIterationStart); 49 | console.timeEnd("iteration"); 50 | 51 | await yieldForTick(); 52 | } 53 | 54 | await tearDown(); 55 | return stats; 56 | } 57 | 58 | async function getTestMapping() { 59 | let smc = await new sourceMap.SourceMapConsumer(testSourceMap); 60 | 61 | let mappings = []; 62 | smc.eachMapping([].push, mappings, sourceMap.SourceMapConsumer.ORIGINAL_ORDER); 63 | 64 | let testMapping = mappings[Math.floor(mappings.length / 13)]; 65 | smc.destroy(); 66 | return testMapping; 67 | } 68 | 69 | var benchmarks = { 70 | "SourceMapGenerator#toString": () => { 71 | let smg; 72 | return benchmark( 73 | async function() { 74 | var smc = await new sourceMap.SourceMapConsumer(testSourceMap); 75 | smg = sourceMap.SourceMapGenerator.fromSourceMap(smc); 76 | smc.destroy(); 77 | }, 78 | () => { 79 | benchmarkBlackbox(smg.toString().length); 80 | } 81 | ); 82 | }, 83 | 84 | "set.first.breakpoint": () => { 85 | let testMapping; 86 | return benchmark( 87 | async function() { 88 | testMapping = await getTestMapping(); 89 | }, 90 | async function() { 91 | let smc = await new sourceMap.SourceMapConsumer(testSourceMap); 92 | 93 | benchmarkBlackbox( 94 | smc.allGeneratedPositionsFor({ 95 | source: testMapping.source, 96 | line: testMapping.originalLine 97 | }).length 98 | ); 99 | 100 | smc.destroy(); 101 | } 102 | ); 103 | }, 104 | 105 | "first.pause.at.exception": () => { 106 | let testMapping; 107 | return benchmark( 108 | async function() { 109 | testMapping = await getTestMapping(); 110 | }, 111 | async function() { 112 | let smc = await new sourceMap.SourceMapConsumer(testSourceMap); 113 | 114 | benchmarkBlackbox( 115 | smc.originalPositionFor({ 116 | line: testMapping.generatedLine, 117 | column: testMapping.generatedColumn 118 | }) 119 | ); 120 | 121 | smc.destroy(); 122 | } 123 | ); 124 | }, 125 | 126 | "subsequent.setting.breakpoints": () => { 127 | let testMapping; 128 | let smc; 129 | return benchmark( 130 | async function() { 131 | testMapping = await getTestMapping(); 132 | smc = await new sourceMap.SourceMapConsumer(testSourceMap); 133 | }, 134 | async function() { 135 | benchmarkBlackbox( 136 | smc.allGeneratedPositionsFor({ 137 | source: testMapping.source, 138 | line: testMapping.originalLine 139 | }) 140 | ); 141 | }, 142 | function() { 143 | smc.destroy(); 144 | } 145 | ); 146 | }, 147 | 148 | "subsequent.pausing.at.exceptions": () => { 149 | let testMapping; 150 | let smc; 151 | return benchmark( 152 | async function() { 153 | testMapping = await getTestMapping(); 154 | smc = await new sourceMap.SourceMapConsumer(testSourceMap); 155 | }, 156 | async function() { 157 | benchmarkBlackbox( 158 | smc.originalPositionFor({ 159 | line: testMapping.generatedLine, 160 | column: testMapping.generatedColumn 161 | }) 162 | ); 163 | }, 164 | function() { 165 | smc.destroy(); 166 | } 167 | ); 168 | }, 169 | 170 | "parse.and.iterate": () => { 171 | return benchmark(noop, async function() { 172 | const smc = await new sourceMap.SourceMapConsumer(testSourceMap); 173 | 174 | let maxLine = 0; 175 | let maxCol = 0; 176 | smc.eachMapping(m => { 177 | maxLine = Math.max(maxLine, m.generatedLine); 178 | maxLine = Math.max(maxLine, m.originalLine); 179 | maxCol = Math.max(maxCol, m.generatedColumn); 180 | maxCol = Math.max(maxCol, m.originalColumn); 181 | }); 182 | benchmarkBlackbox(maxLine); 183 | benchmarkBlackbox(maxCol); 184 | 185 | smc.destroy(); 186 | }); 187 | }, 188 | 189 | "iterate.already.parsed": () => { 190 | let smc; 191 | return benchmark( 192 | async function() { 193 | smc = await new sourceMap.SourceMapConsumer(testSourceMap); 194 | }, 195 | async function() { 196 | let maxLine = 0; 197 | let maxCol = 0; 198 | smc.eachMapping(m => { 199 | maxLine = Math.max(maxLine, m.generatedLine); 200 | maxLine = Math.max(maxLine, m.originalLine); 201 | maxCol = Math.max(maxCol, m.generatedColumn); 202 | maxCol = Math.max(maxCol, m.originalColumn); 203 | }); 204 | benchmarkBlackbox(maxLine); 205 | benchmarkBlackbox(maxCol); 206 | }, 207 | function() { 208 | smc.destroy(); 209 | } 210 | ); 211 | } 212 | }; 213 | -------------------------------------------------------------------------------- /bench/function-size.r: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | 3 | # Usage: 4 | # 5 | # ./function-size.r function-size.csv 6 | # 7 | # Output will be placed in SVG files. 8 | 9 | library(ggplot2) 10 | 11 | args <- commandArgs(trailingOnly = TRUE) 12 | 13 | data <- read.table(args[1], header = TRUE, sep = ",") 14 | 15 | thePlot <- ggplot(data, 16 | aes(x = reorder(Function, -Size), 17 | y = data$Size, 18 | fill = data$Function)) + 19 | geom_bar(stat="identity") + 20 | theme(legend.position = "none", 21 | axis.text.x = element_text(angle = 67.5, hjust = 1)) + 22 | ggtitle("WebAssembly Code Size by Function") + 23 | labs(y = "Code Size (bytes)", 24 | x = "Function") 25 | 26 | ggsave(plot = thePlot, 27 | file = "function-size.svg", 28 | device = "svg") 29 | 30 | crateData <- aggregate(. ~ Crate, data = data, sum) 31 | print(crateData) 32 | 33 | thePlot <- ggplot(crateData, 34 | aes(x = reorder(crateData$Crate, -crateData$Size), 35 | y = crateData$Size, 36 | fill = crateData$Crate)) + 37 | geom_bar(stat="identity") + 38 | theme(legend.position = "none", 39 | axis.text.x = element_text(angle = 45, hjust = 1)) + 40 | ggtitle("WebAssembly Code Size by Crate") + 41 | labs(y = "Code Size (bytes)", 42 | x = "Crate") 43 | 44 | ggsave(plot = thePlot, 45 | file = "crate-size.svg", 46 | device = "svg") 47 | -------------------------------------------------------------------------------- /bench/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "source-map-internal-benchmark", 3 | "private": true, 4 | "scripts": { 5 | "build": "webpack" 6 | }, 7 | "dependencies": { 8 | "webpack": "^4.9.1", 9 | "webpack-cli": "^3.1" 10 | } 11 | } -------------------------------------------------------------------------------- /bench/plot.r: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | 3 | # Usage: 4 | # 5 | # plot.r data.csv 6 | # 7 | # Output will be placed in SVG files. 8 | 9 | library(ggplot2) 10 | 11 | args <- commandArgs(trailingOnly = TRUE) 12 | 13 | data <- read.table(args[1], header = TRUE, sep = ",") 14 | 15 | jitterBoxPlot <- function(data, operation, titleText) { 16 | print(paste(operation, "---------------------------------------------------------------------------")) 17 | 18 | operationData <- subset(data, data$Operation == operation) 19 | 20 | operationData$Implementation.and.Browser <- factor( 21 | operationData$Implementation.and.Browser, 22 | levels = c( 23 | "JavaScript.Chrome.Canary", 24 | "WebAssembly.Chrome.Canary", 25 | "JavaScript.Firefox.Nightly", 26 | "WebAssembly.Firefox.Nightly" 27 | ) 28 | ) 29 | 30 | chromeJs <- subset(operationData, 31 | operationData$Implementation.and.Browser == "JavaScript.Chrome.Canary")$Time 32 | print(paste("mean: scala.js: Chrome+JS =", mean(chromeJs))) 33 | print(paste("cv: scala.js: Chrome+JS =", sd(chromeJs) / mean(chromeJs))) 34 | 35 | chromeWasm <- subset(operationData, 36 | operationData$Implementation.and.Browser == "WebAssembly.Chrome.Canary")$Time 37 | print(paste("mean: scala.js: Chrome+Wasm =", mean(chromeWasm))) 38 | print(paste("cv: scala.js: Chrome+Wasm =", sd(chromeWasm) / mean(chromeWasm))) 39 | 40 | firefoxJs <- subset(operationData, 41 | operationData$Implementation.and.Browser == "JavaScript.Firefox.Nightly")$Time 42 | print(paste("mean: scala.js: Firefox+JS =", mean(firefoxJs))) 43 | print(paste("cv: scala.js: Firefox+JS =", sd(firefoxJs) / mean(firefoxJs))) 44 | 45 | firefoxWasm <- subset(operationData, 46 | operationData$Implementation.and.Browser == "WebAssembly.Firefox.Nightly")$Time 47 | print(paste("mean: scala.js: Firefox+Wasm =", mean(firefoxWasm))) 48 | print(paste("cv: scala.js: Firefox+Wasm =", sd(firefoxWasm) / mean(firefoxWasm))) 49 | 50 | print(paste("normalized: Chrome =", mean(chromeWasm) / mean(chromeJs))) 51 | print(paste("normalized: Firefox =", mean(firefoxWasm) / mean(firefoxJs))) 52 | 53 | thePlot <- ggplot(operationData, 54 | aes(x = operationData$Implementation.and.Browser, 55 | y = operationData$Time, 56 | color = operationData$Implementation.and.Browser, 57 | pch = operationData$Implementation.and.Browser)) + 58 | geom_boxplot(outlier.shape = NA) + 59 | geom_jitter(position = position_jitter(width = 0.1)) + 60 | scale_y_continuous(limits = quantile(operationData$Time, c(NA, 0.99))) + 61 | expand_limits(y = 0) + 62 | theme(legend.position = "none", 63 | axis.text.x = element_text(angle = 45, hjust = 1), 64 | axis.title.x = element_blank()) + 65 | ggtitle(titleText) + 66 | labs(y = "Time (ms)", 67 | subtitle = "Scala.JS Source Map (Mappings String Size = 14,964,446)") 68 | 69 | ## print(thePlot) 70 | svgFile <- paste(operation, ".scalajs.svg", sep="") 71 | ggsave(plot = thePlot, 72 | file = svgFile, 73 | device = "svg") 74 | } 75 | 76 | largeData <- subset(data, Mappings.Size==14964446) 77 | 78 | jitterBoxPlot(largeData, "set.first.breakpoint", "Set First Breakpoint") 79 | jitterBoxPlot(largeData, "subsequent.setting.breakpoints", "Subsquent Setting Breakpoints") 80 | 81 | jitterBoxPlot(largeData, "first.pause.at.exception", "First Pause at Exception") 82 | jitterBoxPlot(largeData, "subsequent.pausing.at.exceptions", "Subsequent Pausing at Exceptions") 83 | 84 | jitterBoxPlot(largeData, "parse.and.iterate", "Parse and Iterate Each Mapping") 85 | jitterBoxPlot(largeData, "iterate.already.parsed", "Already Parsed, Iterate Each Mapping") 86 | 87 | meanPlot <- function(data, operation, titleText) { 88 | operationData <- subset(data, data$Operation == operation) 89 | 90 | operationData$Implementation.and.Browser <- factor( 91 | operationData$Implementation.and.Browser, 92 | levels = c( 93 | "JavaScript.Chrome.Canary", 94 | "WebAssembly.Chrome.Canary", 95 | "JavaScript.Firefox.Nightly", 96 | "WebAssembly.Firefox.Nightly" 97 | ) 98 | ) 99 | 100 | thePlot <- ggplot(operationData, 101 | aes(x = operationData$Mappings.Size, 102 | y = operationData$Time, 103 | color = operationData$Implementation.and.Browser, 104 | pch = operationData$Implementation.and.Browser)) + 105 | stat_summary(fun.y = mean, geom = "line") + 106 | stat_summary(fun.y = mean, geom = "point") + 107 | geom_point() + 108 | theme(legend.position = "bottom", 109 | legend.direction="vertical", 110 | legend.title = element_blank()) + 111 | scale_y_continuous(limits = quantile(operationData$Time, c(NA, 0.99))) + 112 | ## scale_x_log10() + 113 | ## scale_y_log10() + 114 | expand_limits(y = 0) + 115 | ggtitle(titleText) + 116 | labs(x = "Mappings String Size", 117 | y = "Time (ms)", 118 | subtitle = "Mean") 119 | 120 | ## print(thePlot) 121 | svgFile <- paste(operation, ".mean.svg", sep="") 122 | ggsave(plot = thePlot, 123 | file = svgFile, 124 | device = "svg") 125 | } 126 | 127 | meanPlot(data, "set.first.breakpoint", "Set First Breakpoint") 128 | meanPlot(data, "subsequent.setting.breakpoints", "Subsequent Setting Breakpoints") 129 | meanPlot(data, "first.pause.at.exception", "First Pause at Exception") 130 | meanPlot(data, "subsequent.pausing.at.exceptions", "Subsequent Pausing at Exceptions") 131 | meanPlot(data, "parse.and.iterate", "Parse and Iterate Each Mapping") 132 | meanPlot(data, "iterate.already.parsed", "Already Parsed, Iterate Each Mapping") 133 | -------------------------------------------------------------------------------- /bench/section-size.r: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | 3 | # Usage: 4 | # 5 | # ./section-size.r section-sizes.csv 6 | # 7 | # Output will be placed in SVG files. 8 | 9 | library(ggplot2) 10 | 11 | args <- commandArgs(trailingOnly = TRUE) 12 | 13 | data <- read.table(args[1], header = TRUE, sep = ",") 14 | 15 | thePlot <- ggplot(data, 16 | aes(x = reorder(Section, -Size), 17 | y = data$Size, 18 | fill = data$Section)) + 19 | geom_bar(stat="identity") + 20 | theme(legend.position = "none", 21 | axis.text.x = element_text(angle = 67.5, hjust = 1)) + 22 | ggtitle("WebAssembly Size by Section") + 23 | labs(y = "Size (bytes)", 24 | x = "Section") 25 | 26 | ggsave(plot = thePlot, 27 | file = "section-sizes.svg", 28 | device = "svg") 29 | -------------------------------------------------------------------------------- /bench/size.r: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | 3 | # Usage: 4 | # 5 | # size.r size.csv 6 | 7 | # Output will be placed in size.svg 8 | 9 | library(ggplot2) 10 | library(reshape2) 11 | 12 | args <- commandArgs(trailingOnly = TRUE) 13 | 14 | data <- read.table(args[1], header = TRUE, sep = ",") 15 | data <- melt(data, id.var = "Build") 16 | 17 | data$Build <- factor( 18 | data$Build, 19 | levels = c( 20 | "JavaScript", 21 | "JavaScript (gzip)", 22 | "WebAssembly", 23 | "WebAssembly (gc)", 24 | "WebAssembly (gc + snip)", 25 | "WebAssembly (gc + snip + opt)", 26 | "WebAssembly (gc + snip + opt + gzip)" 27 | ) 28 | ) 29 | 30 | thePlot <- ggplot(data, 31 | aes(x = data$Build, 32 | y = data$value, 33 | fill = variable)) + 34 | geom_bar(stat = "identity") + 35 | theme(legend.position = "bottom", 36 | legend.direction="vertical", 37 | legend.title = element_blank(), 38 | axis.text.x = element_text(angle = 45, hjust = 1)) + 39 | ggtitle("Code Size by Implementation and Build") + 40 | labs(x = "Implementation and Build", 41 | y = "Code Size (bytes)") + 42 | scale_fill_discrete(labels = c("JavaScript Size", "WebAssembly Size")) 43 | 44 | svgFile <- "size.svg" 45 | ggsave(plot = thePlot, 46 | file = svgFile, 47 | device = "svg") 48 | -------------------------------------------------------------------------------- /bench/stats.js: -------------------------------------------------------------------------------- 1 | // Original author: Jim Blandy 2 | 3 | function Stats(unit) { 4 | this.unit = unit || ""; 5 | this.xs = []; 6 | this.x0 = this.x1 = this.x2 = 0; 7 | } 8 | 9 | Stats.prototype.take = function (x) { 10 | this.xs.push(x); 11 | this.x0 += 1; 12 | this.x1 += x; 13 | this.x2 += x*x; 14 | } 15 | 16 | Stats.prototype.samples = function () { 17 | return this.x0; 18 | }; 19 | 20 | Stats.prototype.total = function () { 21 | return this.x1; 22 | }; 23 | 24 | Stats.prototype.mean = function () { 25 | return this.x1 / this.x0; 26 | }; 27 | 28 | Stats.prototype.stddev = function () { 29 | return Math.sqrt(this.x0 * this.x2 - this.x1 * this.x1) / (this.x0 - 1); 30 | }; 31 | 32 | Stats.prototype.toString = function () { 33 | return "[Stats " + 34 | "samples: " + this.samples() + ", " + 35 | "total: " + this.total() + " " + this.unit + ", " + 36 | "mean: " + this.mean() + " " + this.unit + ", " + 37 | "stddev: " + this.stddev() + " " + this.unit + "]"; 38 | }; 39 | -------------------------------------------------------------------------------- /bench/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const distDir = path.join(__dirname, "dist"); 3 | 4 | module.exports = { 5 | context: path.join(__dirname, ".."), 6 | entry: "./source-map.js", 7 | mode: "production", 8 | optimization: { 9 | minimize: false, 10 | }, 11 | output: { 12 | path: distDir, 13 | filename: "source-map.js", 14 | library: "sourceMap", 15 | libraryTarget: "umd", 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /lib/array-set.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: js; js-indent-level: 2; -*- */ 2 | /* 3 | * Copyright 2011 Mozilla Foundation and contributors 4 | * Licensed under the New BSD license. See LICENSE or: 5 | * http://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | /** 9 | * A data structure which is a combination of an array and a set. Adding a new 10 | * member is O(1), testing for membership is O(1), and finding the index of an 11 | * element is O(1). Removing elements from the set is not supported. Only 12 | * strings are supported for membership. 13 | */ 14 | class ArraySet { 15 | constructor() { 16 | this._array = []; 17 | this._set = new Map(); 18 | } 19 | 20 | /** 21 | * Static method for creating ArraySet instances from an existing array. 22 | */ 23 | static fromArray(aArray, aAllowDuplicates) { 24 | const set = new ArraySet(); 25 | for (let i = 0, len = aArray.length; i < len; i++) { 26 | set.add(aArray[i], aAllowDuplicates); 27 | } 28 | return set; 29 | } 30 | 31 | /** 32 | * Return how many unique items are in this ArraySet. If duplicates have been 33 | * added, than those do not count towards the size. 34 | * 35 | * @returns Number 36 | */ 37 | size() { 38 | return this._set.size; 39 | } 40 | 41 | /** 42 | * Add the given string to this set. 43 | * 44 | * @param String aStr 45 | */ 46 | add(aStr, aAllowDuplicates) { 47 | const isDuplicate = this.has(aStr); 48 | const idx = this._array.length; 49 | if (!isDuplicate || aAllowDuplicates) { 50 | this._array.push(aStr); 51 | } 52 | if (!isDuplicate) { 53 | this._set.set(aStr, idx); 54 | } 55 | } 56 | 57 | /** 58 | * Is the given string a member of this set? 59 | * 60 | * @param String aStr 61 | */ 62 | has(aStr) { 63 | return this._set.has(aStr); 64 | } 65 | 66 | /** 67 | * What is the index of the given string in the array? 68 | * 69 | * @param String aStr 70 | */ 71 | indexOf(aStr) { 72 | const idx = this._set.get(aStr); 73 | if (idx >= 0) { 74 | return idx; 75 | } 76 | throw new Error('"' + aStr + '" is not in the set.'); 77 | } 78 | 79 | /** 80 | * What is the element at the given index? 81 | * 82 | * @param Number aIdx 83 | */ 84 | at(aIdx) { 85 | if (aIdx >= 0 && aIdx < this._array.length) { 86 | return this._array[aIdx]; 87 | } 88 | throw new Error("No element indexed by " + aIdx); 89 | } 90 | 91 | /** 92 | * Returns the array representation of this set (which has the proper indices 93 | * indicated by indexOf). Note that this is a copy of the internal array used 94 | * for storing the members so that no one can mess with internal state. 95 | */ 96 | toArray() { 97 | return this._array.slice(); 98 | } 99 | } 100 | exports.ArraySet = ArraySet; 101 | -------------------------------------------------------------------------------- /lib/base64-vlq.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: js; js-indent-level: 2; -*- */ 2 | /* 3 | * Copyright 2011 Mozilla Foundation and contributors 4 | * Licensed under the New BSD license. See LICENSE or: 5 | * http://opensource.org/licenses/BSD-3-Clause 6 | * 7 | * Based on the Base 64 VLQ implementation in Closure Compiler: 8 | * https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java 9 | * 10 | * Copyright 2011 The Closure Compiler Authors. All rights reserved. 11 | * Redistribution and use in source and binary forms, with or without 12 | * modification, are permitted provided that the following conditions are 13 | * met: 14 | * 15 | * * Redistributions of source code must retain the above copyright 16 | * notice, this list of conditions and the following disclaimer. 17 | * * Redistributions in binary form must reproduce the above 18 | * copyright notice, this list of conditions and the following 19 | * disclaimer in the documentation and/or other materials provided 20 | * with the distribution. 21 | * * Neither the name of Google Inc. nor the names of its 22 | * contributors may be used to endorse or promote products derived 23 | * from this software without specific prior written permission. 24 | * 25 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 26 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 27 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 28 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 29 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 30 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 31 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 32 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 33 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 34 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 35 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | */ 37 | 38 | const base64 = require("./base64"); 39 | 40 | // A single base 64 digit can contain 6 bits of data. For the base 64 variable 41 | // length quantities we use in the source map spec, the first bit is the sign, 42 | // the next four bits are the actual value, and the 6th bit is the 43 | // continuation bit. The continuation bit tells us whether there are more 44 | // digits in this value following this digit. 45 | // 46 | // Continuation 47 | // | Sign 48 | // | | 49 | // V V 50 | // 101011 51 | 52 | const VLQ_BASE_SHIFT = 5; 53 | 54 | // binary: 100000 55 | const VLQ_BASE = 1 << VLQ_BASE_SHIFT; 56 | 57 | // binary: 011111 58 | const VLQ_BASE_MASK = VLQ_BASE - 1; 59 | 60 | // binary: 100000 61 | const VLQ_CONTINUATION_BIT = VLQ_BASE; 62 | 63 | /** 64 | * Converts from a two-complement value to a value where the sign bit is 65 | * placed in the least significant bit. For example, as decimals: 66 | * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary) 67 | * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary) 68 | */ 69 | function toVLQSigned(aValue) { 70 | return aValue < 0 ? (-aValue << 1) + 1 : (aValue << 1) + 0; 71 | } 72 | 73 | /** 74 | * Returns the base 64 VLQ encoded value. 75 | */ 76 | exports.encode = function base64VLQ_encode(aValue) { 77 | let encoded = ""; 78 | let digit; 79 | 80 | let vlq = toVLQSigned(aValue); 81 | 82 | do { 83 | digit = vlq & VLQ_BASE_MASK; 84 | vlq >>>= VLQ_BASE_SHIFT; 85 | if (vlq > 0) { 86 | // There are still more digits in this value, so we must make sure the 87 | // continuation bit is marked. 88 | digit |= VLQ_CONTINUATION_BIT; 89 | } 90 | encoded += base64.encode(digit); 91 | } while (vlq > 0); 92 | 93 | return encoded; 94 | }; 95 | -------------------------------------------------------------------------------- /lib/base64.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: js; js-indent-level: 2; -*- */ 2 | /* 3 | * Copyright 2011 Mozilla Foundation and contributors 4 | * Licensed under the New BSD license. See LICENSE or: 5 | * http://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | const intToCharMap = 9 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split(""); 10 | 11 | /** 12 | * Encode an integer in the range of 0 to 63 to a single base 64 digit. 13 | */ 14 | exports.encode = function (number) { 15 | if (0 <= number && number < intToCharMap.length) { 16 | return intToCharMap[number]; 17 | } 18 | throw new TypeError("Must be between 0 and 63: " + number); 19 | }; 20 | -------------------------------------------------------------------------------- /lib/binary-search.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: js; js-indent-level: 2; -*- */ 2 | /* 3 | * Copyright 2011 Mozilla Foundation and contributors 4 | * Licensed under the New BSD license. See LICENSE or: 5 | * http://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | exports.GREATEST_LOWER_BOUND = 1; 9 | exports.LEAST_UPPER_BOUND = 2; 10 | 11 | /** 12 | * Recursive implementation of binary search. 13 | * 14 | * @param aLow Indices here and lower do not contain the needle. 15 | * @param aHigh Indices here and higher do not contain the needle. 16 | * @param aNeedle The element being searched for. 17 | * @param aHaystack The non-empty array being searched. 18 | * @param aCompare Function which takes two elements and returns -1, 0, or 1. 19 | * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or 20 | * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the 21 | * closest element that is smaller than or greater than the one we are 22 | * searching for, respectively, if the exact element cannot be found. 23 | */ 24 | function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare, aBias) { 25 | // This function terminates when one of the following is true: 26 | // 27 | // 1. We find the exact element we are looking for. 28 | // 29 | // 2. We did not find the exact element, but we can return the index of 30 | // the next-closest element. 31 | // 32 | // 3. We did not find the exact element, and there is no next-closest 33 | // element than the one we are searching for, so we return -1. 34 | const mid = Math.floor((aHigh - aLow) / 2) + aLow; 35 | const cmp = aCompare(aNeedle, aHaystack[mid], true); 36 | if (cmp === 0) { 37 | // Found the element we are looking for. 38 | return mid; 39 | } else if (cmp > 0) { 40 | // Our needle is greater than aHaystack[mid]. 41 | if (aHigh - mid > 1) { 42 | // The element is in the upper half. 43 | return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare, aBias); 44 | } 45 | 46 | // The exact needle element was not found in this haystack. Determine if 47 | // we are in termination case (3) or (2) and return the appropriate thing. 48 | if (aBias === exports.LEAST_UPPER_BOUND) { 49 | return aHigh < aHaystack.length ? aHigh : -1; 50 | } 51 | return mid; 52 | } 53 | 54 | // Our needle is less than aHaystack[mid]. 55 | if (mid - aLow > 1) { 56 | // The element is in the lower half. 57 | return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare, aBias); 58 | } 59 | 60 | // we are in termination case (3) or (2) and return the appropriate thing. 61 | if (aBias == exports.LEAST_UPPER_BOUND) { 62 | return mid; 63 | } 64 | return aLow < 0 ? -1 : aLow; 65 | } 66 | 67 | /** 68 | * This is an implementation of binary search which will always try and return 69 | * the index of the closest element if there is no exact hit. This is because 70 | * mappings between original and generated line/col pairs are single points, 71 | * and there is an implicit region between each of them, so a miss just means 72 | * that you aren't on the very start of a region. 73 | * 74 | * @param aNeedle The element you are looking for. 75 | * @param aHaystack The array that is being searched. 76 | * @param aCompare A function which takes the needle and an element in the 77 | * array and returns -1, 0, or 1 depending on whether the needle is less 78 | * than, equal to, or greater than the element, respectively. 79 | * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or 80 | * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the 81 | * closest element that is smaller than or greater than the one we are 82 | * searching for, respectively, if the exact element cannot be found. 83 | * Defaults to 'binarySearch.GREATEST_LOWER_BOUND'. 84 | */ 85 | exports.search = function search(aNeedle, aHaystack, aCompare, aBias) { 86 | if (aHaystack.length === 0) { 87 | return -1; 88 | } 89 | 90 | let index = recursiveSearch( 91 | -1, 92 | aHaystack.length, 93 | aNeedle, 94 | aHaystack, 95 | aCompare, 96 | aBias || exports.GREATEST_LOWER_BOUND 97 | ); 98 | if (index < 0) { 99 | return -1; 100 | } 101 | 102 | // We have found either the exact element, or the next-closest element to 103 | // the one we are searching for. However, there may be more than one such 104 | // element. Make sure we always return the smallest of these. 105 | while (index - 1 >= 0) { 106 | if (aCompare(aHaystack[index], aHaystack[index - 1], true) !== 0) { 107 | break; 108 | } 109 | --index; 110 | } 111 | 112 | return index; 113 | }; 114 | -------------------------------------------------------------------------------- /lib/mapping-list.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: js; js-indent-level: 2; -*- */ 2 | /* 3 | * Copyright 2014 Mozilla Foundation and contributors 4 | * Licensed under the New BSD license. See LICENSE or: 5 | * http://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | const util = require("./util"); 9 | 10 | /** 11 | * Determine whether mappingB is after mappingA with respect to generated 12 | * position. 13 | */ 14 | function generatedPositionAfter(mappingA, mappingB) { 15 | // Optimized for most common case 16 | const lineA = mappingA.generatedLine; 17 | const lineB = mappingB.generatedLine; 18 | const columnA = mappingA.generatedColumn; 19 | const columnB = mappingB.generatedColumn; 20 | return ( 21 | lineB > lineA || 22 | (lineB == lineA && columnB >= columnA) || 23 | util.compareByGeneratedPositionsInflated(mappingA, mappingB) <= 0 24 | ); 25 | } 26 | 27 | /** 28 | * A data structure to provide a sorted view of accumulated mappings in a 29 | * performance conscious manner. It trades a negligible overhead in general 30 | * case for a large speedup in case of mappings being added in order. 31 | */ 32 | class MappingList { 33 | constructor() { 34 | this._array = []; 35 | this._sorted = true; 36 | // Serves as infimum 37 | this._last = { generatedLine: -1, generatedColumn: 0 }; 38 | } 39 | 40 | /** 41 | * Iterate through internal items. This method takes the same arguments that 42 | * `Array.prototype.forEach` takes. 43 | * 44 | * NOTE: The order of the mappings is NOT guaranteed. 45 | */ 46 | unsortedForEach(aCallback, aThisArg) { 47 | this._array.forEach(aCallback, aThisArg); 48 | } 49 | 50 | /** 51 | * Add the given source mapping. 52 | * 53 | * @param Object aMapping 54 | */ 55 | add(aMapping) { 56 | if (generatedPositionAfter(this._last, aMapping)) { 57 | this._last = aMapping; 58 | this._array.push(aMapping); 59 | } else { 60 | this._sorted = false; 61 | this._array.push(aMapping); 62 | } 63 | } 64 | 65 | /** 66 | * Returns the flat, sorted array of mappings. The mappings are sorted by 67 | * generated position. 68 | * 69 | * WARNING: This method returns internal data without copying, for 70 | * performance. The return value must NOT be mutated, and should be treated as 71 | * an immutable borrow. If you want to take ownership, you must make your own 72 | * copy. 73 | */ 74 | toArray() { 75 | if (!this._sorted) { 76 | this._array.sort(util.compareByGeneratedPositionsInflated); 77 | this._sorted = true; 78 | } 79 | return this._array; 80 | } 81 | } 82 | 83 | exports.MappingList = MappingList; 84 | -------------------------------------------------------------------------------- /lib/mappings.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/source-map/3cb92cc3b73bfab27c146bae4ef2bc09dbb4e5ed/lib/mappings.wasm -------------------------------------------------------------------------------- /lib/read-wasm-browser.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let mappingsWasm = null; 4 | 5 | module.exports = function readWasm() { 6 | if (typeof mappingsWasm === "string") { 7 | return fetch(mappingsWasm).then(response => response.arrayBuffer()); 8 | } 9 | if (mappingsWasm instanceof ArrayBuffer) { 10 | return Promise.resolve(mappingsWasm); 11 | } 12 | 13 | throw new Error( 14 | "You must provide the string URL or ArrayBuffer contents " + 15 | "of lib/mappings.wasm by calling " + 16 | "SourceMapConsumer.initialize({ 'lib/mappings.wasm': ... }) " + 17 | "before using SourceMapConsumer" 18 | ); 19 | }; 20 | 21 | module.exports.initialize = input => { 22 | mappingsWasm = input; 23 | }; 24 | -------------------------------------------------------------------------------- /lib/read-wasm.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Note: This file is replaced with "read-wasm-browser.js" when this module is 4 | // bundled with a packager that takes package.json#browser fields into account. 5 | 6 | const fs = require("fs"); 7 | const path = require("path"); 8 | 9 | module.exports = function readWasm() { 10 | return new Promise((resolve, reject) => { 11 | const wasmPath = path.join(__dirname, "mappings.wasm"); 12 | fs.readFile(wasmPath, null, (error, data) => { 13 | if (error) { 14 | reject(error); 15 | return; 16 | } 17 | 18 | resolve(data.buffer); 19 | }); 20 | }); 21 | }; 22 | 23 | module.exports.initialize = _ => { 24 | console.debug( 25 | "SourceMapConsumer.initialize is a no-op when running in node.js" 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /lib/source-node.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: js; js-indent-level: 2; -*- */ 2 | /* 3 | * Copyright 2011 Mozilla Foundation and contributors 4 | * Licensed under the New BSD license. See LICENSE or: 5 | * http://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | const SourceMapGenerator = require("./source-map-generator").SourceMapGenerator; 9 | const util = require("./util"); 10 | 11 | // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other 12 | // operating systems these days (capturing the result). 13 | const REGEX_NEWLINE = /(\r?\n)/; 14 | 15 | // Newline character code for charCodeAt() comparisons 16 | const NEWLINE_CODE = 10; 17 | 18 | // Private symbol for identifying `SourceNode`s when multiple versions of 19 | // the source-map library are loaded. This MUST NOT CHANGE across 20 | // versions! 21 | const isSourceNode = "$$$isSourceNode$$$"; 22 | 23 | /** 24 | * SourceNodes provide a way to abstract over interpolating/concatenating 25 | * snippets of generated JavaScript source code while maintaining the line and 26 | * column information associated with the original source code. 27 | * 28 | * @param aLine The original line number. 29 | * @param aColumn The original column number. 30 | * @param aSource The original source's filename. 31 | * @param aChunks Optional. An array of strings which are snippets of 32 | * generated JS, or other SourceNodes. 33 | * @param aName The original identifier. 34 | */ 35 | class SourceNode { 36 | constructor(aLine, aColumn, aSource, aChunks, aName) { 37 | this.children = []; 38 | this.sourceContents = {}; 39 | this.line = aLine == null ? null : aLine; 40 | this.column = aColumn == null ? null : aColumn; 41 | this.source = aSource == null ? null : aSource; 42 | this.name = aName == null ? null : aName; 43 | this[isSourceNode] = true; 44 | if (aChunks != null) this.add(aChunks); 45 | } 46 | 47 | /** 48 | * Creates a SourceNode from generated code and a SourceMapConsumer. 49 | * 50 | * @param aGeneratedCode The generated code 51 | * @param aSourceMapConsumer The SourceMap for the generated code 52 | * @param aRelativePath Optional. The path that relative sources in the 53 | * SourceMapConsumer should be relative to. 54 | */ 55 | static fromStringWithSourceMap( 56 | aGeneratedCode, 57 | aSourceMapConsumer, 58 | aRelativePath 59 | ) { 60 | // The SourceNode we want to fill with the generated code 61 | // and the SourceMap 62 | const node = new SourceNode(); 63 | 64 | // All even indices of this array are one line of the generated code, 65 | // while all odd indices are the newlines between two adjacent lines 66 | // (since `REGEX_NEWLINE` captures its match). 67 | // Processed fragments are accessed by calling `shiftNextLine`. 68 | const remainingLines = aGeneratedCode.split(REGEX_NEWLINE); 69 | let remainingLinesIndex = 0; 70 | const shiftNextLine = function () { 71 | const lineContents = getNextLine(); 72 | // The last line of a file might not have a newline. 73 | const newLine = getNextLine() || ""; 74 | return lineContents + newLine; 75 | 76 | function getNextLine() { 77 | return remainingLinesIndex < remainingLines.length 78 | ? remainingLines[remainingLinesIndex++] 79 | : undefined; 80 | } 81 | }; 82 | 83 | // We need to remember the position of "remainingLines" 84 | let lastGeneratedLine = 1, 85 | lastGeneratedColumn = 0; 86 | 87 | // The generate SourceNodes we need a code range. 88 | // To extract it current and last mapping is used. 89 | // Here we store the last mapping. 90 | let lastMapping = null; 91 | let nextLine; 92 | 93 | aSourceMapConsumer.eachMapping(function (mapping) { 94 | if (lastMapping !== null) { 95 | // We add the code from "lastMapping" to "mapping": 96 | // First check if there is a new line in between. 97 | if (lastGeneratedLine < mapping.generatedLine) { 98 | // Associate first line with "lastMapping" 99 | addMappingWithCode(lastMapping, shiftNextLine()); 100 | lastGeneratedLine++; 101 | lastGeneratedColumn = 0; 102 | // The remaining code is added without mapping 103 | } else { 104 | // There is no new line in between. 105 | // Associate the code between "lastGeneratedColumn" and 106 | // "mapping.generatedColumn" with "lastMapping" 107 | nextLine = remainingLines[remainingLinesIndex] || ""; 108 | const code = nextLine.substr( 109 | 0, 110 | mapping.generatedColumn - lastGeneratedColumn 111 | ); 112 | remainingLines[remainingLinesIndex] = nextLine.substr( 113 | mapping.generatedColumn - lastGeneratedColumn 114 | ); 115 | lastGeneratedColumn = mapping.generatedColumn; 116 | addMappingWithCode(lastMapping, code); 117 | // No more remaining code, continue 118 | lastMapping = mapping; 119 | return; 120 | } 121 | } 122 | // We add the generated code until the first mapping 123 | // to the SourceNode without any mapping. 124 | // Each line is added as separate string. 125 | while (lastGeneratedLine < mapping.generatedLine) { 126 | node.add(shiftNextLine()); 127 | lastGeneratedLine++; 128 | } 129 | if (lastGeneratedColumn < mapping.generatedColumn) { 130 | nextLine = remainingLines[remainingLinesIndex] || ""; 131 | node.add(nextLine.substr(0, mapping.generatedColumn)); 132 | remainingLines[remainingLinesIndex] = nextLine.substr( 133 | mapping.generatedColumn 134 | ); 135 | lastGeneratedColumn = mapping.generatedColumn; 136 | } 137 | lastMapping = mapping; 138 | }, this); 139 | // We have processed all mappings. 140 | if (remainingLinesIndex < remainingLines.length) { 141 | if (lastMapping) { 142 | // Associate the remaining code in the current line with "lastMapping" 143 | addMappingWithCode(lastMapping, shiftNextLine()); 144 | } 145 | // and add the remaining lines without any mapping 146 | node.add(remainingLines.splice(remainingLinesIndex).join("")); 147 | } 148 | 149 | // Copy sourcesContent into SourceNode 150 | aSourceMapConsumer.sources.forEach(function (sourceFile) { 151 | const content = aSourceMapConsumer.sourceContentFor(sourceFile); 152 | if (content != null) { 153 | if (aRelativePath != null) { 154 | sourceFile = util.join(aRelativePath, sourceFile); 155 | } 156 | node.setSourceContent(sourceFile, content); 157 | } 158 | }); 159 | 160 | return node; 161 | 162 | function addMappingWithCode(mapping, code) { 163 | if (mapping === null || mapping.source === undefined) { 164 | node.add(code); 165 | } else { 166 | const source = aRelativePath 167 | ? util.join(aRelativePath, mapping.source) 168 | : mapping.source; 169 | node.add( 170 | new SourceNode( 171 | mapping.originalLine, 172 | mapping.originalColumn, 173 | source, 174 | code, 175 | mapping.name 176 | ) 177 | ); 178 | } 179 | } 180 | } 181 | 182 | /** 183 | * Add a chunk of generated JS to this source node. 184 | * 185 | * @param aChunk A string snippet of generated JS code, another instance of 186 | * SourceNode, or an array where each member is one of those things. 187 | */ 188 | add(aChunk) { 189 | if (Array.isArray(aChunk)) { 190 | aChunk.forEach(function (chunk) { 191 | this.add(chunk); 192 | }, this); 193 | } else if (aChunk[isSourceNode] || typeof aChunk === "string") { 194 | if (aChunk) { 195 | this.children.push(aChunk); 196 | } 197 | } else { 198 | throw new TypeError( 199 | "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + 200 | aChunk 201 | ); 202 | } 203 | return this; 204 | } 205 | 206 | /** 207 | * Add a chunk of generated JS to the beginning of this source node. 208 | * 209 | * @param aChunk A string snippet of generated JS code, another instance of 210 | * SourceNode, or an array where each member is one of those things. 211 | */ 212 | prepend(aChunk) { 213 | if (Array.isArray(aChunk)) { 214 | for (let i = aChunk.length - 1; i >= 0; i--) { 215 | this.prepend(aChunk[i]); 216 | } 217 | } else if (aChunk[isSourceNode] || typeof aChunk === "string") { 218 | this.children.unshift(aChunk); 219 | } else { 220 | throw new TypeError( 221 | "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + 222 | aChunk 223 | ); 224 | } 225 | return this; 226 | } 227 | 228 | /** 229 | * Walk over the tree of JS snippets in this node and its children. The 230 | * walking function is called once for each snippet of JS and is passed that 231 | * snippet and the its original associated source's line/column location. 232 | * 233 | * @param aFn The traversal function. 234 | */ 235 | walk(aFn) { 236 | let chunk; 237 | for (let i = 0, len = this.children.length; i < len; i++) { 238 | chunk = this.children[i]; 239 | if (chunk[isSourceNode]) { 240 | chunk.walk(aFn); 241 | } else if (chunk !== "") { 242 | aFn(chunk, { 243 | source: this.source, 244 | line: this.line, 245 | column: this.column, 246 | name: this.name, 247 | }); 248 | } 249 | } 250 | } 251 | 252 | /** 253 | * Like `String.prototype.join` except for SourceNodes. Inserts `aStr` between 254 | * each of `this.children`. 255 | * 256 | * @param aSep The separator. 257 | */ 258 | join(aSep) { 259 | let newChildren; 260 | let i; 261 | const len = this.children.length; 262 | if (len > 0) { 263 | newChildren = []; 264 | for (i = 0; i < len - 1; i++) { 265 | newChildren.push(this.children[i]); 266 | newChildren.push(aSep); 267 | } 268 | newChildren.push(this.children[i]); 269 | this.children = newChildren; 270 | } 271 | return this; 272 | } 273 | 274 | /** 275 | * Call String.prototype.replace on the very right-most source snippet. Useful 276 | * for trimming whitespace from the end of a source node, etc. 277 | * 278 | * @param aPattern The pattern to replace. 279 | * @param aReplacement The thing to replace the pattern with. 280 | */ 281 | replaceRight(aPattern, aReplacement) { 282 | const lastChild = this.children[this.children.length - 1]; 283 | if (lastChild[isSourceNode]) { 284 | lastChild.replaceRight(aPattern, aReplacement); 285 | } else if (typeof lastChild === "string") { 286 | this.children[this.children.length - 1] = lastChild.replace( 287 | aPattern, 288 | aReplacement 289 | ); 290 | } else { 291 | this.children.push("".replace(aPattern, aReplacement)); 292 | } 293 | return this; 294 | } 295 | 296 | /** 297 | * Set the source content for a source file. This will be added to the SourceMapGenerator 298 | * in the sourcesContent field. 299 | * 300 | * @param aSourceFile The filename of the source file 301 | * @param aSourceContent The content of the source file 302 | */ 303 | setSourceContent(aSourceFile, aSourceContent) { 304 | this.sourceContents[util.toSetString(aSourceFile)] = aSourceContent; 305 | } 306 | 307 | /** 308 | * Walk over the tree of SourceNodes. The walking function is called for each 309 | * source file content and is passed the filename and source content. 310 | * 311 | * @param aFn The traversal function. 312 | */ 313 | walkSourceContents(aFn) { 314 | for (let i = 0, len = this.children.length; i < len; i++) { 315 | if (this.children[i][isSourceNode]) { 316 | this.children[i].walkSourceContents(aFn); 317 | } 318 | } 319 | 320 | const sources = Object.keys(this.sourceContents); 321 | for (let i = 0, len = sources.length; i < len; i++) { 322 | aFn(util.fromSetString(sources[i]), this.sourceContents[sources[i]]); 323 | } 324 | } 325 | 326 | /** 327 | * Return the string representation of this source node. Walks over the tree 328 | * and concatenates all the various snippets together to one string. 329 | */ 330 | toString() { 331 | let str = ""; 332 | this.walk(function (chunk) { 333 | str += chunk; 334 | }); 335 | return str; 336 | } 337 | 338 | /** 339 | * Returns the string representation of this source node along with a source 340 | * map. 341 | */ 342 | toStringWithSourceMap(aArgs) { 343 | const generated = { 344 | code: "", 345 | line: 1, 346 | column: 0, 347 | }; 348 | const map = new SourceMapGenerator(aArgs); 349 | let sourceMappingActive = false; 350 | let lastOriginalSource = null; 351 | let lastOriginalLine = null; 352 | let lastOriginalColumn = null; 353 | let lastOriginalName = null; 354 | this.walk(function (chunk, original) { 355 | generated.code += chunk; 356 | if ( 357 | original.source !== null && 358 | original.line !== null && 359 | original.column !== null 360 | ) { 361 | if ( 362 | lastOriginalSource !== original.source || 363 | lastOriginalLine !== original.line || 364 | lastOriginalColumn !== original.column || 365 | lastOriginalName !== original.name 366 | ) { 367 | map.addMapping({ 368 | source: original.source, 369 | original: { 370 | line: original.line, 371 | column: original.column, 372 | }, 373 | generated: { 374 | line: generated.line, 375 | column: generated.column, 376 | }, 377 | name: original.name, 378 | }); 379 | } 380 | lastOriginalSource = original.source; 381 | lastOriginalLine = original.line; 382 | lastOriginalColumn = original.column; 383 | lastOriginalName = original.name; 384 | sourceMappingActive = true; 385 | } else if (sourceMappingActive) { 386 | map.addMapping({ 387 | generated: { 388 | line: generated.line, 389 | column: generated.column, 390 | }, 391 | }); 392 | lastOriginalSource = null; 393 | sourceMappingActive = false; 394 | } 395 | for (let idx = 0, length = chunk.length; idx < length; idx++) { 396 | if (chunk.charCodeAt(idx) === NEWLINE_CODE) { 397 | generated.line++; 398 | generated.column = 0; 399 | // Mappings end at eol 400 | if (idx + 1 === length) { 401 | lastOriginalSource = null; 402 | sourceMappingActive = false; 403 | } else if (sourceMappingActive) { 404 | map.addMapping({ 405 | source: original.source, 406 | original: { 407 | line: original.line, 408 | column: original.column, 409 | }, 410 | generated: { 411 | line: generated.line, 412 | column: generated.column, 413 | }, 414 | name: original.name, 415 | }); 416 | } 417 | } else { 418 | generated.column++; 419 | } 420 | } 421 | }); 422 | this.walkSourceContents(function (sourceFile, sourceContent) { 423 | map.setSourceContent(sourceFile, sourceContent); 424 | }); 425 | 426 | return { code: generated.code, map }; 427 | } 428 | } 429 | 430 | exports.SourceNode = SourceNode; 431 | -------------------------------------------------------------------------------- /lib/url-browser.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: js; js-indent-level: 2; -*- */ 2 | /* 3 | * Copyright 2011 Mozilla Foundation and contributors 4 | * Licensed under the New BSD license. See LICENSE or: 5 | * http://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | "use strict"; 8 | 9 | /** 10 | * Browser 'URL' implementations have been found to handle non-standard URL 11 | * schemes poorly, and schemes like 12 | * 13 | * webpack:///src/folder/file.js 14 | * 15 | * are very common in source maps. For the time being we use a JS 16 | * implementation in these contexts instead. See 17 | * 18 | * * https://bugzilla.mozilla.org/show_bug.cgi?id=1374505 19 | * * https://bugs.chromium.org/p/chromium/issues/detail?id=734880 20 | */ 21 | module.exports = require("whatwg-url").URL; 22 | -------------------------------------------------------------------------------- /lib/url.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: js; js-indent-level: 2; -*- */ 2 | /* 3 | * Copyright 2011 Mozilla Foundation and contributors 4 | * Licensed under the New BSD license. See LICENSE or: 5 | * http://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | "use strict"; 8 | 9 | // Note: This file is overridden in the 'package.json#browser' field to 10 | // substitute lib/url-browser.js instead. 11 | 12 | // Use the URL global for Node 10, and the 'url' module for Node 8. 13 | module.exports = typeof URL === "function" ? URL : require("url").URL; 14 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: js; js-indent-level: 2; -*- */ 2 | /* 3 | * Copyright 2011 Mozilla Foundation and contributors 4 | * Licensed under the New BSD license. See LICENSE or: 5 | * http://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | const URL = require("./url"); 9 | 10 | /** 11 | * This is a helper function for getting values from parameter/options 12 | * objects. 13 | * 14 | * @param args The object we are extracting values from 15 | * @param name The name of the property we are getting. 16 | * @param defaultValue An optional value to return if the property is missing 17 | * from the object. If this is not specified and the property is missing, an 18 | * error will be thrown. 19 | */ 20 | function getArg(aArgs, aName, aDefaultValue) { 21 | if (aName in aArgs) { 22 | return aArgs[aName]; 23 | } else if (arguments.length === 3) { 24 | return aDefaultValue; 25 | } 26 | throw new Error('"' + aName + '" is a required argument.'); 27 | } 28 | exports.getArg = getArg; 29 | 30 | const supportsNullProto = (function () { 31 | const obj = Object.create(null); 32 | return !("__proto__" in obj); 33 | })(); 34 | 35 | function identity(s) { 36 | return s; 37 | } 38 | 39 | /** 40 | * Because behavior goes wacky when you set `__proto__` on objects, we 41 | * have to prefix all the strings in our set with an arbitrary character. 42 | * 43 | * See https://github.com/mozilla/source-map/pull/31 and 44 | * https://github.com/mozilla/source-map/issues/30 45 | * 46 | * @param String aStr 47 | */ 48 | function toSetString(aStr) { 49 | if (isProtoString(aStr)) { 50 | return "$" + aStr; 51 | } 52 | 53 | return aStr; 54 | } 55 | exports.toSetString = supportsNullProto ? identity : toSetString; 56 | 57 | function fromSetString(aStr) { 58 | if (isProtoString(aStr)) { 59 | return aStr.slice(1); 60 | } 61 | 62 | return aStr; 63 | } 64 | exports.fromSetString = supportsNullProto ? identity : fromSetString; 65 | 66 | function isProtoString(s) { 67 | if (!s) { 68 | return false; 69 | } 70 | 71 | const length = s.length; 72 | 73 | if (length < 9 /* "__proto__".length */) { 74 | return false; 75 | } 76 | 77 | /* eslint-disable no-multi-spaces */ 78 | if ( 79 | s.charCodeAt(length - 1) !== 95 /* '_' */ || 80 | s.charCodeAt(length - 2) !== 95 /* '_' */ || 81 | s.charCodeAt(length - 3) !== 111 /* 'o' */ || 82 | s.charCodeAt(length - 4) !== 116 /* 't' */ || 83 | s.charCodeAt(length - 5) !== 111 /* 'o' */ || 84 | s.charCodeAt(length - 6) !== 114 /* 'r' */ || 85 | s.charCodeAt(length - 7) !== 112 /* 'p' */ || 86 | s.charCodeAt(length - 8) !== 95 /* '_' */ || 87 | s.charCodeAt(length - 9) !== 95 /* '_' */ 88 | ) { 89 | return false; 90 | } 91 | /* eslint-enable no-multi-spaces */ 92 | 93 | for (let i = length - 10; i >= 0; i--) { 94 | if (s.charCodeAt(i) !== 36 /* '$' */) { 95 | return false; 96 | } 97 | } 98 | 99 | return true; 100 | } 101 | 102 | function strcmp(aStr1, aStr2) { 103 | if (aStr1 === aStr2) { 104 | return 0; 105 | } 106 | 107 | if (aStr1 === null) { 108 | return 1; // aStr2 !== null 109 | } 110 | 111 | if (aStr2 === null) { 112 | return -1; // aStr1 !== null 113 | } 114 | 115 | if (aStr1 > aStr2) { 116 | return 1; 117 | } 118 | 119 | return -1; 120 | } 121 | 122 | /** 123 | * Comparator between two mappings with inflated source and name strings where 124 | * the generated positions are compared. 125 | */ 126 | function compareByGeneratedPositionsInflated(mappingA, mappingB) { 127 | let cmp = mappingA.generatedLine - mappingB.generatedLine; 128 | if (cmp !== 0) { 129 | return cmp; 130 | } 131 | 132 | cmp = mappingA.generatedColumn - mappingB.generatedColumn; 133 | if (cmp !== 0) { 134 | return cmp; 135 | } 136 | 137 | cmp = strcmp(mappingA.source, mappingB.source); 138 | if (cmp !== 0) { 139 | return cmp; 140 | } 141 | 142 | cmp = mappingA.originalLine - mappingB.originalLine; 143 | if (cmp !== 0) { 144 | return cmp; 145 | } 146 | 147 | cmp = mappingA.originalColumn - mappingB.originalColumn; 148 | if (cmp !== 0) { 149 | return cmp; 150 | } 151 | 152 | return strcmp(mappingA.name, mappingB.name); 153 | } 154 | exports.compareByGeneratedPositionsInflated = 155 | compareByGeneratedPositionsInflated; 156 | 157 | /** 158 | * Strip any JSON XSSI avoidance prefix from the string (as documented 159 | * in the source maps specification), and then parse the string as 160 | * JSON. 161 | */ 162 | function parseSourceMapInput(str) { 163 | return JSON.parse(str.replace(/^\)]}'[^\n]*\n/, "")); 164 | } 165 | exports.parseSourceMapInput = parseSourceMapInput; 166 | 167 | // We use 'http' as the base here because we want URLs processed relative 168 | // to the safe base to be treated as "special" URLs during parsing using 169 | // the WHATWG URL parsing. This ensures that backslash normalization 170 | // applies to the path and such. 171 | const PROTOCOL = "http:"; 172 | const PROTOCOL_AND_HOST = `${PROTOCOL}//host`; 173 | 174 | /** 175 | * Make it easy to create small utilities that tweak a URL's path. 176 | */ 177 | function createSafeHandler(cb) { 178 | return input => { 179 | const type = getURLType(input); 180 | const base = buildSafeBase(input); 181 | const url = new URL(input, base); 182 | 183 | cb(url); 184 | 185 | const result = url.toString(); 186 | 187 | if (type === "absolute") { 188 | return result; 189 | } else if (type === "scheme-relative") { 190 | return result.slice(PROTOCOL.length); 191 | } else if (type === "path-absolute") { 192 | return result.slice(PROTOCOL_AND_HOST.length); 193 | } 194 | 195 | // This assumes that the callback will only change 196 | // the path, search and hash values. 197 | return computeRelativeURL(base, result); 198 | }; 199 | } 200 | 201 | function withBase(url, base) { 202 | return new URL(url, base).toString(); 203 | } 204 | 205 | function buildUniqueSegment(prefix, str) { 206 | let id = 0; 207 | do { 208 | const ident = prefix + id++; 209 | if (str.indexOf(ident) === -1) return ident; 210 | } while (true); 211 | } 212 | 213 | function buildSafeBase(str) { 214 | const maxDotParts = str.split("..").length - 1; 215 | 216 | // If we used a segment that also existed in `str`, then we would be unable 217 | // to compute relative paths. For example, if `segment` were just "a": 218 | // 219 | // const url = "../../a/" 220 | // const base = buildSafeBase(url); // http://host/a/a/ 221 | // const joined = "http://host/a/"; 222 | // const result = relative(base, joined); 223 | // 224 | // Expected: "../../a/"; 225 | // Actual: "a/" 226 | // 227 | const segment = buildUniqueSegment("p", str); 228 | 229 | let base = `${PROTOCOL_AND_HOST}/`; 230 | for (let i = 0; i < maxDotParts; i++) { 231 | base += `${segment}/`; 232 | } 233 | return base; 234 | } 235 | 236 | const ABSOLUTE_SCHEME = /^[A-Za-z0-9\+\-\.]+:/; 237 | function getURLType(url) { 238 | if (url[0] === "/") { 239 | if (url[1] === "/") return "scheme-relative"; 240 | return "path-absolute"; 241 | } 242 | 243 | return ABSOLUTE_SCHEME.test(url) ? "absolute" : "path-relative"; 244 | } 245 | 246 | /** 247 | * Given two URLs that are assumed to be on the same 248 | * protocol/host/user/password build a relative URL from the 249 | * path, params, and hash values. 250 | * 251 | * @param rootURL The root URL that the target will be relative to. 252 | * @param targetURL The target that the relative URL points to. 253 | * @return A rootURL-relative, normalized URL value. 254 | */ 255 | function computeRelativeURL(rootURL, targetURL) { 256 | if (typeof rootURL === "string") rootURL = new URL(rootURL); 257 | if (typeof targetURL === "string") targetURL = new URL(targetURL); 258 | 259 | const targetParts = targetURL.pathname.split("/"); 260 | const rootParts = rootURL.pathname.split("/"); 261 | 262 | // If we've got a URL path ending with a "/", we remove it since we'd 263 | // otherwise be relative to the wrong location. 264 | if (rootParts.length > 0 && !rootParts[rootParts.length - 1]) { 265 | rootParts.pop(); 266 | } 267 | 268 | while ( 269 | targetParts.length > 0 && 270 | rootParts.length > 0 && 271 | targetParts[0] === rootParts[0] 272 | ) { 273 | targetParts.shift(); 274 | rootParts.shift(); 275 | } 276 | 277 | const relativePath = rootParts 278 | .map(() => "..") 279 | .concat(targetParts) 280 | .join("/"); 281 | 282 | return relativePath + targetURL.search + targetURL.hash; 283 | } 284 | 285 | /** 286 | * Given a URL, ensure that it is treated as a directory URL. 287 | * 288 | * @param url 289 | * @return A normalized URL value. 290 | */ 291 | const ensureDirectory = createSafeHandler(url => { 292 | url.pathname = url.pathname.replace(/\/?$/, "/"); 293 | }); 294 | 295 | /** 296 | * Given a URL, strip off any filename if one is present. 297 | * 298 | * @param url 299 | * @return A normalized URL value. 300 | */ 301 | const trimFilename = createSafeHandler(url => { 302 | url.href = new URL(".", url.toString()).toString(); 303 | }); 304 | 305 | /** 306 | * Normalize a given URL. 307 | * * Convert backslashes. 308 | * * Remove any ".." and "." segments. 309 | * 310 | * @param url 311 | * @return A normalized URL value. 312 | */ 313 | const normalize = createSafeHandler(url => {}); 314 | exports.normalize = normalize; 315 | 316 | /** 317 | * Joins two paths/URLs. 318 | * 319 | * All returned URLs will be normalized. 320 | * 321 | * @param aRoot The root path or URL. Assumed to reference a directory. 322 | * @param aPath The path or URL to be joined with the root. 323 | * @return A joined and normalized URL value. 324 | */ 325 | function join(aRoot, aPath) { 326 | const pathType = getURLType(aPath); 327 | const rootType = getURLType(aRoot); 328 | 329 | aRoot = ensureDirectory(aRoot); 330 | 331 | if (pathType === "absolute") { 332 | return withBase(aPath, undefined); 333 | } 334 | if (rootType === "absolute") { 335 | return withBase(aPath, aRoot); 336 | } 337 | 338 | if (pathType === "scheme-relative") { 339 | return normalize(aPath); 340 | } 341 | if (rootType === "scheme-relative") { 342 | return withBase(aPath, withBase(aRoot, PROTOCOL_AND_HOST)).slice( 343 | PROTOCOL.length 344 | ); 345 | } 346 | 347 | if (pathType === "path-absolute") { 348 | return normalize(aPath); 349 | } 350 | if (rootType === "path-absolute") { 351 | return withBase(aPath, withBase(aRoot, PROTOCOL_AND_HOST)).slice( 352 | PROTOCOL_AND_HOST.length 353 | ); 354 | } 355 | 356 | const base = buildSafeBase(aPath + aRoot); 357 | const newPath = withBase(aPath, withBase(aRoot, base)); 358 | return computeRelativeURL(base, newPath); 359 | } 360 | exports.join = join; 361 | 362 | /** 363 | * Make a path relative to a URL or another path. If returning a 364 | * relative URL is not possible, the original target will be returned. 365 | * All returned URLs will be normalized. 366 | * 367 | * @param aRoot The root path or URL. 368 | * @param aPath The path or URL to be made relative to aRoot. 369 | * @return A rootURL-relative (if possible), normalized URL value. 370 | */ 371 | function relative(rootURL, targetURL) { 372 | const result = relativeIfPossible(rootURL, targetURL); 373 | 374 | return typeof result === "string" ? result : normalize(targetURL); 375 | } 376 | exports.relative = relative; 377 | 378 | function relativeIfPossible(rootURL, targetURL) { 379 | const urlType = getURLType(rootURL); 380 | if (urlType !== getURLType(targetURL)) { 381 | return null; 382 | } 383 | 384 | const base = buildSafeBase(rootURL + targetURL); 385 | const root = new URL(rootURL, base); 386 | const target = new URL(targetURL, base); 387 | 388 | try { 389 | new URL("", target.toString()); 390 | } catch (err) { 391 | // Bail if the URL doesn't support things being relative to it, 392 | // For example, data: and blob: URLs. 393 | return null; 394 | } 395 | 396 | if ( 397 | target.protocol !== root.protocol || 398 | target.user !== root.user || 399 | target.password !== root.password || 400 | target.hostname !== root.hostname || 401 | target.port !== root.port 402 | ) { 403 | return null; 404 | } 405 | 406 | return computeRelativeURL(root, target); 407 | } 408 | 409 | /** 410 | * Compute the URL of a source given the the source root, the source's 411 | * URL, and the source map's URL. 412 | */ 413 | function computeSourceURL(sourceRoot, sourceURL, sourceMapURL) { 414 | // The source map spec states that "sourceRoot" and "sources" entries are to be appended. While 415 | // that is a little vague, implementations have generally interpreted that as joining the 416 | // URLs with a `/` between then, assuming the "sourceRoot" doesn't already end with one. 417 | // For example, 418 | // 419 | // sourceRoot: "some-dir", 420 | // sources: ["/some-path.js"] 421 | // 422 | // and 423 | // 424 | // sourceRoot: "some-dir/", 425 | // sources: ["/some-path.js"] 426 | // 427 | // must behave as "some-dir/some-path.js". 428 | // 429 | // With this library's the transition to a more URL-focused implementation, that behavior is 430 | // preserved here. To acheive that, we trim the "/" from absolute-path when a sourceRoot value 431 | // is present in order to make the sources entries behave as if they are relative to the 432 | // "sourceRoot", as they would have if the two strings were simply concated. 433 | if (sourceRoot && getURLType(sourceURL) === "path-absolute") { 434 | sourceURL = sourceURL.replace(/^\//, ""); 435 | } 436 | 437 | let url = normalize(sourceURL || ""); 438 | 439 | // Parsing URLs can be expensive, so we only perform these joins when needed. 440 | if (sourceRoot) url = join(sourceRoot, url); 441 | if (sourceMapURL) url = join(trimFilename(sourceMapURL), url); 442 | return url; 443 | } 444 | exports.computeSourceURL = computeSourceURL; 445 | -------------------------------------------------------------------------------- /lib/wasm.js: -------------------------------------------------------------------------------- 1 | const readWasm = require("../lib/read-wasm"); 2 | 3 | /** 4 | * Provide the JIT with a nice shape / hidden class. 5 | */ 6 | function Mapping() { 7 | this.generatedLine = 0; 8 | this.generatedColumn = 0; 9 | this.lastGeneratedColumn = null; 10 | this.source = null; 11 | this.originalLine = null; 12 | this.originalColumn = null; 13 | this.name = null; 14 | } 15 | 16 | let cachedWasm = null; 17 | 18 | module.exports = function wasm() { 19 | if (cachedWasm) { 20 | return cachedWasm; 21 | } 22 | 23 | const callbackStack = []; 24 | 25 | cachedWasm = readWasm() 26 | .then(buffer => { 27 | return WebAssembly.instantiate(buffer, { 28 | env: { 29 | mapping_callback( 30 | generatedLine, 31 | generatedColumn, 32 | 33 | hasLastGeneratedColumn, 34 | lastGeneratedColumn, 35 | 36 | hasOriginal, 37 | source, 38 | originalLine, 39 | originalColumn, 40 | 41 | hasName, 42 | name 43 | ) { 44 | const mapping = new Mapping(); 45 | // JS uses 1-based line numbers, wasm uses 0-based. 46 | mapping.generatedLine = generatedLine + 1; 47 | mapping.generatedColumn = generatedColumn; 48 | 49 | if (hasLastGeneratedColumn) { 50 | // JS uses inclusive last generated column, wasm uses exclusive. 51 | mapping.lastGeneratedColumn = lastGeneratedColumn - 1; 52 | } 53 | 54 | if (hasOriginal) { 55 | mapping.source = source; 56 | // JS uses 1-based line numbers, wasm uses 0-based. 57 | mapping.originalLine = originalLine + 1; 58 | mapping.originalColumn = originalColumn; 59 | 60 | if (hasName) { 61 | mapping.name = name; 62 | } 63 | } 64 | 65 | callbackStack[callbackStack.length - 1](mapping); 66 | }, 67 | 68 | start_all_generated_locations_for() { 69 | console.time("all_generated_locations_for"); 70 | }, 71 | end_all_generated_locations_for() { 72 | console.timeEnd("all_generated_locations_for"); 73 | }, 74 | 75 | start_compute_column_spans() { 76 | console.time("compute_column_spans"); 77 | }, 78 | end_compute_column_spans() { 79 | console.timeEnd("compute_column_spans"); 80 | }, 81 | 82 | start_generated_location_for() { 83 | console.time("generated_location_for"); 84 | }, 85 | end_generated_location_for() { 86 | console.timeEnd("generated_location_for"); 87 | }, 88 | 89 | start_original_location_for() { 90 | console.time("original_location_for"); 91 | }, 92 | end_original_location_for() { 93 | console.timeEnd("original_location_for"); 94 | }, 95 | 96 | start_parse_mappings() { 97 | console.time("parse_mappings"); 98 | }, 99 | end_parse_mappings() { 100 | console.timeEnd("parse_mappings"); 101 | }, 102 | 103 | start_sort_by_generated_location() { 104 | console.time("sort_by_generated_location"); 105 | }, 106 | end_sort_by_generated_location() { 107 | console.timeEnd("sort_by_generated_location"); 108 | }, 109 | 110 | start_sort_by_original_location() { 111 | console.time("sort_by_original_location"); 112 | }, 113 | end_sort_by_original_location() { 114 | console.timeEnd("sort_by_original_location"); 115 | }, 116 | }, 117 | }); 118 | }) 119 | .then(Wasm => { 120 | return { 121 | exports: Wasm.instance.exports, 122 | withMappingCallback: (mappingCallback, f) => { 123 | callbackStack.push(mappingCallback); 124 | try { 125 | f(); 126 | } finally { 127 | callbackStack.pop(); 128 | } 129 | }, 130 | }; 131 | }) 132 | .then(null, e => { 133 | cachedWasm = null; 134 | throw e; 135 | }); 136 | 137 | return cachedWasm; 138 | }; 139 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "source-map", 3 | "description": "Generates and consumes source maps", 4 | "version": "0.8.0-beta.0", 5 | "homepage": "https://github.com/mozilla/source-map", 6 | "author": "Nick Fitzgerald ", 7 | "contributors": [ 8 | "Tobias Koppers ", 9 | "Duncan Beevers ", 10 | "Stephen Crane ", 11 | "Ryan Seddon ", 12 | "Miles Elam ", 13 | "Mihai Bazon ", 14 | "Michael Ficarra ", 15 | "Todd Wolfson ", 16 | "Alexander Solovyov ", 17 | "Felix Gnass ", 18 | "Conrad Irwin ", 19 | "usrbincc ", 20 | "David Glasser ", 21 | "Chase Douglas ", 22 | "Evan Wallace ", 23 | "Heather Arthur ", 24 | "Hugh Kennedy ", 25 | "David Glasser ", 26 | "Simon Lydell ", 27 | "Jmeas Smith ", 28 | "Michael Z Goddard ", 29 | "azu ", 30 | "John Gozde ", 31 | "Adam Kirkton ", 32 | "Chris Montgomery ", 33 | "J. Ryan Stinnett ", 34 | "Jack Herrington ", 35 | "Chris Truter ", 36 | "Daniel Espeset ", 37 | "Jamie Wong ", 38 | "Eddy Bruël ", 39 | "Hawken Rives ", 40 | "Gilad Peleg ", 41 | "djchie ", 42 | "Gary Ye ", 43 | "Nicolas Lalevée " 44 | ], 45 | "repository": { 46 | "type": "git", 47 | "url": "http://github.com/mozilla/source-map.git" 48 | }, 49 | "main": "./source-map.js", 50 | "types": "./source-map.d.ts", 51 | "browser": { 52 | "./lib/url.js": "./lib/url-browser.js", 53 | "./lib/read-wasm.js": "./lib/read-wasm-browser.js" 54 | }, 55 | "files": [ 56 | "source-map.js", 57 | "source-map.d.ts", 58 | "lib/" 59 | ], 60 | "publishConfig": { 61 | "tag": "next" 62 | }, 63 | "engines": { 64 | "node": ">= 12" 65 | }, 66 | "license": "BSD-3-Clause", 67 | "scripts": { 68 | "lint": "eslint --fix *.js lib/ test/ --ignore-pattern 'test/source-map-tests/**'", 69 | "test": "git submodule update --init --recursive; node test/run-tests.js", 70 | "coverage": "c8 --reporter=text --reporter=html npm test", 71 | "prettier": "prettier --write .", 72 | "clean": "rm -rf coverage", 73 | "toc": "doctoc --github --notitle README.md CONTRIBUTING.md" 74 | }, 75 | "devDependencies": { 76 | "c8": "^7.12.0", 77 | "doctoc": "^2.2.1", 78 | "eslint": "^8.24.0", 79 | "eslint-config-prettier": "^8.5.0", 80 | "prettier": "^2.7.1" 81 | }, 82 | "dependencies": { 83 | "whatwg-url": "^7.0.0" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /source-map.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for source-map 0.7 2 | // Project: https://github.com/mozilla/source-map 3 | // Definitions by: Morten Houston Ludvigsen , 4 | // Ron Buckton , 5 | // John Vilk 6 | // Definitions: https://github.com/mozilla/source-map 7 | export type SourceMapUrl = string; 8 | 9 | export interface StartOfSourceMap { 10 | file?: string; 11 | sourceRoot?: string; 12 | skipValidation?: boolean; 13 | } 14 | 15 | export interface RawSourceMap { 16 | version: number; 17 | sources: string[]; 18 | names: string[]; 19 | sourceRoot?: string; 20 | sourcesContent?: string[]; 21 | mappings: string; 22 | file: string; 23 | } 24 | 25 | export interface RawIndexMap extends StartOfSourceMap { 26 | version: number; 27 | sections: RawSection[]; 28 | } 29 | 30 | export interface RawSection { 31 | offset: Position; 32 | map: RawSourceMap; 33 | } 34 | 35 | export interface Position { 36 | line: number; 37 | column: number; 38 | } 39 | 40 | export interface NullablePosition { 41 | line: number | null; 42 | column: number | null; 43 | lastColumn: number | null; 44 | } 45 | 46 | export interface MappedPosition { 47 | source: string; 48 | line: number; 49 | column: number; 50 | name?: string; 51 | } 52 | 53 | export interface NullableMappedPosition { 54 | source: string | null; 55 | line: number | null; 56 | column: number | null; 57 | name: string | null; 58 | } 59 | 60 | export interface MappingItem { 61 | source: string; 62 | generatedLine: number; 63 | generatedColumn: number; 64 | lastGeneratedColumn: number | null; 65 | originalLine: number; 66 | originalColumn: number; 67 | name: string; 68 | } 69 | 70 | export interface Mapping { 71 | generated: Position; 72 | original: Position; 73 | source: string; 74 | name?: string; 75 | } 76 | 77 | export interface CodeWithSourceMap { 78 | code: string; 79 | map: SourceMapGenerator; 80 | } 81 | 82 | export interface SourceMappings { 83 | "lib/mappings.wasm": SourceMapUrl | ArrayBuffer; 84 | } 85 | 86 | export interface SourceMapConsumer { 87 | /** 88 | * When using SourceMapConsumer outside of node.js, for example on the Web, it 89 | * needs to know from what URL to load lib/mappings.wasm. You must inform it 90 | * by calling initialize before constructing any SourceMapConsumers. 91 | * 92 | * @param mappings an object with the following property: 93 | * - "lib/mappings.wasm": A String containing the URL of the 94 | * lib/mappings.wasm file, or an ArrayBuffer with the contents of 95 | * lib/mappings.wasm. 96 | */ 97 | initialize(mappings: SourceMappings): void; 98 | 99 | /** 100 | * Compute the last column for each generated mapping. The last column is 101 | * inclusive. 102 | */ 103 | computeColumnSpans(): void; 104 | 105 | /** 106 | * Returns the original source, line, and column information for the generated 107 | * source's line and column positions provided. The only argument is an object 108 | * with the following properties: 109 | * 110 | * - line: The line number in the generated source. 111 | * - column: The column number in the generated source. 112 | * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or 113 | * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the 114 | * closest element that is smaller than or greater than the one we are 115 | * searching for, respectively, if the exact element cannot be found. 116 | * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'. 117 | * 118 | * and an object is returned with the following properties: 119 | * 120 | * - source: The original source file, or null. 121 | * - line: The line number in the original source, or null. 122 | * - column: The column number in the original source, or null. 123 | * - name: The original identifier, or null. 124 | */ 125 | originalPositionFor( 126 | generatedPosition: Position & { bias?: number } 127 | ): NullableMappedPosition; 128 | 129 | /** 130 | * Returns the generated line and column information for the original source, 131 | * line, and column positions provided. The only argument is an object with 132 | * the following properties: 133 | * 134 | * - source: The filename of the original source. 135 | * - line: The line number in the original source. 136 | * - column: The column number in the original source. 137 | * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or 138 | * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the 139 | * closest element that is smaller than or greater than the one we are 140 | * searching for, respectively, if the exact element cannot be found. 141 | * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'. 142 | * 143 | * and an object is returned with the following properties: 144 | * 145 | * - line: The line number in the generated source, or null. 146 | * - column: The column number in the generated source, or null. 147 | */ 148 | generatedPositionFor( 149 | originalPosition: MappedPosition & { bias?: number } 150 | ): NullablePosition; 151 | 152 | /** 153 | * Returns all generated line and column information for the original source, 154 | * line, and column provided. If no column is provided, returns all mappings 155 | * corresponding to a either the line we are searching for or the next 156 | * closest line that has any mappings. Otherwise, returns all mappings 157 | * corresponding to the given line and either the column we are searching for 158 | * or the next closest column that has any offsets. 159 | * 160 | * The only argument is an object with the following properties: 161 | * 162 | * - source: The filename of the original source. 163 | * - line: The line number in the original source. 164 | * - column: Optional. the column number in the original source. 165 | * 166 | * and an array of objects is returned, each with the following properties: 167 | * 168 | * - line: The line number in the generated source, or null. 169 | * - column: The column number in the generated source, or null. 170 | */ 171 | allGeneratedPositionsFor( 172 | originalPosition: MappedPosition 173 | ): NullablePosition[]; 174 | 175 | /** 176 | * Return true if we have the source content for every source in the source 177 | * map, false otherwise. 178 | */ 179 | hasContentsOfAllSources(): boolean; 180 | 181 | /** 182 | * Returns the original source content. The only argument is the url of the 183 | * original source file. Returns null if no original source content is 184 | * available. 185 | */ 186 | sourceContentFor( 187 | source: string, 188 | returnNullOnMissing?: boolean 189 | ): string | null; 190 | 191 | /** 192 | * Iterate over each mapping between an original source/line/column and a 193 | * generated line/column in this source map. 194 | * 195 | * @param callback 196 | * The function that is called with each mapping. 197 | * @param context 198 | * Optional. If specified, this object will be the value of `this` every 199 | * time that `aCallback` is called. 200 | * @param order 201 | * Either `SourceMapConsumer.GENERATED_ORDER` or 202 | * `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to 203 | * iterate over the mappings sorted by the generated file's line/column 204 | * order or the original's source/line/column order, respectively. Defaults to 205 | * `SourceMapConsumer.GENERATED_ORDER`. 206 | */ 207 | eachMapping( 208 | callback: (mapping: MappingItem) => void, 209 | context?: any, 210 | order?: number 211 | ): void; 212 | /** 213 | * Free this source map consumer's associated wasm data that is manually-managed. 214 | * Alternatively, you can use SourceMapConsumer.with to avoid needing to remember to call destroy. 215 | */ 216 | destroy(): void; 217 | } 218 | 219 | export interface SourceMapConsumerConstructor { 220 | prototype: SourceMapConsumer; 221 | 222 | GENERATED_ORDER: number; 223 | ORIGINAL_ORDER: number; 224 | GREATEST_LOWER_BOUND: number; 225 | LEAST_UPPER_BOUND: number; 226 | 227 | new ( 228 | rawSourceMap: RawSourceMap, 229 | sourceMapUrl?: SourceMapUrl 230 | ): Promise; 231 | new ( 232 | rawSourceMap: RawIndexMap, 233 | sourceMapUrl?: SourceMapUrl 234 | ): Promise; 235 | new ( 236 | rawSourceMap: RawSourceMap | RawIndexMap | string, 237 | sourceMapUrl?: SourceMapUrl 238 | ): Promise; 239 | 240 | /** 241 | * Create a BasicSourceMapConsumer from a SourceMapGenerator. 242 | * 243 | * @param sourceMap 244 | * The source map that will be consumed. 245 | */ 246 | fromSourceMap( 247 | sourceMap: SourceMapGenerator, 248 | sourceMapUrl?: SourceMapUrl 249 | ): Promise; 250 | 251 | /** 252 | * Construct a new `SourceMapConsumer` from `rawSourceMap` and `sourceMapUrl` 253 | * (see the `SourceMapConsumer` constructor for details. Then, invoke the `async 254 | * function f(SourceMapConsumer) -> T` with the newly constructed consumer, wait 255 | * for `f` to complete, call `destroy` on the consumer, and return `f`'s return 256 | * value. 257 | * 258 | * You must not use the consumer after `f` completes! 259 | * 260 | * By using `with`, you do not have to remember to manually call `destroy` on 261 | * the consumer, since it will be called automatically once `f` completes. 262 | * 263 | * ```js 264 | * const xSquared = await SourceMapConsumer.with( 265 | * myRawSourceMap, 266 | * null, 267 | * async function (consumer) { 268 | * // Use `consumer` inside here and don't worry about remembering 269 | * // to call `destroy`. 270 | * 271 | * const x = await whatever(consumer); 272 | * return x * x; 273 | * } 274 | * ); 275 | * 276 | * // You may not use that `consumer` anymore out here; it has 277 | * // been destroyed. But you can use `xSquared`. 278 | * console.log(xSquared); 279 | * ``` 280 | */ 281 | with( 282 | rawSourceMap: RawSourceMap | RawIndexMap | string, 283 | sourceMapUrl: SourceMapUrl | null | undefined, 284 | callback: ( 285 | consumer: BasicSourceMapConsumer | IndexedSourceMapConsumer 286 | ) => Promise | T 287 | ): Promise; 288 | } 289 | 290 | export const SourceMapConsumer: SourceMapConsumerConstructor; 291 | 292 | export interface BasicSourceMapConsumer extends SourceMapConsumer { 293 | file: string; 294 | sourceRoot: string; 295 | sources: string[]; 296 | sourcesContent: string[]; 297 | } 298 | 299 | export interface BasicSourceMapConsumerConstructor { 300 | prototype: BasicSourceMapConsumer; 301 | 302 | new (rawSourceMap: RawSourceMap | string): Promise; 303 | 304 | /** 305 | * Create a BasicSourceMapConsumer from a SourceMapGenerator. 306 | * 307 | * @param sourceMap 308 | * The source map that will be consumed. 309 | */ 310 | fromSourceMap(sourceMap: SourceMapGenerator): Promise; 311 | } 312 | 313 | export const BasicSourceMapConsumer: BasicSourceMapConsumerConstructor; 314 | 315 | export interface IndexedSourceMapConsumer extends SourceMapConsumer { 316 | sources: string[]; 317 | } 318 | 319 | export interface IndexedSourceMapConsumerConstructor { 320 | prototype: IndexedSourceMapConsumer; 321 | 322 | new (rawSourceMap: RawIndexMap | string): Promise; 323 | } 324 | 325 | export const IndexedSourceMapConsumer: IndexedSourceMapConsumerConstructor; 326 | 327 | export class SourceMapGenerator { 328 | constructor(startOfSourceMap?: StartOfSourceMap); 329 | 330 | /** 331 | * Creates a new SourceMapGenerator based on a SourceMapConsumer 332 | * 333 | * @param sourceMapConsumer The SourceMap. 334 | */ 335 | static fromSourceMap( 336 | sourceMapConsumer: SourceMapConsumer 337 | ): SourceMapGenerator; 338 | 339 | /** 340 | * Add a single mapping from original source line and column to the generated 341 | * source's line and column for this source map being created. The mapping 342 | * object should have the following properties: 343 | * 344 | * - generated: An object with the generated line and column positions. 345 | * - original: An object with the original line and column positions. 346 | * - source: The original source file (relative to the sourceRoot). 347 | * - name: An optional original token name for this mapping. 348 | */ 349 | addMapping(mapping: Mapping): void; 350 | 351 | /** 352 | * Set the source content for a source file. 353 | */ 354 | setSourceContent(sourceFile: string, sourceContent: string): void; 355 | 356 | /** 357 | * Applies the mappings of a sub-source-map for a specific source file to the 358 | * source map being generated. Each mapping to the supplied source file is 359 | * rewritten using the supplied source map. Note: The resolution for the 360 | * resulting mappings is the minimium of this map and the supplied map. 361 | * 362 | * @param sourceMapConsumer The source map to be applied. 363 | * @param sourceFile Optional. The filename of the source file. 364 | * If omitted, SourceMapConsumer's file property will be used. 365 | * @param sourceMapPath Optional. The dirname of the path to the source map 366 | * to be applied. If relative, it is relative to the SourceMapConsumer. 367 | * This parameter is needed when the two source maps aren't in the same 368 | * directory, and the source map to be applied contains relative source 369 | * paths. If so, those relative source paths need to be rewritten 370 | * relative to the SourceMapGenerator. 371 | */ 372 | applySourceMap( 373 | sourceMapConsumer: SourceMapConsumer, 374 | sourceFile?: string, 375 | sourceMapPath?: string 376 | ): void; 377 | 378 | toString(): string; 379 | 380 | toJSON(): RawSourceMap; 381 | } 382 | 383 | export class SourceNode { 384 | children: SourceNode[]; 385 | sourceContents: any; 386 | line: number; 387 | column: number; 388 | source: string; 389 | name: string; 390 | 391 | constructor(); 392 | constructor( 393 | line: number | null, 394 | column: number | null, 395 | source: string | null, 396 | chunks?: Array | SourceNode | string, 397 | name?: string 398 | ); 399 | 400 | static fromStringWithSourceMap( 401 | code: string, 402 | sourceMapConsumer: SourceMapConsumer, 403 | relativePath?: string 404 | ): SourceNode; 405 | 406 | add(chunk: Array | SourceNode | string): SourceNode; 407 | 408 | prepend(chunk: Array | SourceNode | string): SourceNode; 409 | 410 | setSourceContent(sourceFile: string, sourceContent: string): void; 411 | 412 | walk(fn: (chunk: string, mapping: MappedPosition) => void): void; 413 | 414 | walkSourceContents(fn: (file: string, content: string) => void): void; 415 | 416 | join(sep: string): SourceNode; 417 | 418 | replaceRight(pattern: string, replacement: string): SourceNode; 419 | 420 | toString(): string; 421 | 422 | toStringWithSourceMap(startOfSourceMap?: StartOfSourceMap): CodeWithSourceMap; 423 | } 424 | -------------------------------------------------------------------------------- /source-map.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2009-2011 Mozilla Foundation and contributors 3 | * Licensed under the New BSD license. See LICENSE or: 4 | * http://opensource.org/licenses/BSD-3-Clause 5 | */ 6 | exports.SourceMapGenerator = 7 | require("./lib/source-map-generator").SourceMapGenerator; 8 | exports.SourceMapConsumer = 9 | require("./lib/source-map-consumer").SourceMapConsumer; 10 | exports.SourceNode = require("./lib/source-node").SourceNode; 11 | -------------------------------------------------------------------------------- /test/run-tests.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* -*- Mode: js; js-indent-level: 2; -*- */ 3 | /* 4 | * Copyright 2011 Mozilla Foundation and contributors 5 | * Licensed under the New BSD license. See LICENSE or: 6 | * http://opensource.org/licenses/BSD-3-Clause 7 | */ 8 | const assert = require("assert"); 9 | const fs = require("fs"); 10 | const path = require("path"); 11 | 12 | async function run(tests) { 13 | let total = 0; 14 | let passed = 0; 15 | 16 | for (let i = 0; i < tests.length; i++) { 17 | for (const k in tests[i].testCase) { 18 | if (/^test/.test(k)) { 19 | total++; 20 | try { 21 | await tests[i].testCase[k](assert); 22 | passed++; 23 | } catch (e) { 24 | console.log("FAILED " + tests[i].name + ": " + k + "!"); 25 | console.log(e.stack); 26 | } 27 | } 28 | } 29 | } 30 | 31 | console.log(""); 32 | console.log(passed + " / " + total + " tests passed."); 33 | console.log(""); 34 | 35 | return total - passed; 36 | } 37 | 38 | function isTestFile(f) { 39 | const testToRun = process.argv[2]; 40 | return testToRun ? path.basename(testToRun) === f : /^test\-.*?\.js/.test(f); 41 | } 42 | 43 | function toRelativeModule(f) { 44 | return "./" + f.replace(/\.js$/, ""); 45 | } 46 | 47 | const requires = fs 48 | .readdirSync(__dirname) 49 | .filter(isTestFile) 50 | .map(toRelativeModule); 51 | 52 | run( 53 | requires.map(require).map(function (mod, i) { 54 | return { 55 | name: requires[i], 56 | testCase: mod, 57 | }; 58 | }) 59 | ).then( 60 | code => process.exit(code), 61 | e => { 62 | console.error(e); 63 | process.exit(1); 64 | } 65 | ); 66 | -------------------------------------------------------------------------------- /test/test-api.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: js; js-indent-level: 2; -*- */ 2 | /* 3 | * Copyright 2012 Mozilla Foundation and contributors 4 | * Licensed under the New BSD license. See LICENSE or: 5 | * http://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | const sourceMap = require("../source-map"); 9 | 10 | exports["test that the api is properly exposed in the top level"] = function ( 11 | assert 12 | ) { 13 | assert.equal(typeof sourceMap.SourceMapGenerator, "function"); 14 | assert.equal(typeof sourceMap.SourceMapConsumer, "function"); 15 | assert.equal(typeof sourceMap.SourceNode, "function"); 16 | }; 17 | -------------------------------------------------------------------------------- /test/test-array-set.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: js; js-indent-level: 2; -*- */ 2 | /* 3 | * Copyright 2011 Mozilla Foundation and contributors 4 | * Licensed under the New BSD license. See LICENSE or: 5 | * http://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | const ArraySet = require("../lib/array-set").ArraySet; 9 | 10 | function makeTestSet() { 11 | const set = new ArraySet(); 12 | for (let i = 0; i < 100; i++) { 13 | set.add(String(i)); 14 | } 15 | return set; 16 | } 17 | 18 | exports["test .has() membership"] = function (assert) { 19 | const set = makeTestSet(); 20 | for (let i = 0; i < 100; i++) { 21 | assert.ok(set.has(String(i))); 22 | } 23 | }; 24 | 25 | exports["test .indexOf() elements"] = function (assert) { 26 | const set = makeTestSet(); 27 | for (let i = 0; i < 100; i++) { 28 | assert.strictEqual(set.indexOf(String(i)), i); 29 | } 30 | }; 31 | 32 | exports["test .at() indexing"] = function (assert) { 33 | const set = makeTestSet(); 34 | for (let i = 0; i < 100; i++) { 35 | assert.strictEqual(set.at(i), String(i)); 36 | } 37 | }; 38 | 39 | exports["test creating from an array"] = function (assert) { 40 | const set = ArraySet.fromArray([ 41 | "foo", 42 | "bar", 43 | "baz", 44 | "quux", 45 | "hasOwnProperty", 46 | ]); 47 | 48 | assert.ok(set.has("foo")); 49 | assert.ok(set.has("bar")); 50 | assert.ok(set.has("baz")); 51 | assert.ok(set.has("quux")); 52 | assert.ok(set.has("hasOwnProperty")); 53 | 54 | assert.strictEqual(set.indexOf("foo"), 0); 55 | assert.strictEqual(set.indexOf("bar"), 1); 56 | assert.strictEqual(set.indexOf("baz"), 2); 57 | assert.strictEqual(set.indexOf("quux"), 3); 58 | 59 | assert.strictEqual(set.at(0), "foo"); 60 | assert.strictEqual(set.at(1), "bar"); 61 | assert.strictEqual(set.at(2), "baz"); 62 | assert.strictEqual(set.at(3), "quux"); 63 | }; 64 | 65 | exports["test that you can add __proto__; see github issue #30"] = function ( 66 | assert 67 | ) { 68 | const set = new ArraySet(); 69 | set.add("__proto__"); 70 | assert.ok(set.has("__proto__")); 71 | assert.strictEqual(set.at(0), "__proto__"); 72 | assert.strictEqual(set.indexOf("__proto__"), 0); 73 | }; 74 | 75 | exports["test .fromArray() with duplicates"] = function (assert) { 76 | let set = ArraySet.fromArray(["foo", "foo"]); 77 | assert.ok(set.has("foo")); 78 | assert.strictEqual(set.at(0), "foo"); 79 | assert.strictEqual(set.indexOf("foo"), 0); 80 | assert.strictEqual(set.toArray().length, 1); 81 | 82 | set = ArraySet.fromArray(["foo", "foo"], true); 83 | assert.ok(set.has("foo")); 84 | assert.strictEqual(set.at(0), "foo"); 85 | assert.strictEqual(set.at(1), "foo"); 86 | assert.strictEqual(set.indexOf("foo"), 0); 87 | assert.strictEqual(set.toArray().length, 2); 88 | }; 89 | 90 | exports["test .add() with duplicates"] = function (assert) { 91 | const set = new ArraySet(); 92 | set.add("foo"); 93 | 94 | set.add("foo"); 95 | assert.ok(set.has("foo")); 96 | assert.strictEqual(set.at(0), "foo"); 97 | assert.strictEqual(set.indexOf("foo"), 0); 98 | assert.strictEqual(set.toArray().length, 1); 99 | 100 | set.add("foo", true); 101 | assert.ok(set.has("foo")); 102 | assert.strictEqual(set.at(0), "foo"); 103 | assert.strictEqual(set.at(1), "foo"); 104 | assert.strictEqual(set.indexOf("foo"), 0); 105 | assert.strictEqual(set.toArray().length, 2); 106 | }; 107 | 108 | exports["test .size()"] = function (assert) { 109 | const set = new ArraySet(); 110 | set.add("foo"); 111 | set.add("bar"); 112 | set.add("baz"); 113 | assert.strictEqual(set.size(), 3); 114 | }; 115 | 116 | exports["test .size() with disallowed duplicates"] = function (assert) { 117 | const set = new ArraySet(); 118 | 119 | set.add("foo"); 120 | set.add("foo"); 121 | 122 | set.add("bar"); 123 | set.add("bar"); 124 | 125 | set.add("baz"); 126 | set.add("baz"); 127 | 128 | assert.strictEqual(set.size(), 3); 129 | }; 130 | 131 | exports["test .size() with allowed duplicates"] = function (assert) { 132 | const set = new ArraySet(); 133 | 134 | set.add("foo"); 135 | set.add("foo", true); 136 | 137 | set.add("bar"); 138 | set.add("bar", true); 139 | 140 | set.add("baz"); 141 | set.add("baz", true); 142 | 143 | assert.strictEqual(set.size(), 3); 144 | }; 145 | -------------------------------------------------------------------------------- /test/test-base64.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: js; js-indent-level: 2; -*- */ 2 | /* 3 | * Copyright 2011 Mozilla Foundation and contributors 4 | * Licensed under the New BSD license. See LICENSE or: 5 | * http://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | const base64 = require("../lib/base64"); 9 | 10 | exports["test out of range encoding"] = function (assert) { 11 | assert.throws(function () { 12 | base64.encode(-1); 13 | }, /Must be between 0 and 63/); 14 | assert.throws(function () { 15 | base64.encode(64); 16 | }, /Must be between 0 and 63/); 17 | }; 18 | 19 | exports["test normal encoding and decoding"] = function (assert) { 20 | for (let i = 0; i < 64; i++) { 21 | base64.encode(i); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /test/test-binary-search.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: js; js-indent-level: 2; -*- */ 2 | /* 3 | * Copyright 2011 Mozilla Foundation and contributors 4 | * Licensed under the New BSD license. See LICENSE or: 5 | * http://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | const binarySearch = require("../lib/binary-search"); 9 | 10 | function numberCompare(a, b) { 11 | return a - b; 12 | } 13 | 14 | exports["test too high with default (glb) bias"] = function (assert) { 15 | const needle = 30; 16 | const haystack = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]; 17 | 18 | assert.doesNotThrow(function () { 19 | binarySearch.search(needle, haystack, numberCompare); 20 | }); 21 | 22 | assert.equal( 23 | haystack[binarySearch.search(needle, haystack, numberCompare)], 24 | 20 25 | ); 26 | }; 27 | 28 | exports["test too low with default (glb) bias"] = function (assert) { 29 | const needle = 1; 30 | const haystack = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]; 31 | 32 | assert.doesNotThrow(function () { 33 | binarySearch.search(needle, haystack, numberCompare); 34 | }); 35 | 36 | assert.equal(binarySearch.search(needle, haystack, numberCompare), -1); 37 | }; 38 | 39 | exports["test too high with lub bias"] = function (assert) { 40 | const needle = 30; 41 | const haystack = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]; 42 | 43 | assert.doesNotThrow(function () { 44 | binarySearch.search(needle, haystack, numberCompare); 45 | }); 46 | 47 | assert.equal( 48 | binarySearch.search( 49 | needle, 50 | haystack, 51 | numberCompare, 52 | binarySearch.LEAST_UPPER_BOUND 53 | ), 54 | -1 55 | ); 56 | }; 57 | 58 | exports["test too low with lub bias"] = function (assert) { 59 | const needle = 1; 60 | const haystack = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]; 61 | 62 | assert.doesNotThrow(function () { 63 | binarySearch.search(needle, haystack, numberCompare); 64 | }); 65 | 66 | assert.equal( 67 | haystack[ 68 | binarySearch.search( 69 | needle, 70 | haystack, 71 | numberCompare, 72 | binarySearch.LEAST_UPPER_BOUND 73 | ) 74 | ], 75 | 2 76 | ); 77 | }; 78 | 79 | exports["test exact search"] = function (assert) { 80 | const needle = 4; 81 | const haystack = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]; 82 | 83 | assert.equal( 84 | haystack[binarySearch.search(needle, haystack, numberCompare)], 85 | 4 86 | ); 87 | }; 88 | 89 | exports["test fuzzy search with default (glb) bias"] = function (assert) { 90 | const needle = 19; 91 | const haystack = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]; 92 | 93 | assert.equal( 94 | haystack[binarySearch.search(needle, haystack, numberCompare)], 95 | 18 96 | ); 97 | }; 98 | 99 | exports["test fuzzy search with lub bias"] = function (assert) { 100 | const needle = 19; 101 | const haystack = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]; 102 | 103 | assert.equal( 104 | haystack[ 105 | binarySearch.search( 106 | needle, 107 | haystack, 108 | numberCompare, 109 | binarySearch.LEAST_UPPER_BOUND 110 | ) 111 | ], 112 | 20 113 | ); 114 | }; 115 | 116 | exports["test multiple matches"] = function (assert) { 117 | const needle = 5; 118 | const haystack = [1, 1, 2, 5, 5, 5, 13, 21]; 119 | 120 | assert.equal( 121 | binarySearch.search( 122 | needle, 123 | haystack, 124 | numberCompare, 125 | binarySearch.LEAST_UPPER_BOUND 126 | ), 127 | 3 128 | ); 129 | }; 130 | 131 | exports["test multiple matches at the beginning"] = function (assert) { 132 | const needle = 1; 133 | const haystack = [1, 1, 2, 5, 5, 5, 13, 21]; 134 | 135 | assert.equal( 136 | binarySearch.search( 137 | needle, 138 | haystack, 139 | numberCompare, 140 | binarySearch.LEAST_UPPER_BOUND 141 | ), 142 | 0 143 | ); 144 | }; 145 | -------------------------------------------------------------------------------- /test/test-dog-fooding.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: js; js-indent-level: 2; -*- */ 2 | /* 3 | * Copyright 2011 Mozilla Foundation and contributors 4 | * Licensed under the New BSD license. See LICENSE or: 5 | * http://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | const util = require("./util"); 9 | const SourceMapConsumer = 10 | require("../lib/source-map-consumer").SourceMapConsumer; 11 | const SourceMapGenerator = 12 | require("../lib/source-map-generator").SourceMapGenerator; 13 | 14 | exports["test eating our own dog food"] = async function (assert) { 15 | const smg = new SourceMapGenerator({ 16 | file: "testing.js", 17 | sourceRoot: "/wu/tang", 18 | }); 19 | 20 | smg.addMapping({ 21 | source: "gza.coffee", 22 | original: { line: 1, column: 0 }, 23 | generated: { line: 2, column: 2 }, 24 | }); 25 | 26 | smg.addMapping({ 27 | source: "gza.coffee", 28 | original: { line: 2, column: 0 }, 29 | generated: { line: 3, column: 2 }, 30 | }); 31 | 32 | smg.addMapping({ 33 | source: "gza.coffee", 34 | original: { line: 3, column: 0 }, 35 | generated: { line: 4, column: 2 }, 36 | }); 37 | 38 | smg.addMapping({ 39 | source: "gza.coffee", 40 | original: { line: 4, column: 0 }, 41 | generated: { line: 5, column: 2 }, 42 | }); 43 | 44 | smg.addMapping({ 45 | source: "gza.coffee", 46 | original: { line: 5, column: 10 }, 47 | generated: { line: 6, column: 12 }, 48 | }); 49 | 50 | const smc = await new SourceMapConsumer(smg.toString()); 51 | 52 | // Exact 53 | util.assertMapping( 54 | 2, 55 | 2, 56 | "/wu/tang/gza.coffee", 57 | 1, 58 | 0, 59 | null, 60 | null, 61 | smc, 62 | assert 63 | ); 64 | util.assertMapping( 65 | 3, 66 | 2, 67 | "/wu/tang/gza.coffee", 68 | 2, 69 | 0, 70 | null, 71 | null, 72 | smc, 73 | assert 74 | ); 75 | util.assertMapping( 76 | 4, 77 | 2, 78 | "/wu/tang/gza.coffee", 79 | 3, 80 | 0, 81 | null, 82 | null, 83 | smc, 84 | assert 85 | ); 86 | util.assertMapping( 87 | 5, 88 | 2, 89 | "/wu/tang/gza.coffee", 90 | 4, 91 | 0, 92 | null, 93 | null, 94 | smc, 95 | assert 96 | ); 97 | util.assertMapping( 98 | 6, 99 | 12, 100 | "/wu/tang/gza.coffee", 101 | 5, 102 | 10, 103 | null, 104 | null, 105 | smc, 106 | assert 107 | ); 108 | 109 | // Fuzzy 110 | 111 | // Generated to original with default (glb) bias. 112 | util.assertMapping(2, 0, null, null, null, null, null, smc, assert, true); 113 | util.assertMapping( 114 | 2, 115 | 9, 116 | "/wu/tang/gza.coffee", 117 | 1, 118 | 0, 119 | null, 120 | null, 121 | smc, 122 | assert, 123 | true 124 | ); 125 | util.assertMapping(3, 0, null, null, null, null, null, smc, assert, true); 126 | util.assertMapping( 127 | 3, 128 | 9, 129 | "/wu/tang/gza.coffee", 130 | 2, 131 | 0, 132 | null, 133 | null, 134 | smc, 135 | assert, 136 | true 137 | ); 138 | util.assertMapping(4, 0, null, null, null, null, null, smc, assert, true); 139 | util.assertMapping( 140 | 4, 141 | 9, 142 | "/wu/tang/gza.coffee", 143 | 3, 144 | 0, 145 | null, 146 | null, 147 | smc, 148 | assert, 149 | true 150 | ); 151 | util.assertMapping(5, 0, null, null, null, null, null, smc, assert, true); 152 | util.assertMapping( 153 | 5, 154 | 9, 155 | "/wu/tang/gza.coffee", 156 | 4, 157 | 0, 158 | null, 159 | null, 160 | smc, 161 | assert, 162 | true 163 | ); 164 | util.assertMapping(6, 0, null, null, null, null, null, smc, assert, true); 165 | util.assertMapping(6, 9, null, null, null, null, null, smc, assert, true); 166 | util.assertMapping( 167 | 6, 168 | 13, 169 | "/wu/tang/gza.coffee", 170 | 5, 171 | 10, 172 | null, 173 | null, 174 | smc, 175 | assert, 176 | true 177 | ); 178 | 179 | // Generated to original with lub bias. 180 | util.assertMapping( 181 | 2, 182 | 0, 183 | "/wu/tang/gza.coffee", 184 | 1, 185 | 0, 186 | null, 187 | SourceMapConsumer.LEAST_UPPER_BOUND, 188 | smc, 189 | assert, 190 | true 191 | ); 192 | util.assertMapping( 193 | 2, 194 | 9, 195 | null, 196 | null, 197 | null, 198 | null, 199 | SourceMapConsumer.LEAST_UPPER_BOUND, 200 | smc, 201 | assert, 202 | true 203 | ); 204 | util.assertMapping( 205 | 3, 206 | 0, 207 | "/wu/tang/gza.coffee", 208 | 2, 209 | 0, 210 | null, 211 | SourceMapConsumer.LEAST_UPPER_BOUND, 212 | smc, 213 | assert, 214 | true 215 | ); 216 | util.assertMapping( 217 | 3, 218 | 9, 219 | null, 220 | null, 221 | null, 222 | null, 223 | SourceMapConsumer.LEAST_UPPER_BOUND, 224 | smc, 225 | assert, 226 | true 227 | ); 228 | util.assertMapping( 229 | 4, 230 | 0, 231 | "/wu/tang/gza.coffee", 232 | 3, 233 | 0, 234 | null, 235 | SourceMapConsumer.LEAST_UPPER_BOUND, 236 | smc, 237 | assert, 238 | true 239 | ); 240 | util.assertMapping( 241 | 4, 242 | 9, 243 | null, 244 | null, 245 | null, 246 | null, 247 | SourceMapConsumer.LEAST_UPPER_BOUND, 248 | smc, 249 | assert, 250 | true 251 | ); 252 | util.assertMapping( 253 | 5, 254 | 0, 255 | "/wu/tang/gza.coffee", 256 | 4, 257 | 0, 258 | null, 259 | SourceMapConsumer.LEAST_UPPER_BOUND, 260 | smc, 261 | assert, 262 | true 263 | ); 264 | util.assertMapping( 265 | 5, 266 | 9, 267 | null, 268 | null, 269 | null, 270 | null, 271 | SourceMapConsumer.LEAST_UPPER_BOUND, 272 | smc, 273 | assert, 274 | true 275 | ); 276 | util.assertMapping( 277 | 6, 278 | 0, 279 | "/wu/tang/gza.coffee", 280 | 5, 281 | 10, 282 | null, 283 | SourceMapConsumer.LEAST_UPPER_BOUND, 284 | smc, 285 | assert, 286 | true 287 | ); 288 | util.assertMapping( 289 | 6, 290 | 9, 291 | "/wu/tang/gza.coffee", 292 | 5, 293 | 10, 294 | null, 295 | SourceMapConsumer.LEAST_UPPER_BOUND, 296 | smc, 297 | assert, 298 | true 299 | ); 300 | util.assertMapping( 301 | 6, 302 | 13, 303 | null, 304 | null, 305 | null, 306 | null, 307 | SourceMapConsumer.LEAST_UPPER_BOUND, 308 | smc, 309 | assert, 310 | true 311 | ); 312 | 313 | // Original to generated with default (glb) bias 314 | util.assertMapping( 315 | 2, 316 | 2, 317 | "/wu/tang/gza.coffee", 318 | 1, 319 | 1, 320 | null, 321 | null, 322 | smc, 323 | assert, 324 | null, 325 | true 326 | ); 327 | util.assertMapping( 328 | 3, 329 | 2, 330 | "/wu/tang/gza.coffee", 331 | 2, 332 | 3, 333 | null, 334 | null, 335 | smc, 336 | assert, 337 | null, 338 | true 339 | ); 340 | util.assertMapping( 341 | 4, 342 | 2, 343 | "/wu/tang/gza.coffee", 344 | 3, 345 | 6, 346 | null, 347 | null, 348 | smc, 349 | assert, 350 | null, 351 | true 352 | ); 353 | util.assertMapping( 354 | 5, 355 | 2, 356 | "/wu/tang/gza.coffee", 357 | 4, 358 | 9, 359 | null, 360 | null, 361 | smc, 362 | assert, 363 | null, 364 | true 365 | ); 366 | util.assertMapping( 367 | 5, 368 | 2, 369 | "/wu/tang/gza.coffee", 370 | 5, 371 | 9, 372 | null, 373 | null, 374 | smc, 375 | assert, 376 | null, 377 | true 378 | ); 379 | util.assertMapping( 380 | 6, 381 | 12, 382 | "/wu/tang/gza.coffee", 383 | 6, 384 | 19, 385 | null, 386 | null, 387 | smc, 388 | assert, 389 | null, 390 | true 391 | ); 392 | 393 | // Original to generated with lub bias. 394 | util.assertMapping( 395 | 3, 396 | 2, 397 | "/wu/tang/gza.coffee", 398 | 1, 399 | 1, 400 | null, 401 | SourceMapConsumer.LEAST_UPPER_BOUND, 402 | smc, 403 | assert, 404 | null, 405 | true 406 | ); 407 | util.assertMapping( 408 | 4, 409 | 2, 410 | "/wu/tang/gza.coffee", 411 | 2, 412 | 3, 413 | null, 414 | SourceMapConsumer.LEAST_UPPER_BOUND, 415 | smc, 416 | assert, 417 | null, 418 | true 419 | ); 420 | util.assertMapping( 421 | 5, 422 | 2, 423 | "/wu/tang/gza.coffee", 424 | 3, 425 | 6, 426 | null, 427 | SourceMapConsumer.LEAST_UPPER_BOUND, 428 | smc, 429 | assert, 430 | null, 431 | true 432 | ); 433 | util.assertMapping( 434 | 6, 435 | 12, 436 | "/wu/tang/gza.coffee", 437 | 4, 438 | 9, 439 | null, 440 | SourceMapConsumer.LEAST_UPPER_BOUND, 441 | smc, 442 | assert, 443 | null, 444 | true 445 | ); 446 | util.assertMapping( 447 | 6, 448 | 12, 449 | "/wu/tang/gza.coffee", 450 | 5, 451 | 9, 452 | null, 453 | SourceMapConsumer.LEAST_UPPER_BOUND, 454 | smc, 455 | assert, 456 | null, 457 | true 458 | ); 459 | util.assertMapping( 460 | null, 461 | null, 462 | "/wu/tang/gza.coffee", 463 | 6, 464 | 19, 465 | null, 466 | SourceMapConsumer.LEAST_UPPER_BOUND, 467 | smc, 468 | assert, 469 | null, 470 | true 471 | ); 472 | 473 | smc.destroy(); 474 | }; 475 | -------------------------------------------------------------------------------- /test/test-nested-consumer-usage.js: -------------------------------------------------------------------------------- 1 | const { SourceMapConsumer, SourceMapGenerator } = require("../"); 2 | 3 | const TS_MAP = { 4 | version: 3, 5 | file: "blah.js", 6 | sourceRoot: "", 7 | sources: ["blah.tsx"], 8 | names: [], 9 | mappings: 10 | ";;AAKA;IACE,MAAM,CAAC,EAAC,MAAM,EAAE,SAAS,EAAC,CAAC;AAC7B,CAAC;AAFD,yBAEC", 11 | sourcesContent: [ 12 | "\ntype Cheese = {\n readonly cheese: string\n}\n\nexport default function Cheese(): Cheese {\n" + 13 | " return {cheese: 'stilton'};\n}\n", 14 | ], 15 | }; 16 | 17 | const BABEL_MAP = { 18 | version: 3, 19 | sources: ["blah.tsx"], 20 | names: [ 21 | "Object", 22 | "defineProperty", 23 | "exports", 24 | "value", 25 | "Cheese", 26 | "cheese", 27 | "default", 28 | ], 29 | mappings: 30 | // eslint-disable-next-line 31 | "AAAA;;AACAA,OAAOC,cAAP,CAAsBC,OAAtB,EAA+B,YAA/B,EAA6C,EAAEC,OAAO,IAAT,EAA7C;AACA,SAASC,MAAT,GAAkB;AACd,WAAO,EAAEC,QAAQ,SAAV,EAAP;AACH;AACDH,QAAQI,OAAR,GAAkBF,MAAlB", 32 | sourcesContent: [ 33 | '"use strict";\nObject.defineProperty(exports, "__esModule", { value: true });\nfunction Cheese() {\n' + 34 | " return { cheese: 'stilton' };\n}\nexports.default = Cheese;\n//# sourceMappingURL=blah.js.map", 35 | ], 36 | }; 37 | 38 | async function composeSourceMaps(tsMap, babelMap, tsFileName) { 39 | const tsConsumer = await new SourceMapConsumer(tsMap); 40 | const babelConsumer = await new SourceMapConsumer(babelMap); 41 | const map = new SourceMapGenerator(); 42 | babelConsumer.eachMapping( 43 | ({ 44 | source, 45 | generatedLine, 46 | generatedColumn, 47 | originalLine, 48 | originalColumn, 49 | name, 50 | }) => { 51 | if (originalLine) { 52 | const original = tsConsumer.originalPositionFor({ 53 | line: originalLine, 54 | column: originalColumn, 55 | }); 56 | if (original.line) { 57 | map.addMapping({ 58 | generated: { 59 | line: generatedLine, 60 | column: generatedColumn, 61 | }, 62 | original: { 63 | line: original.line, 64 | column: original.column, 65 | }, 66 | source: tsFileName, 67 | name, 68 | }); 69 | } 70 | } 71 | } 72 | ); 73 | return map.toJSON(); 74 | } 75 | 76 | exports["test nested consumer usage"] = async function (assert) { 77 | await composeSourceMaps(TS_MAP, BABEL_MAP, "blah.tsx"); 78 | }; 79 | -------------------------------------------------------------------------------- /test/test-spec-tests.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: js; js-indent-level: 2; -*- */ 2 | /* 3 | * Copyright 2024 Mozilla Foundation and contributors 4 | * Licensed under the New BSD license. See LICENSE or: 5 | * http://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | const fs = require("fs").promises; 9 | const SourceMapConsumer = 10 | require("../lib/source-map-consumer").SourceMapConsumer; 11 | 12 | const sourceMapSpecTests = require("./source-map-tests/source-map-spec-tests.json"); 13 | 14 | async function readJSON(path) { 15 | const file = await fs.open(require.resolve(path)); 16 | const json = JSON.parse(await file.readFile()); 17 | file.close(); 18 | return json; 19 | } 20 | 21 | // Known failures due to intentional implementation choices or due to bugs. 22 | const skippedTests = [ 23 | // Versions are explicitly checked a bit loosely. 24 | "versionNumericString", 25 | // Stricter sources array checking isn't implemented. 26 | "sourcesNotStringOrNull", 27 | "sourcesAndSourcesContentBothNull", 28 | // Stricter names array checking isn't implemented. 29 | "namesMissing", 30 | "namesNotString", 31 | // This check isn't as strict in this library. 32 | "invalidMappingNotAString1", 33 | // A mapping segment with no fields is technically invalid in the spec. 34 | "invalidMappingSegmentWithZeroFields", 35 | // These tests fail due to imprecision in the spec about the 32-bit limit. 36 | "invalidMappingSegmentWithColumnExceeding32Bits", 37 | "invalidMappingSegmentWithOriginalLineExceeding32Bits", 38 | "invalidMappingSegmentWithOriginalColumnExceeding32Bits", 39 | // A large VLQ that should parse, but currently does not. 40 | "validMappingLargeVLQ", 41 | // The library currently doesn't check the types of offset lines/columns. 42 | "indexMapOffsetLineWrongType", 43 | "indexMapOffsetColumnWrongType", 44 | // The spec is not totally clear about this case. 45 | "indexMapInvalidBaseMappings", 46 | // The spec's definition of overlap can be refined 47 | "indexMapInvalidOverlap", 48 | // The library doesn't support the new ignoreList feature yet. 49 | "ignoreListWrongType1", 50 | "ignoreListWrongType2", 51 | "ignoreListWrongType3", 52 | "ignoreListOutOfBounds", 53 | ]; 54 | 55 | // The source-map library converts null sources to the "null" URL in its 56 | // sources list, so for equality checking we accept this as null. 57 | function nullish(nullOrString) { 58 | if (nullOrString === "null") { 59 | return null; 60 | } 61 | return nullOrString; 62 | } 63 | 64 | function mapLine(line) { 65 | return line + 1; 66 | } 67 | 68 | async function testMappingAction(assert, rawSourceMap, action) { 69 | return SourceMapConsumer.with(rawSourceMap, null, consumer => { 70 | let mappedPosition = consumer.originalPositionFor({ 71 | line: mapLine(action.generatedLine), 72 | column: action.generatedColumn, 73 | }); 74 | 75 | assert.equal( 76 | mappedPosition.line, 77 | mapLine(action.originalLine), 78 | `original line didn't match, expected ${mapLine( 79 | action.originalLine 80 | )} got ${mappedPosition.line}` 81 | ); 82 | assert.equal( 83 | mappedPosition.column, 84 | action.originalColumn, 85 | `original column didn't match, expected ${action.originalColumn} got ${mappedPosition.column}` 86 | ); 87 | assert.equal( 88 | nullish(mappedPosition.source), 89 | action.originalSource, 90 | `original source didn't match, expected ${action.originalSource} got ${mappedPosition.source}` 91 | ); 92 | if (action.mappedName) { 93 | assert.equal( 94 | mappedPosition.name, 95 | action.mappedName, 96 | `mapped name didn't match, expected ${action.mappedName} got ${mappedPosition.name}` 97 | ); 98 | } 99 | 100 | // When the source is null, a reverse lookup may not make sense 101 | // because there isn't a unique way to look it up. 102 | if (action.originalSource !== null) { 103 | mappedPosition = consumer.generatedPositionFor({ 104 | source: action.originalSource, 105 | line: mapLine(action.originalLine), 106 | column: action.originalColumn, 107 | }); 108 | 109 | assert.equal( 110 | mappedPosition.line, 111 | mapLine(action.generatedLine), 112 | `generated line didn't match, expected ${mapLine( 113 | action.generatedLine 114 | )} got ${mappedPosition.line}` 115 | ); 116 | assert.equal( 117 | mappedPosition.column, 118 | action.generatedColumn, 119 | `generated column didn't match, expected ${action.generatedColumn} got ${mappedPosition.column}` 120 | ); 121 | } 122 | }); 123 | } 124 | 125 | async function testTransitiveMappingAction(assert, rawSourceMap, action) { 126 | return SourceMapConsumer.with(rawSourceMap, null, async consumer => { 127 | assert.ok( 128 | Array.isArray(action.intermediateMaps), 129 | "transitive mapping case requires intermediate maps" 130 | ); 131 | 132 | let mappedPosition = consumer.originalPositionFor({ 133 | line: mapLine(action.generatedLine), 134 | column: action.generatedColumn, 135 | }); 136 | 137 | for (const intermediateMapPath of action.intermediateMaps) { 138 | const intermediateMap = await readJSON( 139 | `./source-map-tests/resources/${intermediateMapPath}` 140 | ); 141 | await SourceMapConsumer.with( 142 | intermediateMap, 143 | null, 144 | consumerIntermediate => { 145 | mappedPosition = consumerIntermediate.originalPositionFor({ 146 | line: mappedPosition.line, 147 | column: mappedPosition.column, 148 | }); 149 | } 150 | ); 151 | } 152 | 153 | assert.equal( 154 | mappedPosition.line, 155 | mapLine(action.originalLine), 156 | `original line didn't match, expected ${mapLine( 157 | action.originalLine 158 | )} got ${mappedPosition.line}` 159 | ); 160 | assert.equal( 161 | mappedPosition.column, 162 | action.originalColumn, 163 | `original column didn't match, expected ${action.originalColumn} got ${mappedPosition.column}` 164 | ); 165 | assert.equal( 166 | mappedPosition.source, 167 | action.originalSource, 168 | `original source didn't match, expected ${action.originalSource} got ${mappedPosition.source}` 169 | ); 170 | }); 171 | } 172 | 173 | for (const testCase of sourceMapSpecTests.tests) { 174 | if (skippedTests.includes(testCase.name)) { 175 | continue; 176 | } 177 | exports[`test from source map spec tests, name: ${testCase.name}`] = 178 | async function (assert) { 179 | const json = await readJSON( 180 | `./source-map-tests/resources/${testCase.sourceMapFile}` 181 | ); 182 | try { 183 | const map = await new SourceMapConsumer(json); 184 | map.eachMapping(() => {}); 185 | map.destroy(); 186 | } catch (exn) { 187 | if (testCase.sourceMapIsValid) { 188 | assert.fail( 189 | "Expected valid source map but failed to load successfully: " + 190 | exn.message 191 | ); 192 | } 193 | return; 194 | } 195 | if (!testCase.sourceMapIsValid) { 196 | assert.fail("Expected invalid source map but loaded successfully"); 197 | } 198 | if (testCase.testActions) { 199 | for (const testAction of testCase.testActions) { 200 | if (testAction.actionType == "checkMapping") { 201 | await testMappingAction(assert, json, testAction); 202 | } else if (testAction.actionType == "checkMappingTransitive") { 203 | await testTransitiveMappingAction(assert, json, testAction); 204 | } 205 | } 206 | } 207 | }; 208 | } 209 | -------------------------------------------------------------------------------- /test/test-util.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: js; js-indent-level: 2; -*- */ 2 | /* 3 | * Copyright 2014 Mozilla Foundation and contributors 4 | * Licensed under the New BSD license. See LICENSE or: 5 | * http://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | const libUtil = require("../lib/util"); 9 | exports["test normalize()"] = function (assert) { 10 | assert.equal(libUtil.normalize("/.."), "/"); 11 | assert.equal(libUtil.normalize("/../"), "/"); 12 | assert.equal(libUtil.normalize("/../../../.."), "/"); 13 | assert.equal(libUtil.normalize("/../../../../a/b/c"), "/a/b/c"); 14 | assert.equal(libUtil.normalize("/a/b/c/../../../d/../../e"), "/e"); 15 | 16 | assert.equal(libUtil.normalize(".."), "../"); 17 | assert.equal(libUtil.normalize("../"), "../"); 18 | 19 | assert.equal(libUtil.normalize("../../a/"), "../../a/"); 20 | assert.equal(libUtil.normalize("a/.."), ""); 21 | assert.equal(libUtil.normalize("a/../../.."), "../../"); 22 | 23 | assert.equal(libUtil.normalize("/."), "/"); 24 | assert.equal(libUtil.normalize("/./"), "/"); 25 | assert.equal(libUtil.normalize("/./././."), "/"); 26 | assert.equal(libUtil.normalize("/././././a/b/c"), "/a/b/c"); 27 | assert.equal(libUtil.normalize("/a/b/c/./././d/././e"), "/a/b/c/d/e"); 28 | 29 | assert.equal(libUtil.normalize(""), ""); 30 | assert.equal(libUtil.normalize("."), ""); 31 | assert.equal(libUtil.normalize("./"), ""); 32 | assert.equal(libUtil.normalize("././a"), "a"); 33 | assert.equal(libUtil.normalize("a/./"), "a/"); 34 | assert.equal(libUtil.normalize("a/././."), "a/"); 35 | 36 | assert.equal(libUtil.normalize("/a/b//c////d/////"), "/a/b//c////d/////"); 37 | 38 | assert.equal(libUtil.normalize("///a/b//c////d/////"), "//a/b//c////d/////"); 39 | assert.equal(libUtil.normalize("a/b//c////d"), "a/b//c////d"); 40 | 41 | assert.equal(libUtil.normalize(".///.././../a/b//./.."), "a/b/"); 42 | 43 | assert.equal( 44 | libUtil.normalize("http://www.example.com"), 45 | "http://www.example.com/" 46 | ); 47 | assert.equal( 48 | libUtil.normalize("http://www.example.com/"), 49 | "http://www.example.com/" 50 | ); 51 | assert.equal( 52 | libUtil.normalize("http://www.example.com/./..//a/b/c/.././d//"), 53 | "http://www.example.com//a/b/d//" 54 | ); 55 | }; 56 | 57 | exports["test join()"] = function (assert) { 58 | assert.equal(libUtil.join("a", "b"), "a/b"); 59 | assert.equal(libUtil.join("a/", "b"), "a/b"); 60 | assert.equal(libUtil.join("a//", "b"), "a//b"); 61 | assert.equal(libUtil.join("a", "b/"), "a/b/"); 62 | assert.equal(libUtil.join("a", "b//"), "a/b//"); 63 | assert.equal(libUtil.join("a/", "/b"), "/b"); 64 | assert.equal(libUtil.join("a//", "//b"), "//b/"); 65 | 66 | assert.equal(libUtil.join("a", ".."), ""); 67 | assert.equal(libUtil.join("a", "../b"), "b"); 68 | assert.equal(libUtil.join("a/b", "../c"), "a/c"); 69 | 70 | assert.equal(libUtil.join("a", "."), "a/"); 71 | assert.equal(libUtil.join("a", "./b"), "a/b"); 72 | assert.equal(libUtil.join("a/b", "./c"), "a/b/c"); 73 | 74 | assert.equal( 75 | libUtil.join("a", "http://www.example.com"), 76 | "http://www.example.com/" 77 | ); 78 | assert.equal(libUtil.join("a", "data:foo,bar"), "data:foo,bar"); 79 | 80 | assert.equal(libUtil.join("", "b"), "b"); 81 | assert.equal(libUtil.join(".", "b"), "b"); 82 | assert.equal(libUtil.join("", "b/"), "b/"); 83 | assert.equal(libUtil.join(".", "b/"), "b/"); 84 | assert.equal(libUtil.join("", "b//"), "b//"); 85 | assert.equal(libUtil.join(".", "b//"), "b//"); 86 | 87 | assert.equal(libUtil.join("", ".."), "../"); 88 | assert.equal(libUtil.join(".", ".."), "../"); 89 | assert.equal(libUtil.join("", "../b"), "../b"); 90 | assert.equal(libUtil.join(".", "../b"), "../b"); 91 | 92 | assert.equal(libUtil.join("", "."), ""); 93 | assert.equal(libUtil.join(".", "."), ""); 94 | assert.equal(libUtil.join("", "./b"), "b"); 95 | assert.equal(libUtil.join(".", "./b"), "b"); 96 | 97 | assert.equal( 98 | libUtil.join("", "http://www.example.com"), 99 | "http://www.example.com/" 100 | ); 101 | assert.equal( 102 | libUtil.join(".", "http://www.example.com"), 103 | "http://www.example.com/" 104 | ); 105 | assert.equal(libUtil.join("", "data:foo,bar"), "data:foo,bar"); 106 | assert.equal(libUtil.join(".", "data:foo,bar"), "data:foo,bar"); 107 | 108 | assert.equal(libUtil.join("..", "b"), "../b"); 109 | assert.equal(libUtil.join("..", "b/"), "../b/"); 110 | assert.equal(libUtil.join("..", "b//"), "../b//"); 111 | 112 | assert.equal(libUtil.join("..", ".."), "../../"); 113 | assert.equal(libUtil.join("..", "../b"), "../../b"); 114 | 115 | assert.equal(libUtil.join("..", "."), "../"); 116 | assert.equal(libUtil.join("..", "./b"), "../b"); 117 | 118 | assert.equal( 119 | libUtil.join("..", "http://www.example.com"), 120 | "http://www.example.com/" 121 | ); 122 | assert.equal(libUtil.join("..", "data:foo,bar"), "data:foo,bar"); 123 | 124 | assert.equal(libUtil.join("a", ""), "a/"); 125 | assert.equal(libUtil.join("a", "."), "a/"); 126 | assert.equal(libUtil.join("a/", ""), "a/"); 127 | assert.equal(libUtil.join("a/", "."), "a/"); 128 | assert.equal(libUtil.join("a//", ""), "a//"); 129 | assert.equal(libUtil.join("a//", "."), "a//"); 130 | assert.equal(libUtil.join("/a", ""), "/a/"); 131 | assert.equal(libUtil.join("/a", "."), "/a/"); 132 | assert.equal(libUtil.join("", ""), ""); 133 | assert.equal(libUtil.join(".", ""), ""); 134 | assert.equal(libUtil.join(".", ""), ""); 135 | assert.equal(libUtil.join(".", "."), ""); 136 | assert.equal(libUtil.join("..", ""), "../"); 137 | assert.equal(libUtil.join("..", "."), "../"); 138 | assert.equal(libUtil.join("http://foo.org/a", ""), "http://foo.org/a/"); 139 | assert.equal(libUtil.join("http://foo.org/a", "."), "http://foo.org/a/"); 140 | assert.equal(libUtil.join("http://foo.org/a/", ""), "http://foo.org/a/"); 141 | assert.equal(libUtil.join("http://foo.org/a/", "."), "http://foo.org/a/"); 142 | assert.equal(libUtil.join("http://foo.org/a//", ""), "http://foo.org/a//"); 143 | assert.equal(libUtil.join("http://foo.org/a//", "."), "http://foo.org/a//"); 144 | assert.equal(libUtil.join("http://foo.org", ""), "http://foo.org/"); 145 | assert.equal(libUtil.join("http://foo.org", "."), "http://foo.org/"); 146 | assert.equal(libUtil.join("http://foo.org/", ""), "http://foo.org/"); 147 | assert.equal(libUtil.join("http://foo.org/", "."), "http://foo.org/"); 148 | assert.equal(libUtil.join("http://foo.org//", ""), "http://foo.org//"); 149 | assert.equal(libUtil.join("http://foo.org//", "."), "http://foo.org//"); 150 | assert.equal(libUtil.join("//www.example.com", ""), "//www.example.com/"); 151 | assert.equal(libUtil.join("//www.example.com", "."), "//www.example.com/"); 152 | 153 | assert.equal(libUtil.join("http://foo.org/a", "b"), "http://foo.org/a/b"); 154 | assert.equal(libUtil.join("http://foo.org/a/", "b"), "http://foo.org/a/b"); 155 | assert.equal(libUtil.join("http://foo.org/a//", "b"), "http://foo.org/a//b"); 156 | assert.equal(libUtil.join("http://foo.org/a", "b/"), "http://foo.org/a/b/"); 157 | assert.equal(libUtil.join("http://foo.org/a", "b//"), "http://foo.org/a/b//"); 158 | assert.equal(libUtil.join("http://foo.org/a/", "/b"), "http://foo.org/b"); 159 | assert.equal(libUtil.join("http://foo.org/a//", "//b"), "http://b/"); 160 | 161 | assert.equal(libUtil.join("http://foo.org/a", ".."), "http://foo.org/"); 162 | assert.equal(libUtil.join("http://foo.org/a", "../b"), "http://foo.org/b"); 163 | assert.equal( 164 | libUtil.join("http://foo.org/a/b", "../c"), 165 | "http://foo.org/a/c" 166 | ); 167 | 168 | assert.equal(libUtil.join("http://foo.org/a", "."), "http://foo.org/a/"); 169 | assert.equal(libUtil.join("http://foo.org/a", "./b"), "http://foo.org/a/b"); 170 | assert.equal( 171 | libUtil.join("http://foo.org/a/b", "./c"), 172 | "http://foo.org/a/b/c" 173 | ); 174 | 175 | assert.equal( 176 | libUtil.join("http://foo.org/a", "http://www.example.com"), 177 | "http://www.example.com/" 178 | ); 179 | assert.equal( 180 | libUtil.join("http://foo.org/a", "data:foo,bar"), 181 | "data:foo,bar" 182 | ); 183 | 184 | assert.equal(libUtil.join("http://foo.org", "a"), "http://foo.org/a"); 185 | assert.equal(libUtil.join("http://foo.org/", "a"), "http://foo.org/a"); 186 | assert.equal(libUtil.join("http://foo.org//", "a"), "http://foo.org//a"); 187 | assert.equal(libUtil.join("http://foo.org", "/a"), "http://foo.org/a"); 188 | assert.equal(libUtil.join("http://foo.org/", "/a"), "http://foo.org/a"); 189 | assert.equal(libUtil.join("http://foo.org//", "/a"), "http://foo.org/a"); 190 | 191 | assert.equal( 192 | libUtil.join("http://www.example.com", "//foo.org/bar"), 193 | "http://foo.org/bar" 194 | ); 195 | assert.equal( 196 | libUtil.join("//www.example.com", "//foo.org/bar"), 197 | "//foo.org/bar" 198 | ); 199 | }; 200 | 201 | // TODO Issue #128: Define and test this function properly. 202 | exports["test relative()"] = function (assert) { 203 | assert.equal(libUtil.relative("/the/root", "/the/root/one.js"), "one.js"); 204 | assert.equal( 205 | libUtil.relative("http://the/root", "http://the/root/one.js"), 206 | "one.js" 207 | ); 208 | assert.equal( 209 | libUtil.relative("/the/root", "/the/rootone.js"), 210 | "../rootone.js" 211 | ); 212 | assert.equal( 213 | libUtil.relative("http://the/root", "http://the/rootone.js"), 214 | "../rootone.js" 215 | ); 216 | assert.equal( 217 | libUtil.relative("/the/root", "/therootone.js"), 218 | "../../therootone.js" 219 | ); 220 | assert.equal( 221 | libUtil.relative("http://the/root", "/therootone.js"), 222 | "/therootone.js" 223 | ); 224 | 225 | assert.equal(libUtil.relative("", "/the/root/one.js"), "/the/root/one.js"); 226 | assert.equal(libUtil.relative(".", "/the/root/one.js"), "/the/root/one.js"); 227 | assert.equal(libUtil.relative("", "the/root/one.js"), "the/root/one.js"); 228 | assert.equal(libUtil.relative(".", "the/root/one.js"), "the/root/one.js"); 229 | 230 | assert.equal(libUtil.relative("/", "/the/root/one.js"), "the/root/one.js"); 231 | assert.equal(libUtil.relative("/", "the/root/one.js"), "the/root/one.js"); 232 | }; 233 | 234 | exports["test computeSourceURL"] = function (assert) { 235 | // Tests with sourceMapURL. 236 | assert.equal( 237 | libUtil.computeSourceURL("", "src/test.js", "http://example.com"), 238 | "http://example.com/src/test.js" 239 | ); 240 | assert.equal( 241 | libUtil.computeSourceURL(undefined, "src/test.js", "http://example.com"), 242 | "http://example.com/src/test.js" 243 | ); 244 | assert.equal( 245 | libUtil.computeSourceURL("src", "test.js", "http://example.com"), 246 | "http://example.com/src/test.js" 247 | ); 248 | assert.equal( 249 | libUtil.computeSourceURL("src/", "test.js", "http://example.com"), 250 | "http://example.com/src/test.js" 251 | ); 252 | assert.equal( 253 | libUtil.computeSourceURL("src", "/test.js", "http://example.com"), 254 | "http://example.com/src/test.js" 255 | ); 256 | assert.equal( 257 | libUtil.computeSourceURL( 258 | "http://mozilla.com", 259 | "src/test.js", 260 | "http://example.com" 261 | ), 262 | "http://mozilla.com/src/test.js" 263 | ); 264 | assert.equal( 265 | libUtil.computeSourceURL( 266 | "", 267 | "test.js", 268 | "http://example.com/src/test.js.map" 269 | ), 270 | "http://example.com/src/test.js" 271 | ); 272 | assert.equal( 273 | libUtil.computeSourceURL( 274 | "", 275 | "/test.js", 276 | "http://example.com/src/test.js.map" 277 | ), 278 | "http://example.com/test.js" 279 | ); 280 | 281 | // Legacy code won't pass in the sourceMapURL. 282 | assert.equal(libUtil.computeSourceURL("", "src/test.js"), "src/test.js"); 283 | assert.equal( 284 | libUtil.computeSourceURL(undefined, "src/test.js"), 285 | "src/test.js" 286 | ); 287 | assert.equal(libUtil.computeSourceURL("src", "test.js"), "src/test.js"); 288 | assert.equal(libUtil.computeSourceURL("src/", "test.js"), "src/test.js"); 289 | assert.equal(libUtil.computeSourceURL("src", "/test.js"), "src/test.js"); 290 | assert.equal(libUtil.computeSourceURL("src", "../test.js"), "test.js"); 291 | assert.equal( 292 | libUtil.computeSourceURL("src/dir", "../././../test.js"), 293 | "test.js" 294 | ); 295 | 296 | // This gives different results with the old algorithm and the new 297 | // spec-compliant algorithm. 298 | assert.equal( 299 | libUtil.computeSourceURL("http://example.com/dir", "/test.js"), 300 | "http://example.com/dir/test.js" 301 | ); 302 | }; 303 | -------------------------------------------------------------------------------- /test/util.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: js; js-indent-level: 2; -*- */ 2 | /* 3 | * Copyright 2011 Mozilla Foundation and contributors 4 | * Licensed under the New BSD license. See LICENSE or: 5 | * http://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | const util = require("../lib/util"); 9 | 10 | // This is a test mapping which maps functions from two different files 11 | // (one.js and two.js) to a minified generated source. 12 | // 13 | // Here is one.js: 14 | // 15 | // ONE.foo = function (bar) { 16 | // return baz(bar); 17 | // }; 18 | // 19 | // Here is two.js: 20 | // 21 | // TWO.inc = function (n) { 22 | // return n + 1; 23 | // }; 24 | // 25 | // And here is the generated code (min.js): 26 | // 27 | // ONE.foo=function(a){return baz(a);}; 28 | // TWO.inc=function(a){return a+1;}; 29 | exports.testGeneratedCode = 30 | " ONE.foo=function(a){return baz(a);};\n TWO.inc=function(a){return a+1;};"; 31 | exports.testMap = { 32 | version: 3, 33 | file: "min.js", 34 | names: ["bar", "baz", "n"], 35 | sources: ["one.js", "two.js"], 36 | sourceRoot: "/the/root", 37 | mappings: 38 | "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA", 39 | }; 40 | 41 | exports.testMapWithIgnoreList = { 42 | version: 3, 43 | file: "min.js", 44 | names: ["bar", "baz", "n"], 45 | sources: ["one.js", "two.js"], 46 | sourceRoot: "/the/root", 47 | mappings: 48 | "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA", 49 | x_google_ignoreList: [0], 50 | }; 51 | 52 | exports.testMapNoSourceRoot = { 53 | version: 3, 54 | file: "min.js", 55 | names: ["bar", "baz", "n"], 56 | sources: ["one.js", "two.js"], 57 | mappings: 58 | "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA", 59 | }; 60 | exports.testMapEmptySourceRoot = { 61 | version: 3, 62 | file: "min.js", 63 | names: ["bar", "baz", "n"], 64 | sources: ["one.js", "two.js"], 65 | sourceRoot: "", 66 | mappings: 67 | "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA", 68 | }; 69 | exports.testMapSingleSource = { 70 | version: 3, 71 | file: "min.js", 72 | names: ["bar", "baz"], 73 | sources: ["one.js"], 74 | sourceRoot: "", 75 | mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID", 76 | }; 77 | exports.testMapEmptyMappings = { 78 | version: 3, 79 | file: "min.js", 80 | names: [], 81 | sources: ["one.js", "two.js"], 82 | sourcesContent: [" ONE.foo = 1;", " TWO.inc = 2;"], 83 | sourceRoot: "", 84 | mappings: "", 85 | }; 86 | exports.testMapEmptyMappingsRelativeSources = { 87 | version: 3, 88 | file: "min.js", 89 | names: [], 90 | sources: ["./one.js", "./two.js"], 91 | sourcesContent: [" ONE.foo = 1;", " TWO.inc = 2;"], 92 | sourceRoot: "/the/root", 93 | mappings: "", 94 | }; 95 | exports.testMapEmptyMappingsRelativeSources_generatedExpected = { 96 | version: 3, 97 | file: "min.js", 98 | names: [], 99 | sources: ["one.js", "two.js"], 100 | sourcesContent: [" ONE.foo = 1;", " TWO.inc = 2;"], 101 | sourceRoot: "/the/root", 102 | mappings: "", 103 | }; 104 | exports.testMapMultiSourcesMappingRefersSingleSourceOnly = { 105 | version: 3, 106 | file: "min.js", 107 | names: ["bar", "baz"], 108 | sources: ["one.js", "withoutMappings.js"], 109 | sourceRoot: "", 110 | mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID", 111 | }; 112 | // This mapping is identical to above, but uses the indexed format instead. 113 | exports.indexedTestMap = { 114 | version: 3, 115 | file: "min.js", 116 | sections: [ 117 | { 118 | offset: { 119 | line: 0, 120 | column: 0, 121 | }, 122 | map: { 123 | version: 3, 124 | sources: ["one.js"], 125 | sourcesContent: [ 126 | " ONE.foo = function (bar) {\n return baz(bar);\n };", 127 | ], 128 | names: ["bar", "baz"], 129 | mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID", 130 | file: "min.js", 131 | sourceRoot: "/the/root", 132 | }, 133 | }, 134 | { 135 | offset: { 136 | line: 1, 137 | column: 0, 138 | }, 139 | map: { 140 | version: 3, 141 | sources: ["two.js"], 142 | sourcesContent: [" TWO.inc = function (n) {\n return n + 1;\n };"], 143 | names: ["n"], 144 | mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOA", 145 | file: "min.js", 146 | sourceRoot: "/the/root", 147 | }, 148 | }, 149 | ], 150 | }; 151 | exports.indexedTestMapDifferentSourceRoots = { 152 | version: 3, 153 | file: "min.js", 154 | sections: [ 155 | { 156 | offset: { 157 | line: 0, 158 | column: 0, 159 | }, 160 | map: { 161 | version: 3, 162 | sources: ["one.js"], 163 | sourcesContent: [ 164 | " ONE.foo = function (bar) {\n return baz(bar);\n };", 165 | ], 166 | names: ["bar", "baz"], 167 | mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID", 168 | file: "min.js", 169 | sourceRoot: "/the/root", 170 | }, 171 | }, 172 | { 173 | offset: { 174 | line: 1, 175 | column: 0, 176 | }, 177 | map: { 178 | version: 3, 179 | sources: ["two.js"], 180 | sourcesContent: [" TWO.inc = function (n) {\n return n + 1;\n };"], 181 | names: ["n"], 182 | mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOA", 183 | file: "min.js", 184 | sourceRoot: "/different/root", 185 | }, 186 | }, 187 | ], 188 | }; 189 | exports.indexedTestMapColumnOffset = { 190 | version: 3, 191 | file: "min.js", 192 | sections: [ 193 | { 194 | offset: { 195 | line: 0, 196 | column: 0, 197 | }, 198 | map: { 199 | version: 3, 200 | sources: ["one.js"], 201 | sourcesContent: [ 202 | " ONE.foo = function (bar) {\n return baz(bar);\n };", 203 | ], 204 | names: ["bar", "baz"], 205 | mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID", 206 | file: "min.js", 207 | sourceRoot: "/the/root", 208 | }, 209 | }, 210 | { 211 | offset: { 212 | line: 0, 213 | // Previous section's last generated mapping is [32, Infinity), so 214 | // we're placing this a bit after that. 215 | column: 50, 216 | }, 217 | map: { 218 | version: 3, 219 | sources: ["two.js"], 220 | sourcesContent: [" TWO.inc = function (n) {\n return n + 1;\n };"], 221 | names: ["n"], 222 | mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOA", 223 | file: "min.js", 224 | sourceRoot: "/the/root", 225 | }, 226 | }, 227 | ], 228 | }; 229 | // This mapping is for testing a case where the mapped position is at the 230 | // section offset. 231 | exports.indexedTestMapAtOffsetBoundary = { 232 | version: 3, 233 | file: "min.js", 234 | sections: [ 235 | { 236 | offset: { 237 | line: 0, 238 | column: 0, 239 | }, 240 | map: { 241 | version: 3, 242 | sources: ["one.js"], 243 | sourcesContent: [ 244 | "ONE.foo = function (bar) {\n return baz(bar);\n };", 245 | ], 246 | names: ["bar", "baz"], 247 | mappings: "AAAA", 248 | file: "min.js", 249 | sourceRoot: "/the/root", 250 | }, 251 | }, 252 | ], 253 | }; 254 | exports.testMapWithSourcesContent = { 255 | version: 3, 256 | file: "min.js", 257 | names: ["bar", "baz", "n"], 258 | sources: ["one.js", "two.js"], 259 | sourcesContent: [ 260 | " ONE.foo = function (bar) {\n return baz(bar);\n };", 261 | " TWO.inc = function (n) {\n return n + 1;\n };", 262 | ], 263 | sourceRoot: "/the/root", 264 | mappings: 265 | "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA", 266 | }; 267 | exports.testMapRelativeSources = { 268 | version: 3, 269 | file: "min.js", 270 | names: ["bar", "baz", "n"], 271 | sources: ["./one.js", "./two.js"], 272 | sourcesContent: [ 273 | " ONE.foo = function (bar) {\n return baz(bar);\n };", 274 | " TWO.inc = function (n) {\n return n + 1;\n };", 275 | ], 276 | sourceRoot: "/the/root", 277 | mappings: 278 | "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA", 279 | }; 280 | exports.emptyMap = { 281 | version: 3, 282 | file: "min.js", 283 | names: [], 284 | sources: [], 285 | mappings: "", 286 | }; 287 | exports.mapWithSourcelessMapping = { 288 | version: 3, 289 | file: "example.js", 290 | names: [], 291 | sources: ["example.js"], 292 | mappings: "AAgCA,C", 293 | }; 294 | 295 | function assertMapping( 296 | generatedLine, 297 | generatedColumn, 298 | originalSource, 299 | originalLine, 300 | originalColumn, 301 | name, 302 | bias, 303 | map, 304 | assert, 305 | dontTestGenerated, 306 | dontTestOriginal 307 | ) { 308 | if (!dontTestOriginal) { 309 | const origMapping = map.originalPositionFor({ 310 | line: generatedLine, 311 | column: generatedColumn, 312 | bias, 313 | }); 314 | assert.equal( 315 | origMapping.name, 316 | name, 317 | "Incorrect name, expected " + 318 | JSON.stringify(name) + 319 | ", got " + 320 | JSON.stringify(origMapping.name) 321 | ); 322 | assert.equal( 323 | origMapping.line, 324 | originalLine, 325 | "Incorrect line, expected " + 326 | JSON.stringify(originalLine) + 327 | ", got " + 328 | JSON.stringify(origMapping.line) 329 | ); 330 | assert.equal( 331 | origMapping.column, 332 | originalColumn, 333 | "Incorrect column, expected " + 334 | JSON.stringify(originalColumn) + 335 | ", got " + 336 | JSON.stringify(origMapping.column) 337 | ); 338 | 339 | let expectedSource; 340 | 341 | if ( 342 | originalSource && 343 | map.sourceRoot && 344 | originalSource.indexOf(map.sourceRoot) === 0 345 | ) { 346 | expectedSource = originalSource; 347 | } else if (originalSource) { 348 | expectedSource = map.sourceRoot 349 | ? util.join(map.sourceRoot, originalSource) 350 | : originalSource; 351 | } else { 352 | expectedSource = null; 353 | } 354 | 355 | assert.equal( 356 | origMapping.source, 357 | expectedSource, 358 | "Incorrect source, expected " + 359 | JSON.stringify(expectedSource) + 360 | ", got " + 361 | JSON.stringify(origMapping.source) 362 | ); 363 | } 364 | 365 | if (!dontTestGenerated) { 366 | const genMapping = map.generatedPositionFor({ 367 | source: originalSource, 368 | line: originalLine, 369 | column: originalColumn, 370 | bias, 371 | }); 372 | assert.equal( 373 | genMapping.line, 374 | generatedLine, 375 | "Incorrect line, expected " + 376 | JSON.stringify(generatedLine) + 377 | ", got " + 378 | JSON.stringify(genMapping.line) 379 | ); 380 | assert.equal( 381 | genMapping.column, 382 | generatedColumn, 383 | "Incorrect column, expected " + 384 | JSON.stringify(generatedColumn) + 385 | ", got " + 386 | JSON.stringify(genMapping.column) 387 | ); 388 | } 389 | } 390 | exports.assertMapping = assertMapping; 391 | 392 | function assertEqualMaps(assert, actualMap, expectedMap) { 393 | assert.equal(actualMap.version, expectedMap.version, "version mismatch"); 394 | assert.equal(actualMap.file, expectedMap.file, "file mismatch"); 395 | assert.equal( 396 | actualMap.names.length, 397 | expectedMap.names.length, 398 | "names length mismatch: " + 399 | actualMap.names.join(", ") + 400 | " != " + 401 | expectedMap.names.join(", ") 402 | ); 403 | for (let i = 0; i < actualMap.names.length; i++) { 404 | assert.equal( 405 | actualMap.names[i], 406 | expectedMap.names[i], 407 | "names[" + 408 | i + 409 | "] mismatch: " + 410 | actualMap.names.join(", ") + 411 | " != " + 412 | expectedMap.names.join(", ") 413 | ); 414 | } 415 | assert.equal( 416 | actualMap.sources.length, 417 | expectedMap.sources.length, 418 | "sources length mismatch: " + 419 | actualMap.sources.join(", ") + 420 | " != " + 421 | expectedMap.sources.join(", ") 422 | ); 423 | for (let i = 0; i < actualMap.sources.length; i++) { 424 | assert.equal( 425 | actualMap.sources[i], 426 | expectedMap.sources[i], 427 | "sources[" + 428 | i + 429 | "] length mismatch: " + 430 | actualMap.sources.join(", ") + 431 | " != " + 432 | expectedMap.sources.join(", ") 433 | ); 434 | } 435 | assert.equal( 436 | actualMap.sourceRoot, 437 | expectedMap.sourceRoot, 438 | "sourceRoot mismatch: " + 439 | actualMap.sourceRoot + 440 | " != " + 441 | expectedMap.sourceRoot 442 | ); 443 | assert.equal( 444 | actualMap.mappings, 445 | expectedMap.mappings, 446 | "mappings mismatch:\nActual: " + 447 | actualMap.mappings + 448 | "\nExpected: " + 449 | expectedMap.mappings 450 | ); 451 | if (actualMap.sourcesContent) { 452 | assert.equal( 453 | actualMap.sourcesContent.length, 454 | expectedMap.sourcesContent.length, 455 | "sourcesContent length mismatch" 456 | ); 457 | for (let i = 0; i < actualMap.sourcesContent.length; i++) { 458 | assert.equal( 459 | actualMap.sourcesContent[i], 460 | expectedMap.sourcesContent[i], 461 | "sourcesContent[" + i + "] mismatch" 462 | ); 463 | } 464 | } 465 | } 466 | exports.assertEqualMaps = assertEqualMaps; 467 | -------------------------------------------------------------------------------- /wasm-mappings/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target/ 3 | **/*.rs.bk 4 | Cargo.lock 5 | -------------------------------------------------------------------------------- /wasm-mappings/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to `source-map-mappings` 2 | 3 | ## Building 4 | 5 | To build the core library for the host target (for use with testing): 6 | 7 | ``` 8 | $ cargo build 9 | ``` 10 | 11 | To build for WebAssembly, ensure that you have the `wasm32-unknown-unknown` target: 12 | 13 | ``` 14 | $ rustup update 15 | $ rustup target add wasm32-unknown-unknown --toolchain nightly 16 | ``` 17 | 18 | Then, cross compile to a `.wasm` file via the WebAssembly API crate: 19 | 20 | ``` 21 | $ cd source-map-mappings-wasm-api/ 22 | $ ./build.py -o output.wasm 23 | ``` 24 | 25 | The `build.py` script handles shrinking the size of the resulting `.wasm` file 26 | for you, with `wasm-gc`, `wasm-snip`, and `wasm-opt`. 27 | 28 | For more details, run: 29 | 30 | ``` 31 | $ ./build.py --help 32 | ``` 33 | 34 | ## Testing 35 | 36 | To run all the tests: 37 | 38 | ``` 39 | $ cargo test 40 | ``` 41 | 42 | ## Automatic code formatting 43 | 44 | We use [`rustfmt`](https://github.com/rust-lang-nursery/rustfmt) to enforce a 45 | consistent code style across the whole code base. 46 | 47 | You can install the latest version of `rustfmt` with this command: 48 | 49 | ``` 50 | $ rustup update nightly 51 | $ cargo install -f rustfmt-nightly 52 | ``` 53 | 54 | Ensure that `~/.cargo/bin` is on your path. 55 | 56 | Once that is taken care of, you can (re)format all code by running this command: 57 | 58 | ``` 59 | $ cargo fmt 60 | ``` 61 | -------------------------------------------------------------------------------- /wasm-mappings/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = [ 3 | "Nick Fitzgerald ", 4 | "Tom Tromey ", 5 | ] 6 | categories = [ 7 | "development-tools::debugging", 8 | "parser-implementations", 9 | ] 10 | description = "Parse the `mappings` string from a source map." 11 | keywords = [ 12 | "sourcemap", 13 | "source", 14 | "map", 15 | "vlq", 16 | ] 17 | license = "BSD-3-Clause" 18 | name = "source-map-mappings" 19 | readme = "./README.md" 20 | repository = "https://github.com/mozilla/source-map" 21 | version = "0.5.0" 22 | 23 | [dependencies] 24 | rand = "0.4.1" 25 | vlq = "0.5.1" 26 | 27 | [dev-dependencies] 28 | quickcheck = "0.5.0" 29 | [profile.release] 30 | debug = true 31 | -------------------------------------------------------------------------------- /wasm-mappings/README.md: -------------------------------------------------------------------------------- 1 | # `source-map-mappings` 2 | 3 | Parse the `"mappings"` string from a source map. 4 | 5 | This is intended to be compiled to WebAssembly and eventually used from the 6 | [`mozilla/source-map`][source-map] library. This is **not** a general purpose 7 | source maps library. 8 | 9 | [source-map]: https://github.com/mozilla/source-map 10 | 11 | - [Documentation](#documentation) 12 | - [License](#license) 13 | - [Contributing](#contributing) 14 | 15 | ### Documentation 16 | 17 | [📚 Documentation on `docs.rs` 📚][docs] 18 | 19 | [docs]: https://docs.rs/source-map-mappings 20 | 21 | ### License 22 | 23 | Licensed under [BSD-3-Clause](http://opensource.org/licenses/BSD-3-Clause). 24 | 25 | ### Contributing 26 | 27 | See 28 | [CONTRIBUTING.md](https://github.com/mozilla/source-map-mappings/blob/master/wasm-mappings/CONTRIBUTING.md) 29 | for hacking. 30 | -------------------------------------------------------------------------------- /wasm-mappings/benches/bench.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate source_map_mappings; 4 | extern crate test; 5 | 6 | static FIXTURE: &'static [u8] = include_bytes!("./part-of-scala-js-source-map"); 7 | 8 | #[bench] 9 | fn bench_parse_part_of_scala_js_source_map(b: &mut test::Bencher) { 10 | b.iter(|| { 11 | let mut mappings = source_map_mappings::parse_mappings::<()>(FIXTURE).unwrap(); 12 | test::black_box( 13 | mappings 14 | .all_generated_locations_for(7, 2, None) 15 | .count(), 16 | ); 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /wasm-mappings/ci/script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | 5 | case "$JOB" in 6 | "test") 7 | cargo test 8 | ;; 9 | "bench") 10 | cargo bench 11 | ;; 12 | "wasm") 13 | rustup target add wasm32-unknown-unknown 14 | cd source-map-mappings-wasm-api/ 15 | 16 | # Non-release builds are broken for wasm32-unknown-unknown targets right now. 17 | # cargo build --target wasm32-unknown-unknown 18 | # test -f target/wasm32-unknown-unknown/debug/source_map_mappings_wasm_api.wasm 19 | 20 | cargo build --release --target wasm32-unknown-unknown 21 | test -f target/wasm32-unknown-unknown/release/source_map_mappings_wasm_api.wasm 22 | 23 | rm target/wasm32-unknown-unknown/release/source_map_mappings_wasm_api.wasm 24 | cargo build --release --target wasm32-unknown-unknown --features profiling 25 | test -f target/wasm32-unknown-unknown/release/source_map_mappings_wasm_api.wasm 26 | ;; 27 | *) 28 | echo "Unknown \$JOB = '$JOB'" 29 | exit 1 30 | esac 31 | -------------------------------------------------------------------------------- /wasm-mappings/source-map-mappings-wasm-api/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /wasm-mappings/source-map-mappings-wasm-api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Nick Fitzgerald ", "Tom Tromey "] 3 | description = "Exported WebAssembly API for the `source-map-mappings` crate." 4 | license = "BSD-3-Clause" 5 | name = "source-map-mappings-wasm-api" 6 | readme = "../README.md" 7 | repository = "https://github.com/mozilla/source-map" 8 | version = "0.5.0" 9 | 10 | [dependencies] 11 | source-map-mappings = { version = "0.5.0", path = ".." } 12 | 13 | [features] 14 | profiling = [] 15 | 16 | [lib] 17 | crate-type = ["cdylib"] 18 | 19 | [profile.release] 20 | debug = true 21 | -------------------------------------------------------------------------------- /wasm-mappings/source-map-mappings-wasm-api/build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import os 5 | import re 6 | import subprocess 7 | import sys 8 | 9 | DESC = """ 10 | 11 | Build, trim, and optimize the `.wasm` file for inclusion in the 12 | `mozilla/source-map` library. 13 | 14 | Requires: 15 | 16 | - wasm-nm: https://github.com/fitzgen/wasm-nm 17 | - wasm-gc: https://github.com/alexcrichton/wasm-gc 18 | - wasm-snip: https://github.com/fitzgen/wasm-snip 19 | - wasm-opt: https://github.com/WebAssembly/binaryen 20 | 21 | """ 22 | 23 | parser = argparse.ArgumentParser( 24 | formatter_class=argparse.RawDescriptionHelpFormatter, 25 | description=DESC) 26 | 27 | parser.add_argument( 28 | "-g", 29 | "--debug", 30 | action="store_true", 31 | help="Include debug info (the \"name\" section) in the final `.wasm` file.") 32 | 33 | parser.add_argument( 34 | "-p", 35 | "--profiling", 36 | action="store_true", 37 | help="Enable the `profiling` cargo feature.") 38 | 39 | parser.add_argument( 40 | "-o", 41 | "--output", 42 | type=str, 43 | default=None, 44 | help="The path to write the output `.wasm` file to. If not supplied, the `.wasm` file is written to `stdout`.") 45 | 46 | parser.add_argument( 47 | "--no-wasm-opt", 48 | dest="wasm_opt", 49 | action="store_false", 50 | help="Do not run `wasm-opt`.") 51 | 52 | parser.add_argument( 53 | "--no-wasm-gc", 54 | dest="wasm_gc", 55 | action="store_false", 56 | help="Do not run `wasm-gc`.") 57 | 58 | parser.add_argument( 59 | "--no-wasm-snip", 60 | dest="wasm_snip", 61 | action="store_false", 62 | help="Do not run `wasm-snip`.") 63 | 64 | def decode(f): 65 | return f.decode(encoding="utf-8", errors="ignore") 66 | 67 | def run(cmd, **kwargs): 68 | sys.stderr.write(str(cmd) + "\n") 69 | 70 | if "stdout" not in kwargs: 71 | kwargs["stdout"] = subprocess.PIPE 72 | child = subprocess.run(cmd, **kwargs) 73 | if child.returncode != 0: 74 | raise Exception("{} did not exit OK".format(str(cmd))) 75 | return decode(child.stdout) 76 | 77 | def add_path_ext_prefix(path, prefix): 78 | (root, ext) = os.path.splitext(path) 79 | return root + "." + prefix + ext 80 | 81 | def build(args): 82 | cmd = ["cargo", "build", "--release", "--target", "wasm32-unknown-unknown"] 83 | if args.profiling: 84 | cmd.extend(["--features", "profiling"]) 85 | run(cmd) 86 | return "./target/wasm32-unknown-unknown/release/source_map_mappings_wasm_api.wasm" 87 | 88 | def wasm_gc(args, wasm_path): 89 | if not args.wasm_gc: 90 | return wasm_path 91 | 92 | out_path = add_path_ext_prefix(wasm_path, "gc") 93 | run(["wasm-gc", wasm_path, out_path]) 94 | return out_path 95 | 96 | SHOULD_SNIP = [ 97 | re.compile(r".*(std|core)(9|::)panicking.*"), 98 | re.compile(r".*(std|core)(3|::)fmt.*"), 99 | re.compile(r".*core(6|::)option(13|::)expect_failed.*"), 100 | re.compile(r".*core(5|::)slice(\d+|::)slice_index_.*_fail.*"), 101 | re.compile(r".*core(3|::)str(\d+|::)slice_.*_fail.*"), 102 | re.compile(r".*core(6|::)result(13|::)unwrap_failed.*"), 103 | re.compile(r".*std(6|::)thread(5|::)local.*"), 104 | re.compile(r".*std(2|::)io(5|::).*"), 105 | re.compile(r"__.*2"), 106 | re.compile(r".*(std|core)(5|::)error.*"), 107 | re.compile(r".*(std|core)(3|::)any(3|::)Any.*"), 108 | ] 109 | 110 | def wasm_snip(args, wasm_path): 111 | if not args.wasm_snip: 112 | return wasm_path 113 | 114 | out_path = add_path_ext_prefix(wasm_path, "snip") 115 | 116 | private_functions = run(["wasm-nm", "-j", wasm_path]).splitlines() 117 | 118 | snip_functions = set() 119 | for snip in SHOULD_SNIP: 120 | snip_functions.update(filter(lambda f: re.match(snip, f), 121 | private_functions)) 122 | 123 | run(["wasm-snip", "-o", out_path, wasm_path, *snip_functions]), 124 | return out_path 125 | 126 | def wasm_opt(args, wasm_path): 127 | if not args.wasm_opt: 128 | return wasm_path 129 | 130 | out_path = add_path_ext_prefix(wasm_path, "opt") 131 | 132 | cmd = [ 133 | "wasm-opt", 134 | "-O3", 135 | "-Oz", 136 | "--duplicate-function-elimination", 137 | "-o", out_path, 138 | wasm_path 139 | ] 140 | if args.debug: 141 | cmd.append("-g") 142 | run(cmd) 143 | return out_path 144 | 145 | def main(): 146 | args = parser.parse_args() 147 | os.chdir(os.path.dirname(sys.argv[0])) 148 | 149 | wasm_path = build(args) 150 | wasm_path = wasm_gc(args, wasm_path) 151 | wasm_path = wasm_snip(args, wasm_path) 152 | # GC again after snipping. 153 | wasm_path = wasm_gc(args, wasm_path) 154 | wasm_path = wasm_opt(args, wasm_path) 155 | 156 | if args.output: 157 | run(["cp", wasm_path, args.output]) 158 | else: 159 | run(["cat", wasm_path], stdout=subprocess.STDOUT) 160 | 161 | if __name__ == "__main__": 162 | main() 163 | -------------------------------------------------------------------------------- /wasm-mappings/source-map-mappings-wasm-api/who-calls.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import re 5 | import subprocess 6 | 7 | DESC = """ 8 | 9 | List the callers of some function in the given `.wasm` file. 10 | 11 | Constructs the call graph of functions in the `.wasm` file and then queries 12 | edges in that call graph. 13 | 14 | Requires that the `wasm-objdump` tool from the WABT[0] is installed and on the 15 | `$PATH`. 16 | 17 | [0]: https://github.com/WebAssembly/wabt 18 | 19 | ## Example Usage 20 | 21 | Print every caller of `std::panicking::begin_panic`: 22 | 23 | $ ./who-calls.py path/to/something.wasm \\ 24 | --function "std::panicking::begin_panic::h59c9bbae5c8cc295" 25 | 26 | Print the top 5 largest functions and their callers: 27 | 28 | $ ./who-calls.py path/to/something.wasm --top 29 | 30 | """ 31 | 32 | parser = argparse.ArgumentParser( 33 | formatter_class=argparse.RawDescriptionHelpFormatter, 34 | description=DESC) 35 | 36 | parser.add_argument( 37 | "wasm_file", 38 | type=str, 39 | help="The input `.wasm` file.") 40 | 41 | parser.add_argument( 42 | "--function", 43 | type=str, 44 | help="The function whose callers should be listed.") 45 | 46 | parser.add_argument( 47 | "-t", 48 | "--top", 49 | type=int, 50 | default=None, 51 | help="Display the largest N functions and their callers") 52 | 53 | parser.add_argument( 54 | "-d", 55 | "--max-depth", 56 | type=int, 57 | default=None, 58 | help="The maximum call stack depth to display") 59 | 60 | def decode(f): 61 | return f.decode(encoding="utf-8", errors="ignore") 62 | 63 | def run(cmd, **kwargs): 64 | kwargs["stdout"] = subprocess.PIPE 65 | child = subprocess.run(cmd, **kwargs) 66 | if child.returncode != 0: 67 | raise Exception("{} did not exit OK".format(str(cmd))) 68 | return decode(child.stdout) 69 | 70 | def disassemble(args): 71 | return run(["wasm-objdump", "-d", args.wasm_file]) 72 | 73 | START_FUNCTION = re.compile(r"^(\w+) <([\w<>:\s]+)>:$") 74 | CALL_FUNCTION = re.compile(r"^ \w+: [\w ]*\|\s*call \w+ <([\w<>:\s]+)>$") 75 | 76 | def parse_call_graph(disassembly, args): 77 | call_graph = {} 78 | current_function = None 79 | 80 | for line in disassembly.splitlines(): 81 | match = re.match(START_FUNCTION, line) 82 | if match: 83 | current_function = match.groups()[1] 84 | call_graph[current_function] = set() 85 | continue 86 | 87 | match = re.match(CALL_FUNCTION, line) 88 | if match and current_function: 89 | call_graph[current_function].add(match.groups()[0]) 90 | 91 | return call_graph 92 | 93 | def parse_top_functions(disassembly, args): 94 | functions = [] 95 | last_function = None 96 | 97 | for line in disassembly.splitlines(): 98 | match = re.match(START_FUNCTION, line) 99 | if match: 100 | (start, function) = match.groups() 101 | start = int(start, 16) 102 | if last_function: 103 | (f, last_start) = last_function 104 | functions.append((f, start - last_start)) 105 | last_function = (function, start) 106 | 107 | top_functions = sorted(functions, key=lambda a: a[1], reverse=True) 108 | return top_functions[:args.top] 109 | 110 | def reverse_call_graph(call_graph, args): 111 | reversed_call_graph = {} 112 | 113 | for function, calls in call_graph.items(): 114 | if function not in reversed_call_graph: 115 | reversed_call_graph[function] = set() 116 | 117 | for call in calls: 118 | if call not in reversed_call_graph: 119 | reversed_call_graph[call] = set() 120 | reversed_call_graph[call].add(function) 121 | 122 | return reversed_call_graph 123 | 124 | def print_callers(reversed_call_graph, args, function=None, depth=0, seen=set()): 125 | if not function: 126 | function = args.function 127 | seen.add(function) 128 | 129 | if depth == 0: 130 | depth += 1 131 | print("{}".format(function)) 132 | if function not in reversed_call_graph: 133 | print(" ") 134 | return 135 | 136 | if args.max_depth is None or depth < args.max_depth: 137 | for caller in reversed_call_graph[function]: 138 | if caller in seen: 139 | continue 140 | 141 | indent = "" 142 | for _ in range(0, depth): 143 | indent += " " 144 | 145 | print("{}⬑ {}".format(indent, caller)) 146 | 147 | print_callers(reversed_call_graph, args, function=caller, depth=depth+1, seen=seen) 148 | 149 | seen.remove(function) 150 | 151 | def main(): 152 | args = parser.parse_args() 153 | disassembly = disassemble(args) 154 | call_graph = parse_call_graph(disassembly, args) 155 | reversed_call_graph = reverse_call_graph(call_graph, args) 156 | 157 | if args.function: 158 | print_callers(reversed_call_graph, args) 159 | elif args.top: 160 | top_functions = parse_top_functions(disassembly, args) 161 | for f, size in top_functions: 162 | print(size, "bytes: ", end="") 163 | print_callers(reversed_call_graph, args, function=f) 164 | else: 165 | raise Exception("Must use one of --function or --top") 166 | 167 | if __name__ == "__main__": 168 | main() 169 | -------------------------------------------------------------------------------- /wasm-mappings/src/comparators.rs: -------------------------------------------------------------------------------- 1 | //! Comparator functions for sorting mappings in different ways. 2 | 3 | use super::{Mapping, OriginalLocation}; 4 | use std::cmp::Ordering; 5 | use std::fmt; 6 | 7 | /// A function that can compare two `T`s. 8 | pub trait ComparatorFunction: fmt::Debug { 9 | /// Compare the given values. 10 | fn compare(&T, &T) -> Ordering; 11 | } 12 | 13 | impl ComparatorFunction> for F 14 | where 15 | F: ComparatorFunction, 16 | { 17 | #[inline] 18 | fn compare(a: &Option, b: &Option) -> Ordering { 19 | match (a, b) { 20 | (&None, &None) => Ordering::Equal, 21 | (&Some(_), &None) => Ordering::Less, 22 | (&None, &Some(_)) => Ordering::Greater, 23 | (&Some(ref a), &Some(ref b)) => F::compare(a, b), 24 | } 25 | } 26 | } 27 | 28 | // Yes, using this style of comparison instead of `cmp.then(cmp2).then(cmp3)` is 29 | // actually a big performance win in practice: 30 | // 31 | // ``` 32 | // $ cargo benchcmp control variable 33 | // name control ns/iter variable ns/iter diff ns/iter diff % speedup 34 | // bench_parse_part_of_scala_js_source_map 2,029,981 1,290,716 -739,265 -36.42% x 1.57 35 | // ``` 36 | // 37 | // This doesn't seem right, but you can't argue with those numbers... 38 | macro_rules! compare { 39 | ($a:expr, $b:expr) => { 40 | let cmp = ($a as i64) - ($b as i64); 41 | if cmp < 0 { 42 | return Ordering::Less; 43 | } else if cmp > 0 { 44 | return Ordering::Greater; 45 | } 46 | } 47 | } 48 | 49 | /// Sort mappings by their generated location, but don't compare generated 50 | /// lines. This is useful for when we know that all mappings being sorted have 51 | /// the same generated line number. 52 | #[derive(Debug)] 53 | pub struct ByGeneratedTail; 54 | 55 | impl ComparatorFunction for ByGeneratedTail { 56 | #[inline] 57 | fn compare(a: &Mapping, b: &Mapping) -> Ordering { 58 | compare!(a.generated_column, b.generated_column); 59 | ByOriginalLocation::compare(&a.original, &b.original) 60 | } 61 | } 62 | 63 | /// Sort mappings by their original locations, breaking ties by their generated 64 | /// locations. 65 | #[derive(Debug)] 66 | pub struct ByOriginalLocation; 67 | 68 | impl ComparatorFunction for ByOriginalLocation { 69 | #[inline] 70 | fn compare(a: &Mapping, b: &Mapping) -> Ordering { 71 | let c = ByOriginalLocation::compare(&a.original, &b.original); 72 | match c { 73 | Ordering::Less | Ordering::Greater => c, 74 | Ordering::Equal => { 75 | compare!(a.generated_line, b.generated_line); 76 | compare!(a.generated_column, b.generated_column); 77 | Ordering::Equal 78 | } 79 | } 80 | } 81 | } 82 | 83 | impl ComparatorFunction for ByOriginalLocation { 84 | #[inline] 85 | fn compare(a: &OriginalLocation, b: &OriginalLocation) -> Ordering { 86 | compare!(a.source, b.source); 87 | compare!(a.original_line, b.original_line); 88 | compare!(a.original_column, b.original_column); 89 | a.name.cmp(&b.name) 90 | } 91 | } 92 | 93 | /// Assuming mappings are in the same original source, sort mappings by their 94 | /// original locations, breaking ties by their generated locations. 95 | #[derive(Debug)] 96 | pub struct ByOriginalLocationSameSource; 97 | 98 | impl ComparatorFunction for ByOriginalLocationSameSource { 99 | #[inline] 100 | fn compare(a: &Mapping, b: &Mapping) -> Ordering { 101 | let c = ByOriginalLocationSameSource::compare(&a.original, &b.original); 102 | match c { 103 | Ordering::Less | Ordering::Greater => c, 104 | Ordering::Equal => { 105 | compare!(a.generated_line, b.generated_line); 106 | compare!(a.generated_column, b.generated_column); 107 | Ordering::Equal 108 | } 109 | } 110 | } 111 | } 112 | 113 | impl ComparatorFunction for ByOriginalLocationSameSource { 114 | #[inline] 115 | fn compare(a: &OriginalLocation, b: &OriginalLocation) -> Ordering { 116 | debug_assert_eq!(a.source, b.source); 117 | compare!(a.original_line, b.original_line); 118 | compare!(a.original_column, b.original_column); 119 | a.name.cmp(&b.name) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /wasm-mappings/tests/tests.rs: -------------------------------------------------------------------------------- 1 | extern crate source_map_mappings; 2 | 3 | use source_map_mappings::{parse_mappings, Bias, Mapping, Mappings, OriginalLocation}; 4 | 5 | #[test] 6 | fn parse_empty_mappings() { 7 | let mut mappings = parse_mappings::<()>(&[]).expect("should parse OK"); 8 | assert!(mappings.by_generated_location().is_empty()); 9 | assert_eq!(mappings.by_original_location().count(), 0); 10 | } 11 | 12 | #[test] 13 | fn invalid_mappings() { 14 | assert!(parse_mappings::<()>(b"...").is_err()); 15 | } 16 | 17 | // From mozilla/source-map's test/util.js `exports.testMap`. 18 | const TEST_MAPPINGS: &'static [u8] = 19 | b"CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA"; 20 | 21 | #[test] 22 | fn can_parse_test_mappings_ok() { 23 | parse_mappings::<()>(TEST_MAPPINGS).unwrap(); 24 | } 25 | 26 | fn assert_generated_location_for( 27 | mappings: &mut Mappings, 28 | source: u32, 29 | original_line: u32, 30 | original_column: u32, 31 | bias: Bias, 32 | expected: Option, 33 | ) { 34 | let actual = mappings.generated_location_for(source, original_line, original_column, bias); 35 | assert_eq!(actual, expected.as_ref()); 36 | } 37 | 38 | fn assert_original_location_for( 39 | mappings: &mut Mappings, 40 | generated_line: u32, 41 | generated_column: u32, 42 | bias: Bias, 43 | expected: Option, 44 | ) { 45 | let actual = mappings.original_location_for(generated_line, generated_column, bias); 46 | assert_eq!(actual, expected.as_ref()); 47 | } 48 | 49 | fn assert_bidirectional(mappings: &mut Mappings, mapping: Mapping) { 50 | let orig = mapping.original.as_ref().unwrap(); 51 | for bias in &[Bias::GreatestLowerBound, Bias::LeastUpperBound] { 52 | assert_generated_location_for( 53 | mappings, 54 | orig.source, 55 | orig.original_line, 56 | orig.original_column, 57 | *bias, 58 | Some(mapping.clone()), 59 | ); 60 | 61 | assert_original_location_for( 62 | mappings, 63 | mapping.generated_line, 64 | mapping.generated_column, 65 | *bias, 66 | Some(mapping.clone()), 67 | ); 68 | } 69 | } 70 | 71 | #[test] 72 | fn test_mapping_back_exactly() { 73 | let mut mappings = parse_mappings::<()>(TEST_MAPPINGS).unwrap(); 74 | 75 | assert_bidirectional( 76 | &mut mappings, 77 | Mapping { 78 | generated_line: 0, 79 | generated_column: 1, 80 | last_generated_column: Some(5), 81 | original: Some(OriginalLocation { 82 | source: 0, 83 | original_line: 0, 84 | original_column: 1, 85 | name: None, 86 | }), 87 | }, 88 | ); 89 | assert_bidirectional( 90 | &mut mappings, 91 | Mapping { 92 | generated_line: 0, 93 | generated_column: 5, 94 | last_generated_column: Some(9), 95 | original: Some(OriginalLocation { 96 | source: 0, 97 | original_line: 0, 98 | original_column: 5, 99 | name: None, 100 | }), 101 | }, 102 | ); 103 | assert_bidirectional( 104 | &mut mappings, 105 | Mapping { 106 | generated_line: 0, 107 | generated_column: 9, 108 | last_generated_column: Some(18), 109 | original: Some(OriginalLocation { 110 | source: 0, 111 | original_line: 0, 112 | original_column: 11, 113 | name: None, 114 | }), 115 | }, 116 | ); 117 | assert_bidirectional( 118 | &mut mappings, 119 | Mapping { 120 | generated_line: 0, 121 | generated_column: 18, 122 | last_generated_column: Some(21), 123 | original: Some(OriginalLocation { 124 | source: 0, 125 | original_line: 0, 126 | original_column: 21, 127 | name: Some(0), 128 | }), 129 | }, 130 | ); 131 | assert_bidirectional( 132 | &mut mappings, 133 | Mapping { 134 | generated_line: 0, 135 | generated_column: 21, 136 | last_generated_column: Some(28), 137 | original: Some(OriginalLocation { 138 | source: 0, 139 | original_line: 1, 140 | original_column: 3, 141 | name: None, 142 | }), 143 | }, 144 | ); 145 | assert_bidirectional( 146 | &mut mappings, 147 | Mapping { 148 | generated_line: 0, 149 | generated_column: 28, 150 | last_generated_column: Some(32), 151 | original: Some(OriginalLocation { 152 | source: 0, 153 | original_line: 1, 154 | original_column: 10, 155 | name: Some(1), 156 | }), 157 | }, 158 | ); 159 | assert_bidirectional( 160 | &mut mappings, 161 | Mapping { 162 | generated_line: 0, 163 | generated_column: 32, 164 | last_generated_column: None, 165 | original: Some(OriginalLocation { 166 | source: 0, 167 | original_line: 1, 168 | original_column: 14, 169 | name: Some(0), 170 | }), 171 | }, 172 | ); 173 | 174 | assert_bidirectional( 175 | &mut mappings, 176 | Mapping { 177 | generated_line: 1, 178 | generated_column: 1, 179 | last_generated_column: Some(5), 180 | original: Some(OriginalLocation { 181 | source: 1, 182 | original_line: 0, 183 | original_column: 1, 184 | name: None, 185 | }), 186 | }, 187 | ); 188 | assert_bidirectional( 189 | &mut mappings, 190 | Mapping { 191 | generated_line: 1, 192 | generated_column: 5, 193 | last_generated_column: Some(9), 194 | original: Some(OriginalLocation { 195 | source: 1, 196 | original_line: 0, 197 | original_column: 5, 198 | name: None, 199 | }), 200 | }, 201 | ); 202 | assert_bidirectional( 203 | &mut mappings, 204 | Mapping { 205 | generated_line: 1, 206 | generated_column: 9, 207 | last_generated_column: Some(18), 208 | original: Some(OriginalLocation { 209 | source: 1, 210 | original_line: 0, 211 | original_column: 11, 212 | name: None, 213 | }), 214 | }, 215 | ); 216 | assert_bidirectional( 217 | &mut mappings, 218 | Mapping { 219 | generated_line: 1, 220 | generated_column: 18, 221 | last_generated_column: Some(21), 222 | original: Some(OriginalLocation { 223 | source: 1, 224 | original_line: 0, 225 | original_column: 21, 226 | name: Some(2), 227 | }), 228 | }, 229 | ); 230 | assert_bidirectional( 231 | &mut mappings, 232 | Mapping { 233 | generated_line: 1, 234 | generated_column: 21, 235 | last_generated_column: Some(28), 236 | original: Some(OriginalLocation { 237 | source: 1, 238 | original_line: 1, 239 | original_column: 3, 240 | name: None, 241 | }), 242 | }, 243 | ); 244 | assert_bidirectional( 245 | &mut mappings, 246 | Mapping { 247 | generated_line: 1, 248 | generated_column: 28, 249 | last_generated_column: None, 250 | original: Some(OriginalLocation { 251 | source: 1, 252 | original_line: 1, 253 | original_column: 10, 254 | name: Some(2), 255 | }), 256 | }, 257 | ); 258 | } 259 | 260 | // From mozilla/source-map's test/test-source-map-consumer.js's "test 261 | // allGeneratedPositionsFor for line" test case. 262 | const TEST_MAPPINGS_2: &'static [u8] = b";EAAC,ACAA;EACA,CAAC;EACD"; 263 | 264 | #[test] 265 | fn test_all_generated_locations_for_some_line() { 266 | let mut mappings = parse_mappings::<()>(TEST_MAPPINGS_2).unwrap(); 267 | 268 | let mappings_on_source_1_line_1: Vec<_> = mappings 269 | .all_generated_locations_for(1, 1, None) 270 | .cloned() 271 | .collect(); 272 | 273 | assert_eq!( 274 | mappings_on_source_1_line_1, 275 | vec![ 276 | Mapping { 277 | generated_line: 2, 278 | generated_column: 2, 279 | last_generated_column: Some(3), 280 | original: Some(OriginalLocation { 281 | source: 1, 282 | original_line: 1, 283 | original_column: 1, 284 | name: None, 285 | }), 286 | }, 287 | Mapping { 288 | generated_line: 2, 289 | generated_column: 3, 290 | last_generated_column: None, 291 | original: Some(OriginalLocation { 292 | source: 1, 293 | original_line: 1, 294 | original_column: 2, 295 | name: None, 296 | }), 297 | }, 298 | ] 299 | ); 300 | } 301 | 302 | // Taken from mozilla/source-map's test/test-source-map-consumer.js's "test 303 | // allGeneratedPositionsFor for line fuzzy" 304 | const TEST_MAPPINGS_3: &'static [u8] = b";EAAC,ACAA;;EAEA"; 305 | 306 | #[test] 307 | fn test_all_generated_locations_for_line_fuzzy() { 308 | let mut mappings = parse_mappings::<()>(TEST_MAPPINGS_3).unwrap(); 309 | 310 | let mappings_on_source_1_line_1: Vec<_> = mappings 311 | .all_generated_locations_for(1, 1, None) 312 | .cloned() 313 | .collect(); 314 | 315 | assert_eq!( 316 | mappings_on_source_1_line_1, 317 | vec![ 318 | Mapping { 319 | generated_line: 3, 320 | generated_column: 2, 321 | last_generated_column: None, 322 | original: Some(OriginalLocation { 323 | source: 1, 324 | original_line: 2, 325 | original_column: 1, 326 | name: None, 327 | }), 328 | }, 329 | ] 330 | ); 331 | } 332 | 333 | // Taken from mozilla/source-map's test/test-source-map-consumer.js's "test 334 | // allGeneratedPositionsFor for column". 335 | const TEST_MAPPINGS_4: &'static [u8] = b"EAAC,CAAA"; 336 | 337 | #[test] 338 | fn test_all_generated_locations_for_column() { 339 | let mut mappings = parse_mappings::<()>(TEST_MAPPINGS_4).unwrap(); 340 | 341 | let mappings_on_source_0_line_0_column_1: Vec<_> = mappings 342 | .all_generated_locations_for(0, 0, Some(1)) 343 | .cloned() 344 | .collect(); 345 | 346 | assert_eq!( 347 | mappings_on_source_0_line_0_column_1, 348 | vec![ 349 | Mapping { 350 | generated_line: 0, 351 | generated_column: 2, 352 | last_generated_column: Some(3), 353 | original: Some(OriginalLocation { 354 | source: 0, 355 | original_line: 0, 356 | original_column: 1, 357 | name: None, 358 | }), 359 | }, 360 | Mapping { 361 | generated_line: 0, 362 | generated_column: 3, 363 | last_generated_column: None, 364 | original: Some(OriginalLocation { 365 | source: 0, 366 | original_line: 0, 367 | original_column: 1, 368 | name: None, 369 | }), 370 | }, 371 | ] 372 | ); 373 | } 374 | 375 | #[test] 376 | fn test_all_generated_locations_for_column_fuzzy() { 377 | let mut mappings = parse_mappings::<()>(TEST_MAPPINGS_4).unwrap(); 378 | 379 | let mappings_on_source_0_line_0_column_0: Vec<_> = mappings 380 | .all_generated_locations_for(0, 0, Some(0)) 381 | .cloned() 382 | .collect(); 383 | 384 | assert_eq!( 385 | mappings_on_source_0_line_0_column_0, 386 | vec![ 387 | Mapping { 388 | generated_line: 0, 389 | generated_column: 2, 390 | last_generated_column: Some(3), 391 | original: Some(OriginalLocation { 392 | source: 0, 393 | original_line: 0, 394 | original_column: 1, 395 | name: None, 396 | }), 397 | }, 398 | Mapping { 399 | generated_line: 0, 400 | generated_column: 3, 401 | last_generated_column: None, 402 | original: Some(OriginalLocation { 403 | source: 0, 404 | original_line: 0, 405 | original_column: 1, 406 | name: None, 407 | }), 408 | }, 409 | ] 410 | ); 411 | } 412 | 413 | // From mozilla/source-map's test/test-source-map-consumer.js's "test 414 | // allGeneratedPositionsFor for column on different line fuzzy". 415 | const TEST_MAPPINGS_5: &'static [u8] = b";EACC,CAAA"; 416 | 417 | #[test] 418 | fn test_all_generated_locations_for_column_on_different_line_fuzzy() { 419 | let mut mappings = parse_mappings::<()>(TEST_MAPPINGS_5).unwrap(); 420 | 421 | let mappings_on_source_0_line_0_column_0: Vec<_> = mappings 422 | .all_generated_locations_for(0, 0, Some(0)) 423 | .cloned() 424 | .collect(); 425 | 426 | assert!(mappings_on_source_0_line_0_column_0.is_empty()); 427 | } 428 | --------------------------------------------------------------------------------