├── .editorconfig ├── .eslintrc.js ├── .gitattributes ├── .github └── workflows │ └── nodejs.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── jest.config.js ├── package-lock.json ├── package.json ├── prettier.config.js ├── renovate.json ├── rollup.config.js ├── src ├── build-source-map-tree.ts ├── remapping.ts ├── source-map-tree.ts ├── source-map.ts └── types.ts ├── test ├── samples │ ├── null-source │ │ └── test.ts │ ├── sourceless-transform │ │ └── test.ts │ ├── transpile-concat-hires │ │ ├── babel.config.js │ │ ├── build.js │ │ ├── build.sh │ │ ├── files │ │ │ ├── bundle.js │ │ │ ├── bundle.js.map │ │ │ ├── main.js │ │ │ ├── main.js.map │ │ │ ├── main.mjs │ │ │ ├── placeholder.js │ │ │ ├── placeholder.js.map │ │ │ └── placeholder.mjs │ │ └── test.ts │ ├── transpile-concat-lowres │ │ ├── babel.config.js │ │ ├── build.js │ │ ├── build.sh │ │ ├── files │ │ │ ├── bundle.js │ │ │ ├── bundle.js.map │ │ │ ├── main.js │ │ │ ├── main.js.map │ │ │ ├── main.mjs │ │ │ ├── placeholder.js │ │ │ ├── placeholder.js.map │ │ │ └── placeholder.mjs │ │ └── test.ts │ ├── transpile-minify │ │ ├── babel.config.js │ │ ├── build.sh │ │ ├── files │ │ │ ├── helloworld.js │ │ │ ├── helloworld.js.map │ │ │ ├── helloworld.min.js │ │ │ ├── helloworld.min.js.map │ │ │ └── helloworld.mjs │ │ └── test.ts │ └── transpile-rollup │ │ ├── babel.config.js │ │ ├── build.sh │ │ ├── files │ │ ├── a.js │ │ ├── a.js.map │ │ ├── a.mjs │ │ ├── b.js │ │ ├── b.js.map │ │ ├── b.mjs │ │ ├── bundle.js │ │ ├── bundle.js.map │ │ ├── index.js │ │ ├── index.js.map │ │ └── index.mjs │ │ └── test.ts └── unit │ ├── build-source-map-tree.ts │ ├── remapping.ts │ ├── source-map-tree.ts │ └── source-map.ts ├── tsconfig.build.json └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | #root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | max_line_length = 100 10 | indent_size = 2 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | plugins: ['@typescript-eslint'], 5 | extends: [ 6 | 'eslint:recommended', 7 | 'plugin:@typescript-eslint/recommended', 8 | 'prettier', 9 | ], 10 | rules: { 11 | '@typescript-eslint/consistent-type-imports': 'error', 12 | '@typescript-eslint/no-duplicate-imports': 'error', 13 | '@typescript-eslint/no-explicit-any': 'off', 14 | '@typescript-eslint/no-non-null-assertion': 'off', 15 | '@typescript-eslint/no-unused-vars': [ 16 | 'error', 17 | { 18 | argsIgnorePattern: '^_', 19 | }, 20 | ], 21 | 'no-constant-condition': 'off', 22 | 'no-unused-labels': 'off', 23 | }, 24 | overrides: [ 25 | { 26 | files: ['test/**/*.ts'], 27 | rules: { 28 | '@typescript-eslint/no-empty-function': 'off', 29 | '@typescript-eslint/no-explicit-any': 'off', 30 | }, 31 | }, 32 | ], 33 | }; 34 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /test/samples/** text eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [12.x, 14.x, 16.x] 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - name: npm install and test 21 | run: | 22 | npm install 23 | npm test 24 | npm run build 25 | env: 26 | CI: true 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | dist 4 | .rpt2_cache 5 | .vscode 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | See https://github.com/ampproject/meta/blob/master/CODE_OF_CONDUCT.md 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows [Google's Open Source Community 28 | Guidelines](https://opensource.google.com/conduct/). -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @ampproject/remapping 2 | 3 | > Remap sequential sourcemaps through transformations to point at the original source code 4 | 5 | Remapping allows you to take the sourcemaps generated through transforming your code and "remap" 6 | them to the original source locations. Think "my minified code, transformed with babel and bundled 7 | with webpack", all pointing to the correct location in your original source code. 8 | 9 | With remapping, none of your source code transformations need to be aware of the input's sourcemap, 10 | they only need to generate an output sourcemap. This greatly simplifies building custom 11 | transformations (think a find-and-replace). 12 | 13 | ## Installation 14 | 15 | ```sh 16 | npm install @ampproject/remapping 17 | ``` 18 | 19 | ## Usage 20 | 21 | ```typescript 22 | function remapping( 23 | map: SourceMap | SourceMap[], 24 | loader: (file: string, ctx: LoaderContext) => (SourceMap | null | undefined), 25 | options?: { excludeContent: boolean, decodedMappings: boolean } 26 | ): SourceMap; 27 | 28 | // LoaderContext gives the loader the importing sourcemap, tree depth, the ability to override the 29 | // "source" location (where child sources are resolved relative to, or the location of original 30 | // source), and the ability to override the "content" of an original source for inclusion in the 31 | // output sourcemap. 32 | type LoaderContext = { 33 | readonly importer: string; 34 | readonly depth: number; 35 | source: string; 36 | content: string | null | undefined; 37 | } 38 | ``` 39 | 40 | `remapping` takes the final output sourcemap, and a `loader` function. For every source file pointer 41 | in the sourcemap, the `loader` will be called with the resolved path. If the path itself represents 42 | a transformed file (it has a sourcmap associated with it), then the `loader` should return that 43 | sourcemap. If not, the path will be treated as an original, untransformed source code. 44 | 45 | ```js 46 | // Babel transformed "helloworld.js" into "transformed.js" 47 | const transformedMap = JSON.stringify({ 48 | file: 'transformed.js', 49 | // 1st column of 2nd line of output file translates into the 1st source 50 | // file, line 3, column 2 51 | mappings: ';CAEE', 52 | sources: ['helloworld.js'], 53 | version: 3, 54 | }); 55 | 56 | // Uglify minified "transformed.js" into "transformed.min.js" 57 | const minifiedTransformedMap = JSON.stringify({ 58 | file: 'transformed.min.js', 59 | // 0th column of 1st line of output file translates into the 1st source 60 | // file, line 2, column 1. 61 | mappings: 'AACC', 62 | names: [], 63 | sources: ['transformed.js'], 64 | version: 3, 65 | }); 66 | 67 | const remapped = remapping( 68 | minifiedTransformedMap, 69 | (file, ctx) => { 70 | 71 | // The "transformed.js" file is an transformed file. 72 | if (file === 'transformed.js') { 73 | // The root importer is empty. 74 | console.assert(ctx.importer === ''); 75 | // The depth in the sourcemap tree we're currently loading. 76 | // The root `minifiedTransformedMap` is depth 0, and its source children are depth 1, etc. 77 | console.assert(ctx.depth === 1); 78 | 79 | return transformedMap; 80 | } 81 | 82 | // Loader will be called to load transformedMap's source file pointers as well. 83 | console.assert(file === 'helloworld.js'); 84 | // `transformed.js`'s sourcemap points into `helloworld.js`. 85 | console.assert(ctx.importer === 'transformed.js'); 86 | // This is a source child of `transformed`, which is a source child of `minifiedTransformedMap`. 87 | console.assert(ctx.depth === 2); 88 | return null; 89 | } 90 | ); 91 | 92 | console.log(remapped); 93 | // { 94 | // file: 'transpiled.min.js', 95 | // mappings: 'AAEE', 96 | // sources: ['helloworld.js'], 97 | // version: 3, 98 | // }; 99 | ``` 100 | 101 | In this example, `loader` will be called twice: 102 | 103 | 1. `"transformed.js"`, the first source file pointer in the `minifiedTransformedMap`. We return the 104 | associated sourcemap for it (its a transformed file, after all) so that sourcemap locations can 105 | be traced through it into the source files it represents. 106 | 2. `"helloworld.js"`, our original, unmodified source code. This file does not have a sourcemap, so 107 | we return `null`. 108 | 109 | The `remapped` sourcemap now points from `transformed.min.js` into locations in `helloworld.js`. If 110 | you were to read the `mappings`, it says "0th column of the first line output line points to the 1st 111 | column of the 2nd line of the file `helloworld.js`". 112 | 113 | ### Multiple transformations of a file 114 | 115 | As a convenience, if you have multiple single-source transformations of a file, you may pass an 116 | array of sourcemap files in the order of most-recent transformation sourcemap first. Note that this 117 | changes the `importer` and `depth` of each call to our loader. So our above example could have been 118 | written as: 119 | 120 | ```js 121 | const remapped = remapping( 122 | [minifiedTransformedMap, transformedMap], 123 | () => null 124 | ); 125 | 126 | console.log(remapped); 127 | // { 128 | // file: 'transpiled.min.js', 129 | // mappings: 'AAEE', 130 | // sources: ['helloworld.js'], 131 | // version: 3, 132 | // }; 133 | ``` 134 | 135 | ### Advanced control of the loading graph 136 | 137 | #### `source` 138 | 139 | The `source` property can overridden to any value to change the location of the current load. Eg, 140 | for an original source file, it allows us to change the location to the original source regardless 141 | of what the sourcemap source entry says. And for transformed files, it allows us to change the 142 | relative resolving location for child sources of the loaded sourcemap. 143 | 144 | ```js 145 | const remapped = remapping( 146 | minifiedTransformedMap, 147 | (file, ctx) => { 148 | 149 | if (file === 'transformed.js') { 150 | // We pretend the transformed.js file actually exists in the 'src/' directory. When the nested 151 | // source files are loaded, they will now be relative to `src/`. 152 | ctx.source = 'src/transformed.js'; 153 | return transformedMap; 154 | } 155 | 156 | console.assert(file === 'src/helloworld.js'); 157 | // We could futher change the source of this original file, eg, to be inside a nested directory 158 | // itself. This will be reflected in the remapped sourcemap. 159 | ctx.source = 'src/nested/transformed.js'; 160 | return null; 161 | } 162 | ); 163 | 164 | console.log(remapped); 165 | // { 166 | // …, 167 | // sources: ['src/nested/helloworld.js'], 168 | // }; 169 | ``` 170 | 171 | 172 | #### `content` 173 | 174 | The `content` property can be overridden when we encounter an original source file. Eg, this allows 175 | you to manually provide the source content of the original file regardless of whether the 176 | `sourcesContent` field is present in the parent sourcemap. It can also be set to `null` to remove 177 | the source content. 178 | 179 | ```js 180 | const remapped = remapping( 181 | minifiedTransformedMap, 182 | (file, ctx) => { 183 | 184 | if (file === 'transformed.js') { 185 | // transformedMap does not include a `sourcesContent` field, so usually the remapped sourcemap 186 | // would not include any `sourcesContent` values. 187 | return transformedMap; 188 | } 189 | 190 | console.assert(file === 'helloworld.js'); 191 | // We can read the file to provide the source content. 192 | ctx.content = fs.readFileSync(file, 'utf8'); 193 | return null; 194 | } 195 | ); 196 | 197 | console.log(remapped); 198 | // { 199 | // …, 200 | // sourcesContent: [ 201 | // 'console.log("Hello world!")', 202 | // ], 203 | // }; 204 | ``` 205 | 206 | ### Options 207 | 208 | #### excludeContent 209 | 210 | By default, `excludeContent` is `false`. Passing `{ excludeContent: true }` will exclude the 211 | `sourcesContent` field from the returned sourcemap. This is mainly useful when you want to reduce 212 | the size out the sourcemap. 213 | 214 | #### decodedMappings 215 | 216 | By default, `decodedMappings` is `false`. Passing `{ decodedMappings: true }` will leave the 217 | `mappings` field in a [decoded state](https://github.com/rich-harris/sourcemap-codec) instead of 218 | encoding into a VLQ string. 219 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { 3 | '.ts': 'ts-jest', 4 | }, 5 | testMatch: ['**/test/**/*.ts'], 6 | moduleFileExtensions: ['ts', 'js'], 7 | coveragePathIgnorePatterns: ['/node_modules/', '/test/'], 8 | coverageThreshold: { 9 | global: { 10 | branches: 90, 11 | functions: 95, 12 | lines: 95, 13 | statements: 95, 14 | }, 15 | }, 16 | collectCoverageFrom: ['src/**/*.ts'], 17 | }; 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ampproject/remapping", 3 | "version": "2.3.0", 4 | "description": "Remap sequential sourcemaps through transformations to point at the original source code", 5 | "keywords": [ 6 | "source", 7 | "map", 8 | "remap" 9 | ], 10 | "main": "dist/remapping.umd.js", 11 | "module": "dist/remapping.mjs", 12 | "types": "dist/types/remapping.d.ts", 13 | "exports": { 14 | ".": [ 15 | { 16 | "types": "./dist/types/remapping.d.ts", 17 | "browser": "./dist/remapping.umd.js", 18 | "require": "./dist/remapping.umd.js", 19 | "import": "./dist/remapping.mjs" 20 | }, 21 | "./dist/remapping.umd.js" 22 | ], 23 | "./package.json": "./package.json" 24 | }, 25 | "files": [ 26 | "dist" 27 | ], 28 | "author": "Justin Ridgewell ", 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/ampproject/remapping.git" 32 | }, 33 | "license": "Apache-2.0", 34 | "engines": { 35 | "node": ">=6.0.0" 36 | }, 37 | "scripts": { 38 | "build": "run-s -n build:*", 39 | "build:rollup": "rollup -c rollup.config.js", 40 | "build:ts": "tsc --project tsconfig.build.json", 41 | "lint": "run-s -n lint:*", 42 | "lint:prettier": "npm run test:lint:prettier -- --write", 43 | "lint:ts": "npm run test:lint:ts -- --fix", 44 | "prebuild": "rm -rf dist", 45 | "prepublishOnly": "npm run preversion", 46 | "preversion": "run-s test build", 47 | "test": "run-s -n test:lint test:only", 48 | "test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand", 49 | "test:lint": "run-s -n test:lint:*", 50 | "test:lint:prettier": "prettier --check '{src,test}/**/*.ts'", 51 | "test:lint:ts": "eslint '{src,test}/**/*.ts'", 52 | "test:only": "jest --coverage", 53 | "test:watch": "jest --coverage --watch" 54 | }, 55 | "devDependencies": { 56 | "@rollup/plugin-typescript": "8.3.2", 57 | "@types/jest": "27.4.1", 58 | "@typescript-eslint/eslint-plugin": "5.20.0", 59 | "@typescript-eslint/parser": "5.20.0", 60 | "eslint": "8.14.0", 61 | "eslint-config-prettier": "8.5.0", 62 | "jest": "27.5.1", 63 | "jest-config": "27.5.1", 64 | "npm-run-all": "4.1.5", 65 | "prettier": "2.6.2", 66 | "rollup": "2.70.2", 67 | "ts-jest": "27.1.4", 68 | "tslib": "2.4.0", 69 | "typescript": "4.6.3" 70 | }, 71 | "dependencies": { 72 | "@jridgewell/gen-mapping": "^0.3.5", 73 | "@jridgewell/trace-mapping": "^0.3.24" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | endOfLine: 'lf', 3 | printWidth: 100, 4 | singleQuote: true, 5 | trailingComma: 'es5', 6 | }; 7 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from '@rollup/plugin-typescript'; 2 | 3 | function configure(esm) { 4 | return { 5 | input: 'src/remapping.ts', 6 | output: esm 7 | ? { format: 'es', dir: 'dist', entryFileNames: '[name].mjs', sourcemap: true } 8 | : { 9 | format: 'umd', 10 | name: 'remapping', 11 | dir: 'dist', 12 | entryFileNames: '[name].umd.js', 13 | sourcemap: true, 14 | globals: { 15 | '@jridgewell/gen-mapping': 'genMapping', 16 | '@jridgewell/trace-mapping': 'traceMapping', 17 | }, 18 | }, 19 | plugins: [typescript({ tsconfig: './tsconfig.build.json' })], 20 | watch: { 21 | include: 'src/**', 22 | }, 23 | }; 24 | } 25 | 26 | export default [configure(false), configure(true)]; 27 | -------------------------------------------------------------------------------- /src/build-source-map-tree.ts: -------------------------------------------------------------------------------- 1 | import { TraceMap } from '@jridgewell/trace-mapping'; 2 | 3 | import { OriginalSource, MapSource } from './source-map-tree'; 4 | 5 | import type { Sources, MapSource as MapSourceType } from './source-map-tree'; 6 | import type { SourceMapInput, SourceMapLoader, LoaderContext } from './types'; 7 | 8 | function asArray(value: T | T[]): T[] { 9 | if (Array.isArray(value)) return value; 10 | return [value]; 11 | } 12 | 13 | /** 14 | * Recursively builds a tree structure out of sourcemap files, with each node 15 | * being either an `OriginalSource` "leaf" or a `SourceMapTree` composed of 16 | * `OriginalSource`s and `SourceMapTree`s. 17 | * 18 | * Every sourcemap is composed of a collection of source files and mappings 19 | * into locations of those source files. When we generate a `SourceMapTree` for 20 | * the sourcemap, we attempt to load each source file's own sourcemap. If it 21 | * does not have an associated sourcemap, it is considered an original, 22 | * unmodified source file. 23 | */ 24 | export default function buildSourceMapTree( 25 | input: SourceMapInput | SourceMapInput[], 26 | loader: SourceMapLoader 27 | ): MapSourceType { 28 | const maps = asArray(input).map((m) => new TraceMap(m, '')); 29 | const map = maps.pop()!; 30 | 31 | for (let i = 0; i < maps.length; i++) { 32 | if (maps[i].sources.length > 1) { 33 | throw new Error( 34 | `Transformation map ${i} must have exactly one source file.\n` + 35 | 'Did you specify these with the most recent transformation maps first?' 36 | ); 37 | } 38 | } 39 | 40 | let tree = build(map, loader, '', 0); 41 | for (let i = maps.length - 1; i >= 0; i--) { 42 | tree = MapSource(maps[i], [tree]); 43 | } 44 | return tree; 45 | } 46 | 47 | function build( 48 | map: TraceMap, 49 | loader: SourceMapLoader, 50 | importer: string, 51 | importerDepth: number 52 | ): MapSourceType { 53 | const { resolvedSources, sourcesContent, ignoreList } = map; 54 | 55 | const depth = importerDepth + 1; 56 | const children = resolvedSources.map((sourceFile: string | null, i: number): Sources => { 57 | // The loading context gives the loader more information about why this file is being loaded 58 | // (eg, from which importer). It also allows the loader to override the location of the loaded 59 | // sourcemap/original source, or to override the content in the sourcesContent field if it's 60 | // an unmodified source file. 61 | const ctx: LoaderContext = { 62 | importer, 63 | depth, 64 | source: sourceFile || '', 65 | content: undefined, 66 | ignore: undefined, 67 | }; 68 | 69 | // Use the provided loader callback to retrieve the file's sourcemap. 70 | // TODO: We should eventually support async loading of sourcemap files. 71 | const sourceMap = loader(ctx.source, ctx); 72 | 73 | const { source, content, ignore } = ctx; 74 | 75 | // If there is a sourcemap, then we need to recurse into it to load its source files. 76 | if (sourceMap) return build(new TraceMap(sourceMap, source), loader, source, depth); 77 | 78 | // Else, it's an unmodified source file. 79 | // The contents of this unmodified source file can be overridden via the loader context, 80 | // allowing it to be explicitly null or a string. If it remains undefined, we fall back to 81 | // the importing sourcemap's `sourcesContent` field. 82 | const sourceContent = 83 | content !== undefined ? content : sourcesContent ? sourcesContent[i] : null; 84 | const ignored = ignore !== undefined ? ignore : ignoreList ? ignoreList.includes(i) : false; 85 | return OriginalSource(source, sourceContent, ignored); 86 | }); 87 | 88 | return MapSource(map, children); 89 | } 90 | -------------------------------------------------------------------------------- /src/remapping.ts: -------------------------------------------------------------------------------- 1 | import buildSourceMapTree from './build-source-map-tree'; 2 | import { traceMappings } from './source-map-tree'; 3 | import SourceMap from './source-map'; 4 | 5 | import type { SourceMapInput, SourceMapLoader, Options } from './types'; 6 | export type { 7 | SourceMapSegment, 8 | EncodedSourceMap, 9 | EncodedSourceMap as RawSourceMap, 10 | DecodedSourceMap, 11 | SourceMapInput, 12 | SourceMapLoader, 13 | LoaderContext, 14 | Options, 15 | } from './types'; 16 | export type { SourceMap }; 17 | 18 | /** 19 | * Traces through all the mappings in the root sourcemap, through the sources 20 | * (and their sourcemaps), all the way back to the original source location. 21 | * 22 | * `loader` will be called every time we encounter a source file. If it returns 23 | * a sourcemap, we will recurse into that sourcemap to continue the trace. If 24 | * it returns a falsey value, that source file is treated as an original, 25 | * unmodified source file. 26 | * 27 | * Pass `excludeContent` to exclude any self-containing source file content 28 | * from the output sourcemap. 29 | * 30 | * Pass `decodedMappings` to receive a SourceMap with decoded (instead of 31 | * VLQ encoded) mappings. 32 | */ 33 | export default function remapping( 34 | input: SourceMapInput | SourceMapInput[], 35 | loader: SourceMapLoader, 36 | options?: boolean | Options 37 | ): SourceMap { 38 | const opts = 39 | typeof options === 'object' ? options : { excludeContent: !!options, decodedMappings: false }; 40 | const tree = buildSourceMapTree(input, loader); 41 | return new SourceMap(traceMappings(tree), opts); 42 | } 43 | -------------------------------------------------------------------------------- /src/source-map-tree.ts: -------------------------------------------------------------------------------- 1 | import { GenMapping, maybeAddSegment, setIgnore, setSourceContent } from '@jridgewell/gen-mapping'; 2 | import { traceSegment, decodedMappings } from '@jridgewell/trace-mapping'; 3 | 4 | import type { TraceMap } from '@jridgewell/trace-mapping'; 5 | 6 | export type SourceMapSegmentObject = { 7 | column: number; 8 | line: number; 9 | name: string; 10 | source: string; 11 | content: string | null; 12 | ignore: boolean; 13 | }; 14 | 15 | export type OriginalSource = { 16 | map: null; 17 | sources: Sources[]; 18 | source: string; 19 | content: string | null; 20 | ignore: boolean; 21 | }; 22 | 23 | export type MapSource = { 24 | map: TraceMap; 25 | sources: Sources[]; 26 | source: string; 27 | content: null; 28 | ignore: false; 29 | }; 30 | 31 | export type Sources = OriginalSource | MapSource; 32 | 33 | const SOURCELESS_MAPPING = /* #__PURE__ */ SegmentObject('', -1, -1, '', null, false); 34 | const EMPTY_SOURCES: Sources[] = []; 35 | 36 | function SegmentObject( 37 | source: string, 38 | line: number, 39 | column: number, 40 | name: string, 41 | content: string | null, 42 | ignore: boolean 43 | ): SourceMapSegmentObject { 44 | return { source, line, column, name, content, ignore }; 45 | } 46 | 47 | function Source( 48 | map: TraceMap, 49 | sources: Sources[], 50 | source: '', 51 | content: null, 52 | ignore: false 53 | ): MapSource; 54 | function Source( 55 | map: null, 56 | sources: Sources[], 57 | source: string, 58 | content: string | null, 59 | ignore: boolean 60 | ): OriginalSource; 61 | function Source( 62 | map: TraceMap | null, 63 | sources: Sources[], 64 | source: string | '', 65 | content: string | null, 66 | ignore: boolean 67 | ): Sources { 68 | return { 69 | map, 70 | sources, 71 | source, 72 | content, 73 | ignore, 74 | } as any; 75 | } 76 | 77 | /** 78 | * MapSource represents a single sourcemap, with the ability to trace mappings into its child nodes 79 | * (which may themselves be SourceMapTrees). 80 | */ 81 | export function MapSource(map: TraceMap, sources: Sources[]): MapSource { 82 | return Source(map, sources, '', null, false); 83 | } 84 | 85 | /** 86 | * A "leaf" node in the sourcemap tree, representing an original, unmodified source file. Recursive 87 | * segment tracing ends at the `OriginalSource`. 88 | */ 89 | export function OriginalSource( 90 | source: string, 91 | content: string | null, 92 | ignore: boolean 93 | ): OriginalSource { 94 | return Source(null, EMPTY_SOURCES, source, content, ignore); 95 | } 96 | 97 | /** 98 | * traceMappings is only called on the root level SourceMapTree, and begins the process of 99 | * resolving each mapping in terms of the original source files. 100 | */ 101 | export function traceMappings(tree: MapSource): GenMapping { 102 | // TODO: Eventually support sourceRoot, which has to be removed because the sources are already 103 | // fully resolved. We'll need to make sources relative to the sourceRoot before adding them. 104 | const gen = new GenMapping({ file: tree.map.file }); 105 | const { sources: rootSources, map } = tree; 106 | const rootNames = map.names; 107 | const rootMappings = decodedMappings(map); 108 | 109 | for (let i = 0; i < rootMappings.length; i++) { 110 | const segments = rootMappings[i]; 111 | 112 | for (let j = 0; j < segments.length; j++) { 113 | const segment = segments[j]; 114 | const genCol = segment[0]; 115 | let traced: SourceMapSegmentObject | null = SOURCELESS_MAPPING; 116 | 117 | // 1-length segments only move the current generated column, there's no source information 118 | // to gather from it. 119 | if (segment.length !== 1) { 120 | const source = rootSources[segment[1]]; 121 | traced = originalPositionFor( 122 | source, 123 | segment[2], 124 | segment[3], 125 | segment.length === 5 ? rootNames[segment[4]] : '' 126 | ); 127 | 128 | // If the trace is invalid, then the trace ran into a sourcemap that doesn't contain a 129 | // respective segment into an original source. 130 | if (traced == null) continue; 131 | } 132 | 133 | const { column, line, name, content, source, ignore } = traced; 134 | 135 | maybeAddSegment(gen, i, genCol, source, line, column, name); 136 | if (source && content != null) setSourceContent(gen, source, content); 137 | if (ignore) setIgnore(gen, source, true); 138 | } 139 | } 140 | 141 | return gen; 142 | } 143 | 144 | /** 145 | * originalPositionFor is only called on children SourceMapTrees. It recurses down into its own 146 | * child SourceMapTrees, until we find the original source map. 147 | */ 148 | export function originalPositionFor( 149 | source: Sources, 150 | line: number, 151 | column: number, 152 | name: string 153 | ): SourceMapSegmentObject | null { 154 | if (!source.map) { 155 | return SegmentObject(source.source, line, column, name, source.content, source.ignore); 156 | } 157 | 158 | const segment = traceSegment(source.map, line, column); 159 | 160 | // If we couldn't find a segment, then this doesn't exist in the sourcemap. 161 | if (segment == null) return null; 162 | // 1-length segments only move the current generated column, there's no source information 163 | // to gather from it. 164 | if (segment.length === 1) return SOURCELESS_MAPPING; 165 | 166 | return originalPositionFor( 167 | source.sources[segment[1]], 168 | segment[2], 169 | segment[3], 170 | segment.length === 5 ? source.map.names[segment[4]] : name 171 | ); 172 | } 173 | -------------------------------------------------------------------------------- /src/source-map.ts: -------------------------------------------------------------------------------- 1 | import { toDecodedMap, toEncodedMap } from '@jridgewell/gen-mapping'; 2 | 3 | import type { GenMapping } from '@jridgewell/gen-mapping'; 4 | import type { DecodedSourceMap, EncodedSourceMap, Options } from './types'; 5 | 6 | /** 7 | * A SourceMap v3 compatible sourcemap, which only includes fields that were 8 | * provided to it. 9 | */ 10 | export default class SourceMap { 11 | declare file?: string | null; 12 | declare mappings: EncodedSourceMap['mappings'] | DecodedSourceMap['mappings']; 13 | declare sourceRoot?: string; 14 | declare names: string[]; 15 | declare sources: (string | null)[]; 16 | declare sourcesContent?: (string | null)[]; 17 | declare version: 3; 18 | declare ignoreList: number[] | undefined; 19 | 20 | constructor(map: GenMapping, options: Options) { 21 | const out = options.decodedMappings ? toDecodedMap(map) : toEncodedMap(map); 22 | this.version = out.version; // SourceMap spec says this should be first. 23 | this.file = out.file; 24 | this.mappings = out.mappings as SourceMap['mappings']; 25 | this.names = out.names as SourceMap['names']; 26 | this.ignoreList = out.ignoreList as SourceMap['ignoreList']; 27 | this.sourceRoot = out.sourceRoot; 28 | 29 | this.sources = out.sources as SourceMap['sources']; 30 | if (!options.excludeContent) { 31 | this.sourcesContent = out.sourcesContent as SourceMap['sourcesContent']; 32 | } 33 | } 34 | 35 | toString(): string { 36 | return JSON.stringify(this); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type { SourceMapInput } from '@jridgewell/trace-mapping'; 2 | 3 | export type { 4 | SourceMapSegment, 5 | DecodedSourceMap, 6 | EncodedSourceMap, 7 | } from '@jridgewell/trace-mapping'; 8 | 9 | export type { SourceMapInput }; 10 | 11 | export type LoaderContext = { 12 | readonly importer: string; 13 | readonly depth: number; 14 | source: string; 15 | content: string | null | undefined; 16 | ignore: boolean | undefined; 17 | }; 18 | 19 | export type SourceMapLoader = ( 20 | file: string, 21 | ctx: LoaderContext 22 | ) => SourceMapInput | null | undefined | void; 23 | 24 | export type Options = { 25 | excludeContent?: boolean; 26 | decodedMappings?: boolean; 27 | }; 28 | -------------------------------------------------------------------------------- /test/samples/null-source/test.ts: -------------------------------------------------------------------------------- 1 | import type { RawSourceMap } from 'source-map'; 2 | import { SourceMapConsumer } from 'source-map'; 3 | import remapping from '../../../src/remapping'; 4 | 5 | describe('null-source segement', () => { 6 | const original: any = { 7 | version: '3', 8 | sources: ['source.ts'], 9 | names: [], 10 | mappings: 'AAAA,qC,aACA', 11 | sourcesContent: ["function say(msg) {console.log(msg)};say('hello');\nprocess.exit(1);"], 12 | }; 13 | const minified: any = { 14 | version: '3', 15 | sources: ['source.js'], 16 | names: ['say', 'msg', 'console', 'log', 'process', 'exit'], 17 | mappings: 'AAAA,SAASA,IAAIC,GAAMC,QAAQC,IAAIF,GAAMD,IAAI,SAASI,QAAQC,KAAK', 18 | }; 19 | 20 | test('minified code keeps null-source segment', () => { 21 | const remapped = remapping([minified, original], () => null); 22 | const consumer = new SourceMapConsumer(remapped as unknown as RawSourceMap); 23 | 24 | const console = consumer.originalPositionFor({ 25 | column: 20, 26 | line: 1, 27 | }); 28 | expect(console).toMatchObject({ 29 | column: 0, 30 | line: 1, 31 | source: 'source.ts', 32 | }); 33 | 34 | const say = consumer.originalPositionFor({ 35 | column: 38, 36 | line: 1, 37 | }); 38 | expect(say).toMatchObject({ 39 | column: null, 40 | line: null, 41 | source: null, 42 | }); 43 | 44 | const exit = consumer.originalPositionFor({ 45 | column: 53, 46 | line: 1, 47 | }); 48 | expect(exit).toMatchObject({ 49 | column: 0, 50 | line: 2, 51 | source: 'source.ts', 52 | }); 53 | }); 54 | 55 | test('null-source', () => { 56 | const remapped = remapping(original, () => null); 57 | const consumer = new SourceMapConsumer(remapped as unknown as RawSourceMap); 58 | 59 | const console = consumer.originalPositionFor({ 60 | column: 20, 61 | line: 1, 62 | }); 63 | expect(console).toMatchObject({ 64 | column: 0, 65 | line: 1, 66 | source: 'source.ts', 67 | }); 68 | 69 | const say = consumer.originalPositionFor({ 70 | column: 38, 71 | line: 1, 72 | }); 73 | expect(say).toMatchObject({ 74 | column: null, 75 | line: null, 76 | source: null, 77 | }); 78 | 79 | const exit = consumer.originalPositionFor({ 80 | column: 53, 81 | line: 1, 82 | }); 83 | expect(exit).toMatchObject({ 84 | column: 0, 85 | line: 2, 86 | source: 'source.ts', 87 | }); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /test/samples/sourceless-transform/test.ts: -------------------------------------------------------------------------------- 1 | import remapping from '../../../src/remapping'; 2 | 3 | describe('source-less transform', () => { 4 | const original: any = { 5 | version: '3', 6 | sources: ['source.ts'], 7 | names: [], 8 | mappings: 'AAAA', 9 | sourcesContent: ['// hello'], 10 | }; 11 | const minified: any = { 12 | version: '3', 13 | sources: [], 14 | names: [], 15 | mappings: '', 16 | }; 17 | 18 | test('remapping with loader generates empty sourcemap', () => { 19 | const loader = jest.fn(() => null); 20 | loader.mockReturnValueOnce(original); 21 | const remapped = remapping(minified, loader); 22 | 23 | expect(loader).not.toHaveBeenCalled(); 24 | expect(remapped.sources).toHaveLength(0); 25 | expect(remapped.mappings).toBe(''); 26 | }); 27 | 28 | test('remapping with array shorthand generates empty sourcemap', () => { 29 | const loader = jest.fn(() => null); 30 | const remapped = remapping([minified, original], loader); 31 | 32 | expect(loader).toHaveBeenCalledTimes(1); 33 | expect(loader).toHaveBeenCalledWith('source.ts', expect.anything()); 34 | expect(remapped.sources).toHaveLength(0); 35 | expect(remapped.mappings).toBe(''); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/samples/transpile-concat-hires/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(false); 3 | 4 | return { 5 | presets: [['@babel/preset-env', { modules: false }]], 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /test/samples/transpile-concat-hires/build.js: -------------------------------------------------------------------------------- 1 | const MagicString = require('magic-string'); 2 | const { readFileSync, writeFileSync } = require('fs'); 3 | 4 | function load(filename) { 5 | const contents = readFileSync(`${__dirname}/files/${filename}`, 'utf8'); 6 | return new MagicString(contents, { filename }); 7 | } 8 | function save(filename, contents) { 9 | writeFileSync(`${__dirname}/files/${filename}`, contents); 10 | } 11 | 12 | const main = load('main.js'); 13 | const placeholder = load('placeholder.js'); 14 | 15 | const search = '/* PLACEHOLDER */'; 16 | const index = main.original.indexOf(search); 17 | 18 | const before = main.snip(0, index); 19 | const after = main.snip(index + search.length, main.length()); 20 | 21 | const bundle = new MagicString.Bundle(); 22 | bundle.addSource(before); 23 | bundle.addSource(placeholder); 24 | bundle.addSource(after); 25 | 26 | save('bundle.js', bundle.toString()); 27 | save('bundle.js.map', bundle.generateMap({ 28 | hires: true, 29 | includeContent: true, 30 | }).toString()); 31 | -------------------------------------------------------------------------------- /test/samples/transpile-concat-hires/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DIR="$(dirname $0)" 3 | NODE_BIN=`npm bin` 4 | NODE_MODULES=`npm prefix`/node_modules 5 | 6 | rm "$DIR"/files/*.js* 7 | 8 | if [ ! -f "$NODE_BIN/babel" ]; then 9 | npm install --no-save @babel/cli @babel/preset-env 10 | fi 11 | "$NODE_BIN/babel" "$DIR/files" --config-file "./$DIR/babel.config.js" --source-maps -d "$DIR/files" 12 | 13 | if [ ! -d "$NODE_MODULES/magic-string" ]; then 14 | npm install --no-save magic-string 15 | fi 16 | 17 | node "$DIR/build.js" 18 | npx prettier "$DIR/files/*.map" --parser json --write 19 | -------------------------------------------------------------------------------- /test/samples/transpile-concat-hires/files/bundle.js: -------------------------------------------------------------------------------- 1 | var foo = function foo() { 2 | return 'foo'; 3 | }; 4 | 5 | var bar = function bar() { 6 | return 'bar'; 7 | }; 8 | //# sourceMappingURL=placeholder.js.map 9 | 10 | 11 | 12 | var baz = function baz() { 13 | return 'baz'; 14 | }; 15 | //# sourceMappingURL=main.js.map 16 | -------------------------------------------------------------------------------- /test/samples/transpile-concat-hires/files/bundle.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "file": null, 4 | "sources": ["main.js", "placeholder.js"], 5 | "sourcesContent": [ 6 | "/**\n * Copyright 2019 The AMP HTML Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nvar foo = function foo() {\n return 'foo';\n};\n/* PLACEHOLDER */\n\n\nvar baz = function baz() {\n return 'baz';\n};\n//# sourceMappingURL=main.js.map", 7 | "/**\n * Copyright 2019 The AMP HTML Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nvar bar = function bar() {\n return 'bar';\n};\n//# sourceMappingURL=placeholder.js.map" 8 | ], 9 | "names": [], 10 | "mappings": "AAAA,CAAC,CAAC,CAAC;AACH,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC5B,CAAC,CAAC;AACF,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACnE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1C,CAAC,CAAC;AACF,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClD,CAAC,CAAC;AACF,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACtE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACpE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC3E,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACtE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACjC,CAAC,CAAC,CAAC;AACH,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1B,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACf,CAAC,CAAC;AACF;AClBA,CAAC,CAAC,CAAC;AACH,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC5B,CAAC,CAAC;AACF,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACnE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1C,CAAC,CAAC;AACF,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClD,CAAC,CAAC;AACF,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACtE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACpE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC3E,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACtE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACjC,CAAC,CAAC,CAAC;AACH,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1B,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACf,CAAC,CAAC;AACF,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;uCAAC,vCDAtB;AACjB;AACA;AACA,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1B,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACf,CAAC,CAAC;AACF,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC" 11 | } 12 | -------------------------------------------------------------------------------- /test/samples/transpile-concat-hires/files/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 The AMP HTML Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | var foo = function foo() { 17 | return 'foo'; 18 | }; 19 | /* PLACEHOLDER */ 20 | 21 | 22 | var baz = function baz() { 23 | return 'baz'; 24 | }; 25 | //# sourceMappingURL=main.js.map -------------------------------------------------------------------------------- /test/samples/transpile-concat-hires/files/main.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["main.mjs"], 4 | "names": ["foo", "baz"], 5 | "mappings": "AAAA;;;;;;;;;;;;;;;AAgBA,IAAMA,GAAG,GAAG,SAANA,GAAM;AAAA,SAAM,KAAN;AAAA,CAAZ;AAEA;;;AAEA,IAAMC,GAAG,GAAG,SAANA,GAAM;AAAA,SAAM,KAAN;AAAA,CAAZ", 6 | "sourcesContent": [ 7 | "/**\n * Copyright 2019 The AMP HTML Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst foo = () => 'foo';\n\n/* PLACEHOLDER */\n\nconst baz = () => 'baz';\n" 8 | ], 9 | "file": "main.js" 10 | } 11 | -------------------------------------------------------------------------------- /test/samples/transpile-concat-hires/files/main.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 The AMP HTML Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | const foo = () => 'foo'; 18 | 19 | /* PLACEHOLDER */ 20 | 21 | const baz = () => 'baz'; 22 | -------------------------------------------------------------------------------- /test/samples/transpile-concat-hires/files/placeholder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 The AMP HTML Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | var bar = function bar() { 17 | return 'bar'; 18 | }; 19 | //# sourceMappingURL=placeholder.js.map -------------------------------------------------------------------------------- /test/samples/transpile-concat-hires/files/placeholder.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["placeholder.mjs"], 4 | "names": ["bar"], 5 | "mappings": "AAAA;;;;;;;;;;;;;;;AAgBA,IAAMA,GAAG,GAAG,SAANA,GAAM;AAAA,SAAM,KAAN;AAAA,CAAZ", 6 | "sourcesContent": [ 7 | "/**\n * Copyright 2019 The AMP HTML Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst bar = () => 'bar';\n" 8 | ], 9 | "file": "placeholder.js" 10 | } 11 | -------------------------------------------------------------------------------- /test/samples/transpile-concat-hires/files/placeholder.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 The AMP HTML Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | const bar = () => 'bar'; 18 | -------------------------------------------------------------------------------- /test/samples/transpile-concat-hires/test.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | import type { RawSourceMap } from 'source-map'; 3 | import { SourceMapConsumer } from 'source-map'; 4 | import remapping from '../../../src/remapping'; 5 | 6 | function read(filename: string): string { 7 | return readFileSync(`${__dirname}/files/${filename}`, 'utf8'); 8 | } 9 | 10 | describe('transpile then concatenate', () => { 11 | test('concated sections point to source files', () => { 12 | const map = read('bundle.js.map'); 13 | const remapped = remapping(map, (file) => { 14 | return file.endsWith('.mjs') ? null : read(`${file}.map`); 15 | }); 16 | 17 | const consumer = new SourceMapConsumer(remapped as unknown as RawSourceMap); 18 | // the foo in bundle.js 19 | for (let j = 10; j <= 12; j++) { 20 | const foo = consumer.originalPositionFor({ 21 | column: j, 22 | line: 17, 23 | }); 24 | expect(foo).toMatchObject({ 25 | column: 18, 26 | line: 17, 27 | source: 'main.mjs', 28 | }); 29 | } 30 | 31 | // the bar in bundle.js 32 | for (let j = 10; j <= 12; j++) { 33 | const bar = consumer.originalPositionFor({ 34 | column: j, 35 | line: 36, 36 | }); 37 | expect(bar).toMatchObject({ 38 | column: 18, 39 | line: 17, 40 | source: 'placeholder.mjs', 41 | }); 42 | } 43 | 44 | //the baz in bundle.js 45 | for (let j = 10; j <= 12; j++) { 46 | const baz = consumer.originalPositionFor({ 47 | column: j, 48 | line: 43, 49 | }); 50 | expect(baz).toMatchObject({ 51 | column: 18, 52 | line: 21, 53 | source: 'main.mjs', 54 | }); 55 | } 56 | }); 57 | 58 | test('inherits sourcesContent of original sources', () => { 59 | const map = read('bundle.js.map'); 60 | const remapped = remapping(map, (file) => { 61 | return file.endsWith('.mjs') ? null : read(`${file}.map`); 62 | }); 63 | 64 | expect(remapped.sourcesContent).toEqual([read('main.mjs'), read('placeholder.mjs')]); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /test/samples/transpile-concat-lowres/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(false); 3 | 4 | return { 5 | presets: [['@babel/preset-env', { modules: false }]], 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /test/samples/transpile-concat-lowres/build.js: -------------------------------------------------------------------------------- 1 | const MagicString = require('magic-string'); 2 | const parser = require('@babel/parser'); 3 | const { default: traverse } = require('@babel/traverse'); 4 | const { readFileSync, writeFileSync } = require('fs'); 5 | 6 | function load(filename) { 7 | const contents = readFileSync(`${__dirname}/files/${filename}`, 'utf8'); 8 | const s = new MagicString(contents, { filename }); 9 | const ast = parser.parse(contents); 10 | traverse.cheap(ast, (node) => { 11 | s.addSourcemapLocation(node.start); 12 | s.addSourcemapLocation(node.end); 13 | }); 14 | return s; 15 | } 16 | function save(filename, contents) { 17 | writeFileSync(`${__dirname}/files/${filename}`, contents); 18 | } 19 | 20 | const main = load('main.js'); 21 | const placeholder = load('placeholder.js'); 22 | 23 | const search = '/* PLACEHOLDER */'; 24 | const index = main.original.indexOf(search); 25 | 26 | const before = main.snip(0, index); 27 | const after = main.snip(index + search.length, main.length()); 28 | 29 | const bundle = new MagicString.Bundle(); 30 | bundle.addSource(before); 31 | bundle.addSource(placeholder); 32 | bundle.addSource(after); 33 | 34 | save('bundle.js', bundle.toString()); 35 | save('bundle.js.map', bundle.generateMap({ 36 | hires: false, 37 | includeContent: true, 38 | }).toString()); 39 | -------------------------------------------------------------------------------- /test/samples/transpile-concat-lowres/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DIR="$(dirname $0)" 3 | NODE_BIN=`npm bin` 4 | NODE_MODULES=`npm prefix`/node_modules 5 | 6 | rm "$DIR"/files/*.js* 7 | 8 | if [ ! -f "$NODE_BIN/babel" ]; then 9 | npm install --no-save @babel/cli @babel/preset-env 10 | fi 11 | "$NODE_BIN/babel" "$DIR/files" --config-file "./$DIR/babel.config.js" --source-maps -d "$DIR/files" 12 | 13 | if [ ! -d "$NODE_MODULES/magic-string" ]; then 14 | npm install --no-save magic-string 15 | fi 16 | 17 | node "$DIR/build.js" 18 | npx prettier "$DIR/files/*.map" --parser json --write 19 | -------------------------------------------------------------------------------- /test/samples/transpile-concat-lowres/files/bundle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 The AMP HTML Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | var foo = function foo() { 17 | return 'foo'; 18 | }; 19 | 20 | /** 21 | * Copyright 2019 The AMP HTML Authors. All Rights Reserved. 22 | * 23 | * Licensed under the Apache License, Version 2.0 (the "License"); 24 | * you may not use this file except in compliance with the License. 25 | * You may obtain a copy of the License at 26 | * 27 | * http://www.apache.org/licenses/LICENSE-2.0 28 | * 29 | * Unless required by applicable law or agreed to in writing, software 30 | * distributed under the License is distributed on an "AS IS" BASIS, 31 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32 | * See the License for the specific language governing permissions and 33 | * limitations under the License. 34 | */ 35 | var bar = function bar() { 36 | return 'bar'; 37 | }; 38 | //# sourceMappingURL=placeholder.js.map 39 | 40 | 41 | 42 | var baz = function baz() { 43 | return 'baz'; 44 | }; 45 | //# sourceMappingURL=main.js.map -------------------------------------------------------------------------------- /test/samples/transpile-concat-lowres/files/bundle.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "file": null, 4 | "sources": ["main.js", "placeholder.js"], 5 | "sourcesContent": [ 6 | "/**\n * Copyright 2019 The AMP HTML Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nvar foo = function foo() {\n return 'foo';\n};\n/* PLACEHOLDER */\n\n\nvar baz = function baz() {\n return 'baz';\n};\n//# sourceMappingURL=main.js.map", 7 | "/**\n * Copyright 2019 The AMP HTML Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nvar bar = function bar() {\n return 'bar';\n};\n//# sourceMappingURL=placeholder.js.map" 8 | ], 9 | "names": [], 10 | "mappings": "AAAA;;;;;;;;;;;;;;;AAeA,IAAI,GAAG,GAAG,SAAS,GAAG,GAAG;EACvB,OAAO,KAAK,CAAC;CACd,CAAC;AACF;AClBA;;;;;;;;;;;;;;;AAeA,IAAI,GAAG,GAAG,SAAS,GAAG,GAAG;EACvB,OAAO,KAAK,CAAC;CACd,CAAC;;uCACqC,vCDAtB;;;AAGjB,IAAI,GAAG,GAAG,SAAS,GAAG,GAAG;EACvB,OAAO,KAAK,CAAC;CACd,CAAC;" 11 | } 12 | -------------------------------------------------------------------------------- /test/samples/transpile-concat-lowres/files/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 The AMP HTML Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | var foo = function foo() { 17 | return 'foo'; 18 | }; 19 | /* PLACEHOLDER */ 20 | 21 | 22 | var baz = function baz() { 23 | return 'baz'; 24 | }; 25 | //# sourceMappingURL=main.js.map -------------------------------------------------------------------------------- /test/samples/transpile-concat-lowres/files/main.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["main.mjs"], 4 | "names": ["foo", "baz"], 5 | "mappings": "AAAA;;;;;;;;;;;;;;;AAgBA,IAAMA,GAAG,GAAG,SAANA,GAAM;AAAA,SAAM,KAAN;AAAA,CAAZ;AACA;;;AACA,IAAMC,GAAG,GAAG,SAANA,GAAM;AAAA,SAAM,KAAN;AAAA,CAAZ", 6 | "sourcesContent": [ 7 | "/**\n * Copyright 2019 The AMP HTML Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst foo = () => 'foo';\n/* PLACEHOLDER */\nconst baz = () => 'baz';\n" 8 | ], 9 | "file": "main.js" 10 | } 11 | -------------------------------------------------------------------------------- /test/samples/transpile-concat-lowres/files/main.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 The AMP HTML Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | const foo = () => 'foo'; 18 | /* PLACEHOLDER */ 19 | const baz = () => 'baz'; 20 | -------------------------------------------------------------------------------- /test/samples/transpile-concat-lowres/files/placeholder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 The AMP HTML Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | var bar = function bar() { 17 | return 'bar'; 18 | }; 19 | //# sourceMappingURL=placeholder.js.map -------------------------------------------------------------------------------- /test/samples/transpile-concat-lowres/files/placeholder.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["placeholder.mjs"], 4 | "names": ["bar"], 5 | "mappings": "AAAA;;;;;;;;;;;;;;;AAgBA,IAAMA,GAAG,GAAG,SAANA,GAAM;AAAA,SAAM,KAAN;AAAA,CAAZ", 6 | "sourcesContent": [ 7 | "/**\n * Copyright 2019 The AMP HTML Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst bar = () => 'bar';\n" 8 | ], 9 | "file": "placeholder.js" 10 | } 11 | -------------------------------------------------------------------------------- /test/samples/transpile-concat-lowres/files/placeholder.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 The AMP HTML Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | const bar = () => 'bar'; 18 | -------------------------------------------------------------------------------- /test/samples/transpile-concat-lowres/test.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | import type { RawSourceMap } from 'source-map'; 3 | import { SourceMapConsumer } from 'source-map'; 4 | import remapping from '../../../src/remapping'; 5 | 6 | function read(filename: string): string { 7 | return readFileSync(`${__dirname}/files/${filename}`, 'utf8'); 8 | } 9 | 10 | describe('transpile then concatenate', () => { 11 | test('concated sections point to source files', () => { 12 | const map = read('bundle.js.map'); 13 | const remapped = remapping(map, (file) => { 14 | return file.endsWith('.mjs') ? null : read(`${file}.map`); 15 | }); 16 | 17 | const consumer = new SourceMapConsumer(remapped as unknown as RawSourceMap); 18 | 19 | const foo = consumer.originalPositionFor({ 20 | column: 11, 21 | line: 17, 22 | }); 23 | expect(foo).toMatchObject({ 24 | column: 18, 25 | line: 17, 26 | source: 'main.mjs', 27 | }); 28 | 29 | const bar = consumer.originalPositionFor({ 30 | column: 11, 31 | line: 36, 32 | }); 33 | expect(bar).toMatchObject({ 34 | column: 18, 35 | line: 17, 36 | source: 'placeholder.mjs', 37 | }); 38 | 39 | const baz = consumer.originalPositionFor({ 40 | column: 11, 41 | line: 43, 42 | }); 43 | expect(baz).toMatchObject({ 44 | column: 18, 45 | line: 19, 46 | source: 'main.mjs', 47 | }); 48 | }); 49 | 50 | test('inherits sourcesContent of original sources', () => { 51 | const map = read('bundle.js.map'); 52 | const remapped = remapping(map, (file) => { 53 | return file.endsWith('.mjs') ? null : read(`${file}.map`); 54 | }); 55 | 56 | expect(remapped.sourcesContent).toEqual([read('main.mjs'), read('placeholder.mjs')]); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/samples/transpile-minify/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(false); 3 | 4 | return { 5 | presets: [['@babel/preset-env', { modules: false }]], 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /test/samples/transpile-minify/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DIR=`dirname $0` 3 | NODE_BIN=`npm bin` 4 | 5 | rm "$DIR"/files/*.js* 6 | 7 | if [ ! -f "$NODE_BIN/babel" ]; then 8 | npm install --no-save @babel/cli @babel/preset-env 9 | fi 10 | "$NODE_BIN/babel" "$DIR/files" --config-file "./$DIR/babel.config.js" --source-maps -d "$DIR/files" 11 | 12 | npx terser "$DIR/files/helloworld.js" -c --source-map "base='$DIR/files',includeSources" --comments all -o "$DIR/files/helloworld.min.js" 13 | npx prettier "$DIR/files/*.map" --parser json --write 14 | -------------------------------------------------------------------------------- /test/samples/transpile-minify/files/helloworld.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 The AMP HTML Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | self.AMP_CONFIG = {}; 17 | 18 | var greet = function greet() { 19 | return alert("hello"); 20 | }; 21 | 22 | greet(); 23 | //# sourceMappingURL=helloworld.js.map -------------------------------------------------------------------------------- /test/samples/transpile-minify/files/helloworld.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["helloworld.mjs"], 4 | "names": ["self", "AMP_CONFIG", "greet", "alert"], 5 | "mappings": "AAAA;;;;;;;;;;;;;;;AAgBAA,IAAI,CAACC,UAAL,GAAkB,EAAlB;;AAEA,IAAMC,KAAK,GAAG,SAARA,KAAQ;AAAA,SAAMC,KAAK,SAAX;AAAA,CAAd;;AAEAD,KAAK", 6 | "sourcesContent": [ 7 | "/**\n * Copyright 2019 The AMP HTML Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nself.AMP_CONFIG = {};\n\nconst greet = () => alert(`hello`);\n\ngreet();" 8 | ], 9 | "file": "helloworld.js" 10 | } 11 | -------------------------------------------------------------------------------- /test/samples/transpile-minify/files/helloworld.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 The AMP HTML Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | self.AMP_CONFIG={};var greet=function(){return alert("hello")};greet(); 17 | //# sourceMappingURL=helloworld.js.map -------------------------------------------------------------------------------- /test/samples/transpile-minify/files/helloworld.min.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["helloworld.js"], 4 | "names": ["self", "AMP_CONFIG", "greet", "alert"], 5 | "mappings": ";;;;;;;;;;;;;;;AAeAA,KAAKC,WAAa,GAElB,IAAIC,MAAQ,WACV,OAAOC,MAAM,UAGfD", 6 | "sourcesContent": [ 7 | "/**\n * Copyright 2019 The AMP HTML Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nself.AMP_CONFIG = {};\n\nvar greet = function greet() {\n return alert(\"hello\");\n};\n\ngreet();\n//# sourceMappingURL=helloworld.js.map" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/samples/transpile-minify/files/helloworld.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 The AMP HTML Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | self.AMP_CONFIG = {}; 18 | 19 | const greet = () => alert(`hello`); 20 | 21 | greet(); -------------------------------------------------------------------------------- /test/samples/transpile-minify/test.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | import type { RawSourceMap } from 'source-map'; 3 | import { SourceMapConsumer } from 'source-map'; 4 | import remapping from '../../../src/remapping'; 5 | 6 | function read(filename: string): string { 7 | return readFileSync(`${__dirname}/files/${filename}`, 'utf8'); 8 | } 9 | 10 | describe('transpile then minify', () => { 11 | test('minify a transpiled source map', () => { 12 | const map = read('helloworld.min.js.map'); 13 | const remapped = remapping(map, (file) => { 14 | return file.endsWith('.mjs') ? null : read(`${file}.map`); 15 | }); 16 | 17 | const consumer = new SourceMapConsumer(remapped as unknown as RawSourceMap); 18 | const alert = consumer.originalPositionFor({ 19 | column: 47, 20 | line: 16, 21 | }); 22 | expect(alert).toEqual({ 23 | column: 20, 24 | line: 19, 25 | name: 'alert', 26 | source: 'helloworld.mjs', 27 | }); 28 | }); 29 | 30 | test('inherits sourcesContent of original source', () => { 31 | const map = read('helloworld.min.js.map'); 32 | const remapped = remapping(map, (file) => { 33 | return file.endsWith('.mjs') ? null : read(`${file}.map`); 34 | }); 35 | 36 | expect(remapped.sourcesContent).toEqual([read('helloworld.mjs')]); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/samples/transpile-rollup/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(false); 3 | 4 | return { 5 | presets: [['@babel/preset-env', { modules: false }]], 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /test/samples/transpile-rollup/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DIR="$(dirname $0)" 3 | NODE_BIN=`npm bin` 4 | 5 | rm "$DIR"/files/*.js* 6 | 7 | if [ ! -f "$NODE_BIN/babel" ]; then 8 | npm install --no-save @babel/cli @babel/preset-env 9 | fi 10 | "$NODE_BIN/babel" "$DIR/files" --config-file "./$DIR/babel.config.js" --source-maps -d "$DIR/files" 11 | 12 | # Strip the sourceMappingURL to prevent rollup from auto collapsing sourcemaps 13 | for f in $DIR/files/*.js; do 14 | sed '$d' $f > $f.tmp 15 | mv $f.tmp $f 16 | done 17 | 18 | npx rollup -i "$DIR/files/index.js" -f cjs -o "$DIR/files/bundle.js" --sourcemap 19 | npx prettier "$DIR/files/*.map" --parser json --write 20 | -------------------------------------------------------------------------------- /test/samples/transpile-rollup/files/a.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 The AMP HTML Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | export default (function () { 17 | return 'a'; 18 | }); 19 | -------------------------------------------------------------------------------- /test/samples/transpile-rollup/files/a.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["a.mjs"], 4 | "names": [], 5 | "mappings": "AAAA;;;;;;;;;;;;;;;AAgBA,gBAAe;AAAA,SAAM,GAAN;AAAA,CAAf", 6 | "sourcesContent": [ 7 | "/**\n * Copyright 2019 The AMP HTML Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport default () => 'a';" 8 | ], 9 | "file": "a.js" 10 | } 11 | -------------------------------------------------------------------------------- /test/samples/transpile-rollup/files/a.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 The AMP HTML Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export default () => 'a'; -------------------------------------------------------------------------------- /test/samples/transpile-rollup/files/b.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 The AMP HTML Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | export default (function () { 17 | return 'b'; 18 | }); 19 | -------------------------------------------------------------------------------- /test/samples/transpile-rollup/files/b.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["b.mjs"], 4 | "names": [], 5 | "mappings": "AAAA;;;;;;;;;;;;;;;AAgBA,gBAAe;AAAA,SAAM,GAAN;AAAA,CAAf", 6 | "sourcesContent": [ 7 | "/**\n * Copyright 2019 The AMP HTML Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport default () => 'b';" 8 | ], 9 | "file": "b.js" 10 | } 11 | -------------------------------------------------------------------------------- /test/samples/transpile-rollup/files/b.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 The AMP HTML Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export default () => 'b'; -------------------------------------------------------------------------------- /test/samples/transpile-rollup/files/bundle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { value: true }); 4 | 5 | /** 6 | * Copyright 2019 The AMP HTML Authors. All Rights Reserved. 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | var a = (function () { 21 | return 'a'; 22 | }); 23 | 24 | /** 25 | * Copyright 2019 The AMP HTML Authors. All Rights Reserved. 26 | * 27 | * Licensed under the Apache License, Version 2.0 (the "License"); 28 | * you may not use this file except in compliance with the License. 29 | * You may obtain a copy of the License at 30 | * 31 | * http://www.apache.org/licenses/LICENSE-2.0 32 | * 33 | * Unless required by applicable law or agreed to in writing, software 34 | * distributed under the License is distributed on an "AS IS" BASIS, 35 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 36 | * See the License for the specific language governing permissions and 37 | * limitations under the License. 38 | */ 39 | var b = (function () { 40 | return 'b'; 41 | }); 42 | 43 | exports.a = a; 44 | exports.b = b; 45 | //# sourceMappingURL=bundle.js.map 46 | -------------------------------------------------------------------------------- /test/samples/transpile-rollup/files/bundle.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "file": "bundle.js", 4 | "sources": ["a.js", "b.js"], 5 | "sourcesContent": [ 6 | "/**\n * Copyright 2019 The AMP HTML Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nexport default (function () {\n return 'a';\n});\n", 7 | "/**\n * Copyright 2019 The AMP HTML Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nexport default (function () {\n return 'b';\n});\n" 8 | ], 9 | "names": [], 10 | "mappings": ";;;;AAAA;;;;;;;;;;;;;;;AAeA,QAAe,CAAC,YAAY;EAC1B,OAAO,GAAG,CAAC;CACZ,EAAE;;ACjBH;;;;;;;;;;;;;;;AAeA,QAAe,CAAC,YAAY;EAC1B,OAAO,GAAG,CAAC;CACZ,EAAE;;;;;" 11 | } 12 | -------------------------------------------------------------------------------- /test/samples/transpile-rollup/files/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 The AMP HTML Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | export { default as a } from './a.js'; 17 | export { default as b } from './b.js'; 18 | -------------------------------------------------------------------------------- /test/samples/transpile-rollup/files/index.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["index.mjs"], 4 | "names": ["default", "a", "b"], 5 | "mappings": "AAAA;;;;;;;;;;;;;;;AAgBA,SAAQA,OAAO,IAAIC,CAAnB,QAA2B,QAA3B;AACA,SAAQD,OAAO,IAAIE,CAAnB,QAA2B,QAA3B", 6 | "sourcesContent": [ 7 | "/**\n * Copyright 2019 The AMP HTML Authors. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport {default as a} from './a.js';\nexport {default as b} from './b.js';" 8 | ], 9 | "file": "index.js" 10 | } 11 | -------------------------------------------------------------------------------- /test/samples/transpile-rollup/files/index.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 The AMP HTML Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export {default as a} from './a.js'; 18 | export {default as b} from './b.js'; -------------------------------------------------------------------------------- /test/samples/transpile-rollup/test.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | import type { RawSourceMap } from 'source-map'; 3 | import { SourceMapConsumer } from 'source-map'; 4 | import remapping from '../../../src/remapping'; 5 | 6 | function read(filename: string): string { 7 | return readFileSync(`${__dirname}/files/${filename}`, 'utf8'); 8 | } 9 | 10 | describe('transpile then concatenate', () => { 11 | test('concated sections point to source files', () => { 12 | const map = read('bundle.js.map'); 13 | const remapped = remapping(map, (file) => { 14 | return file.endsWith('.mjs') ? null : read(`${file}.map`); 15 | }); 16 | 17 | const consumer = new SourceMapConsumer(remapped as unknown as RawSourceMap); 18 | 19 | const a = consumer.originalPositionFor({ 20 | column: 11, 21 | line: 21, 22 | }); 23 | expect(a).toMatchObject({ 24 | column: 21, 25 | line: 17, 26 | source: 'a.mjs', 27 | }); 28 | 29 | const b = consumer.originalPositionFor({ 30 | column: 11, 31 | line: 40, 32 | }); 33 | expect(b).toMatchObject({ 34 | column: 21, 35 | line: 17, 36 | source: 'b.mjs', 37 | }); 38 | }); 39 | 40 | test('inherits sourcesContent of original sources', () => { 41 | const map = read('bundle.js.map'); 42 | const remapped = remapping(map, (file) => { 43 | return file.endsWith('.mjs') ? null : read(`${file}.map`); 44 | }); 45 | 46 | expect(remapped.sourcesContent).toEqual([read('a.mjs'), read('b.mjs')]); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/unit/build-source-map-tree.ts: -------------------------------------------------------------------------------- 1 | import buildSourceMapTree from '../../src/build-source-map-tree'; 2 | import type { DecodedSourceMap, EncodedSourceMap } from '../../src/types'; 3 | 4 | describe('buildSourceMapTree', () => { 5 | const rawMap: EncodedSourceMap = { 6 | mappings: 'AAAA', 7 | names: [], 8 | sources: ['helloworld.js'], 9 | sourcesContent: [null], 10 | version: 3, 11 | }; 12 | const decodedMap: DecodedSourceMap = { 13 | ...rawMap, 14 | mappings: [[[0, 0, 0, 0]]], 15 | }; 16 | 17 | test('calls loader for any needed sourcemap', () => { 18 | const loader = jest.fn(() => null); 19 | buildSourceMapTree(decodedMap, loader); 20 | 21 | expect(loader).toHaveBeenCalledTimes(1); 22 | expect(loader).toHaveBeenCalledWith('helloworld.js', expect.anything()); 23 | }); 24 | 25 | test('loader cannot be async', () => { 26 | // tslint:disable-next-line: no-any 27 | const loader = (): any => Promise.resolve(null); 28 | expect(() => { 29 | buildSourceMapTree(decodedMap, loader); 30 | }).toThrow(); 31 | }); 32 | 33 | test('creates OriginalSource if no sourcemap', () => { 34 | const tree = buildSourceMapTree(decodedMap, () => null); 35 | expect(tree.sources).toMatchObject([ 36 | { 37 | source: 'helloworld.js', 38 | }, 39 | ]); 40 | }); 41 | 42 | test('creates OriginalSource with sourceContent', () => { 43 | const tree = buildSourceMapTree( 44 | { 45 | ...decodedMap, 46 | sourcesContent: ['1 + 1'], 47 | }, 48 | () => null 49 | ); 50 | 51 | expect(tree.sources).toMatchObject([ 52 | { 53 | content: '1 + 1', 54 | }, 55 | ]); 56 | }); 57 | 58 | test('creates OriginalSource with null content if no sourceContent', () => { 59 | const tree = buildSourceMapTree(decodedMap, () => null); 60 | expect(tree.sources).toMatchObject([ 61 | { 62 | content: null, 63 | }, 64 | ]); 65 | }); 66 | 67 | test('creates OriginalSource with null content if no sourcesContent', () => { 68 | const tree = buildSourceMapTree( 69 | { 70 | ...decodedMap, 71 | sourcesContent: undefined, 72 | }, 73 | () => null 74 | ); 75 | 76 | expect(tree.sources).toMatchObject([ 77 | { 78 | content: null, 79 | }, 80 | ]); 81 | }); 82 | 83 | test('creates ignored OriginalSource with ignoreList', () => { 84 | const tree = buildSourceMapTree( 85 | { 86 | ...decodedMap, 87 | ignoreList: [0], 88 | }, 89 | () => null 90 | ); 91 | 92 | expect(tree.sources).toMatchObject([ 93 | { 94 | ignore: true, 95 | }, 96 | ]); 97 | }); 98 | 99 | test('creates unignored OriginalSource if no ignoreList', () => { 100 | const tree = buildSourceMapTree(decodedMap, () => null); 101 | expect(tree.sources).toMatchObject([ 102 | { 103 | ignore: false, 104 | }, 105 | ]); 106 | }); 107 | 108 | test('creates unignored OriginalSource with if no ignoreList', () => { 109 | const tree = buildSourceMapTree( 110 | { 111 | ...decodedMap, 112 | ignoreList: undefined, 113 | }, 114 | () => null 115 | ); 116 | 117 | expect(tree.sources).toMatchObject([ 118 | { 119 | ignore: false, 120 | }, 121 | ]); 122 | }); 123 | 124 | test('recursively loads sourcemaps', () => { 125 | const loader = jest.fn(); 126 | loader 127 | .mockReturnValueOnce({ 128 | ...rawMap, 129 | sources: ['two.js'], 130 | }) 131 | .mockReturnValue(null); 132 | const tree = buildSourceMapTree(decodedMap, loader); 133 | 134 | expect(tree.sources).toMatchObject([ 135 | { 136 | sources: [ 137 | { 138 | source: 'two.js', 139 | }, 140 | ], 141 | }, 142 | ]); 143 | 144 | expect(loader).toHaveBeenCalledTimes(2); 145 | expect(loader).toHaveBeenCalledWith('helloworld.js', expect.anything()); 146 | expect(loader).toHaveBeenCalledWith('two.js', expect.anything()); 147 | }); 148 | 149 | test('calls loader with sourceRoot joined to source file', () => { 150 | const loader = jest.fn(() => null); 151 | buildSourceMapTree( 152 | { 153 | ...decodedMap, 154 | sourceRoot: 'https://foo.com/', 155 | }, 156 | loader 157 | ); 158 | 159 | expect(loader).toHaveBeenCalledTimes(1); 160 | expect(loader).toHaveBeenCalledWith('https://foo.com/helloworld.js', expect.anything()); 161 | }); 162 | 163 | test('original sources are relative to the tree path', () => { 164 | const loader = jest.fn(); 165 | loader 166 | .mockReturnValueOnce({ 167 | ...rawMap, 168 | file: 'helloworld.js', 169 | sourceRoot: 'https://foo.com/', 170 | sources: ['./assets/two.js'], 171 | }) 172 | .mockReturnValueOnce({ 173 | ...rawMap, 174 | file: 'two.js', 175 | // We need to support relative roots... 176 | sourceRoot: './deep/', 177 | sources: ['three.js'], 178 | }) 179 | .mockReturnValue(null); 180 | const tree = buildSourceMapTree(decodedMap, loader); 181 | 182 | expect(tree.sources).toMatchObject([ 183 | { 184 | // helloworld.js's map 185 | sources: [ 186 | { 187 | // two.js's map 188 | sources: [ 189 | { 190 | source: 'https://foo.com/assets/deep/three.js', 191 | }, 192 | ], 193 | }, 194 | ], 195 | }, 196 | ]); 197 | 198 | expect(loader).toHaveBeenCalledTimes(3); 199 | expect(loader).toHaveBeenCalledWith('helloworld.js', expect.anything()); 200 | expect(loader).toHaveBeenCalledWith('https://foo.com/assets/two.js', expect.anything()); 201 | expect(loader).toHaveBeenCalledWith('https://foo.com/assets/deep/three.js', expect.anything()); 202 | }); 203 | 204 | describe('loader context', () => { 205 | describe('importer', () => { 206 | test('is empty for sources loaded from the root', () => { 207 | const loader = jest.fn(); 208 | buildSourceMapTree(decodedMap, loader); 209 | 210 | expect(loader).toHaveBeenCalledTimes(1); 211 | expect(loader).toHaveBeenCalledWith( 212 | expect.anything(), 213 | expect.objectContaining({ 214 | importer: '', 215 | }) 216 | ); 217 | }); 218 | 219 | test('is parent for nested sources', () => { 220 | const loader = jest.fn(); 221 | loader.mockReturnValueOnce({ 222 | ...rawMap, 223 | sources: ['two.js'], 224 | }); 225 | buildSourceMapTree(decodedMap, loader); 226 | 227 | expect(loader).toHaveBeenCalledTimes(2); 228 | expect(loader).toHaveBeenCalledWith( 229 | 'helloworld.js', 230 | expect.objectContaining({ 231 | importer: '', 232 | }) 233 | ); 234 | expect(loader).toHaveBeenCalledWith( 235 | 'two.js', 236 | expect.objectContaining({ 237 | importer: 'helloworld.js', 238 | }) 239 | ); 240 | }); 241 | }); 242 | 243 | describe('depty', () => { 244 | test('is 1 for sources loaded from the root', () => { 245 | const loader = jest.fn(); 246 | buildSourceMapTree( 247 | { 248 | ...decodedMap, 249 | sources: ['first.js', 'second.js'], 250 | }, 251 | loader 252 | ); 253 | 254 | expect(loader).toHaveBeenCalledTimes(2); 255 | expect(loader).toHaveBeenCalledWith( 256 | 'first.js', 257 | expect.objectContaining({ 258 | depth: 1, 259 | }) 260 | ); 261 | expect(loader).toHaveBeenCalledWith( 262 | 'second.js', 263 | expect.objectContaining({ 264 | depth: 1, 265 | }) 266 | ); 267 | }); 268 | 269 | test('is increased for nested sources', () => { 270 | const loader = jest.fn(); 271 | loader.mockReturnValueOnce({ 272 | ...rawMap, 273 | sources: ['two.js'], 274 | }); 275 | buildSourceMapTree( 276 | { 277 | ...decodedMap, 278 | sources: ['first.js', 'second.js'], 279 | }, 280 | loader 281 | ); 282 | 283 | expect(loader).toHaveBeenCalledTimes(3); 284 | expect(loader).toHaveBeenCalledWith( 285 | 'first.js', 286 | expect.objectContaining({ 287 | depth: 1, 288 | }) 289 | ); 290 | expect(loader).toHaveBeenCalledWith( 291 | 'two.js', 292 | expect.objectContaining({ 293 | depth: 2, 294 | }) 295 | ); 296 | expect(loader).toHaveBeenCalledWith( 297 | 'second.js', 298 | expect.objectContaining({ 299 | depth: 1, 300 | }) 301 | ); 302 | }); 303 | }); 304 | 305 | describe('source', () => { 306 | test('matches the loader source param', () => { 307 | const loader = jest.fn(); 308 | buildSourceMapTree(decodedMap, loader); 309 | 310 | expect(loader).toHaveBeenCalledTimes(1); 311 | expect(loader).toHaveBeenCalledWith( 312 | 'helloworld.js', 313 | expect.objectContaining({ 314 | source: 'helloworld.js', 315 | }) 316 | ); 317 | }); 318 | 319 | test('can be overridden to change source of original file', () => { 320 | const loader = jest.fn(); 321 | loader.mockImplementationOnce((s, ctx) => { 322 | expect(s).toBe('helloworld.js'); 323 | ctx.source = 'bar/baz.js'; 324 | }); 325 | 326 | const tree = buildSourceMapTree(decodedMap, loader); 327 | 328 | expect(tree.sources).toMatchObject([ 329 | { 330 | source: 'bar/baz.js', 331 | }, 332 | ]); 333 | }); 334 | 335 | test('can be overridden to change resolving location', () => { 336 | const loader = jest.fn(); 337 | loader.mockImplementationOnce((s, ctx) => { 338 | expect(s).toBe('helloworld.js'); 339 | ctx.source = 'bar/baz.js'; 340 | return { 341 | ...rawMap, 342 | sources: ['two.js'], 343 | }; 344 | }); 345 | 346 | const tree = buildSourceMapTree(decodedMap, loader); 347 | 348 | expect(tree.sources).toMatchObject([ 349 | { 350 | sources: [ 351 | { 352 | source: 'bar/two.js', 353 | }, 354 | ], 355 | }, 356 | ]); 357 | }); 358 | }); 359 | 360 | describe('content', () => { 361 | test('can override the sourcesContent of parent map', () => { 362 | const loader = jest.fn(); 363 | loader.mockImplementationOnce((s, ctx) => { 364 | expect(s).toBe('helloworld.js'); 365 | ctx.content = 'override'; 366 | }); 367 | 368 | const tree = buildSourceMapTree(decodedMap, loader); 369 | 370 | expect(tree.sources).toMatchObject([ 371 | { 372 | content: 'override', 373 | }, 374 | ]); 375 | }); 376 | 377 | test('can override the sourcesContent of parent map', () => { 378 | const loader = jest.fn(); 379 | loader.mockImplementationOnce((s, ctx) => { 380 | expect(s).toBe('helloworld.js'); 381 | ctx.content = null; 382 | }); 383 | 384 | const tree = buildSourceMapTree( 385 | { 386 | ...decodedMap, 387 | sourcesContent: ['test'], 388 | }, 389 | loader 390 | ); 391 | 392 | expect(tree.sources).toMatchObject([ 393 | { 394 | content: null, 395 | }, 396 | ]); 397 | }); 398 | }); 399 | 400 | describe('ignore', () => { 401 | test('can override the ignore of parent map', () => { 402 | const loader = jest.fn(); 403 | loader.mockImplementationOnce((s, ctx) => { 404 | expect(s).toBe('helloworld.js'); 405 | ctx.ignore = true; 406 | }); 407 | 408 | const tree = buildSourceMapTree(decodedMap, loader); 409 | 410 | expect(tree.sources).toMatchObject([ 411 | { 412 | ignore: true, 413 | }, 414 | ]); 415 | }); 416 | 417 | test('can override the sourcesContent of parent map', () => { 418 | const loader = jest.fn(); 419 | loader.mockImplementationOnce((s, ctx) => { 420 | expect(s).toBe('helloworld.js'); 421 | ctx.ignore = false; 422 | }); 423 | 424 | const tree = buildSourceMapTree( 425 | { 426 | ...decodedMap, 427 | ignoreList: [0], 428 | }, 429 | loader 430 | ); 431 | 432 | expect(tree.sources).toMatchObject([ 433 | { 434 | ignore: false, 435 | }, 436 | ]); 437 | }); 438 | }); 439 | }); 440 | 441 | test('original sources are relative to the tree path, edge cases', () => { 442 | const loader = jest.fn(); 443 | loader 444 | .mockReturnValueOnce({ 445 | ...rawMap, 446 | file: 'helloworld.js', 447 | sources: ['/two.js'], 448 | }) 449 | .mockReturnValueOnce({ 450 | ...rawMap, 451 | file: 'two.js', 452 | // We need to support relative roots... 453 | // sourceRoot: './assets/', 454 | sources: ['./assets/three.js'], 455 | }) 456 | .mockReturnValue(null); 457 | const tree = buildSourceMapTree( 458 | { 459 | ...decodedMap, 460 | // We shouldn't need this, but we need absolute URLs because our resolver 461 | // sucks. 462 | sourceRoot: 'https://foo.com/deep', 463 | }, 464 | loader 465 | ); 466 | 467 | expect(tree.sources).toMatchObject([ 468 | { 469 | // helloworld.js's map 470 | sources: [ 471 | { 472 | // two.js's map 473 | sources: [ 474 | { 475 | source: 'https://foo.com/assets/three.js', 476 | }, 477 | ], 478 | }, 479 | ], 480 | }, 481 | ]); 482 | 483 | expect(loader).toHaveBeenCalledTimes(3); 484 | expect(loader).toHaveBeenCalledWith('https://foo.com/deep/helloworld.js', expect.anything()); 485 | expect(loader).toHaveBeenCalledWith('https://foo.com/two.js', expect.anything()); 486 | expect(loader).toHaveBeenCalledWith('https://foo.com/assets/three.js', expect.anything()); 487 | }); 488 | 489 | describe('array form', () => { 490 | test('transformation maps of a sourcemap may be passed before the sourcemap', () => { 491 | const maps = [ 492 | decodedMap, // "transformation map" 493 | decodedMap, 494 | ]; 495 | const tree = buildSourceMapTree(maps, () => null); 496 | 497 | expect(tree.sources).toMatchObject([ 498 | { 499 | // helloworld.js's map 500 | sources: [ 501 | { 502 | source: 'helloworld.js', 503 | }, 504 | ], 505 | }, 506 | ]); 507 | }); 508 | 509 | test('transformation map does not influence map url', () => { 510 | const maps = [ 511 | { 512 | ...decodedMap, 513 | sourceRoot: 'https://example.com/', 514 | }, // "transformation map" 515 | decodedMap, 516 | ]; 517 | const tree = buildSourceMapTree(maps, () => null); 518 | 519 | expect(tree.sources).toMatchObject([ 520 | { 521 | // helloworld.js's map 522 | sources: [ 523 | { 524 | source: 'helloworld.js', 525 | }, 526 | ], 527 | }, 528 | ]); 529 | }); 530 | 531 | test('throws when transformation map has more than one source', () => { 532 | const maps = [ 533 | { 534 | ...decodedMap, 535 | sources: ['one.js', 'two.js'], 536 | }, // "transformation map" 537 | decodedMap, 538 | ]; 539 | 540 | expect(() => { 541 | buildSourceMapTree(maps, () => null); 542 | }).toThrow(); 543 | }); 544 | 545 | test('handles when transformation map has 0 sources', () => { 546 | const maps = [ 547 | { 548 | ...decodedMap, 549 | mappings: [], 550 | sources: [], 551 | }, // "transformation map" 552 | decodedMap, 553 | ]; 554 | const loader = jest.fn(); 555 | 556 | const tree = buildSourceMapTree(maps, loader); 557 | expect(tree.map).toMatchObject({ 558 | sources: [], 559 | }); 560 | expect(loader).toHaveBeenCalledTimes(1); 561 | expect(loader).toHaveBeenCalledWith('helloworld.js', expect.anything()); 562 | }); 563 | }); 564 | 565 | describe('null source', () => { 566 | test('parses map with null source', () => { 567 | const loader = jest.fn(); 568 | loader 569 | .mockReturnValueOnce({ 570 | ...rawMap, 571 | sources: ['two.js'], 572 | }) 573 | .mockReturnValue(null); 574 | const tree = buildSourceMapTree( 575 | { 576 | ...decodedMap, 577 | sources: [null], 578 | }, 579 | loader 580 | ); 581 | 582 | expect(tree.map).toMatchObject({ 583 | sources: [null], 584 | }); 585 | 586 | expect(loader).toHaveBeenCalledWith('', expect.anything()); 587 | }); 588 | 589 | test('parses maps descending from null source', () => { 590 | const loader = jest.fn(); 591 | loader 592 | .mockReturnValueOnce({ 593 | ...rawMap, 594 | sources: ['two.js'], 595 | }) 596 | .mockReturnValue(null); 597 | const tree = buildSourceMapTree( 598 | { 599 | ...decodedMap, 600 | sources: [null], 601 | }, 602 | loader 603 | ); 604 | 605 | expect(tree.sources).toMatchObject([ 606 | { 607 | sources: [ 608 | { 609 | source: 'two.js', 610 | }, 611 | ], 612 | }, 613 | ]); 614 | 615 | expect(loader).toHaveBeenCalledWith('', expect.anything()); 616 | expect(loader).toHaveBeenCalledWith('two.js', expect.anything()); 617 | }); 618 | 619 | test('parses maps descending from null source with sourceRoot', () => { 620 | const loader = jest.fn(); 621 | loader 622 | .mockReturnValueOnce({ 623 | ...rawMap, 624 | sources: ['two.js'], 625 | }) 626 | .mockReturnValue(null); 627 | const tree = buildSourceMapTree( 628 | { 629 | ...decodedMap, 630 | sourceRoot: 'https://foo.com/', 631 | sources: [null], 632 | }, 633 | loader 634 | ); 635 | 636 | expect(tree.sources).toMatchObject([ 637 | { 638 | sources: [ 639 | { 640 | source: 'https://foo.com/two.js', 641 | }, 642 | ], 643 | }, 644 | ]); 645 | 646 | expect(loader).toHaveBeenCalledWith('https://foo.com/', expect.anything()); 647 | expect(loader).toHaveBeenCalledWith('https://foo.com/two.js', expect.anything()); 648 | }); 649 | }); 650 | }); 651 | -------------------------------------------------------------------------------- /test/unit/remapping.ts: -------------------------------------------------------------------------------- 1 | import remapping from '../../src/remapping'; 2 | import type { EncodedSourceMap } from '../../src/types'; 3 | 4 | describe('remapping', () => { 5 | const rawMap: EncodedSourceMap = { 6 | file: 'transpiled.min.js', 7 | // 0th column of 1st line of output file translates into the 1st source 8 | // file, line 2, column 1, using 1st name. 9 | mappings: 'AACCA', 10 | names: ['add'], 11 | sources: ['transpiled.js'], 12 | sourcesContent: ['1+1'], 13 | version: 3, 14 | ignoreList: [], 15 | }; 16 | const transpiledMap: EncodedSourceMap = { 17 | // 1st column of 2nd line of output file translates into the 1st source 18 | // file, line 3, column 2 19 | mappings: ';CAEE', 20 | names: [], 21 | sources: ['helloworld.js'], 22 | sourcesContent: ['\n\n 1 + 1;'], 23 | version: 3, 24 | ignoreList: [], 25 | }; 26 | const translatedMap: EncodedSourceMap = { 27 | file: 'transpiled.min.js', 28 | // 0th column of 1st line of output file translates into the 1st source 29 | // file, line 3, column 2, using first name 30 | mappings: 'AAEEA', 31 | names: ['add'], 32 | // TODO: support sourceRoot 33 | // sourceRoot: '', 34 | sources: ['helloworld.js'], 35 | sourcesContent: ['\n\n 1 + 1;'], 36 | version: 3, 37 | ignoreList: [], 38 | }; 39 | 40 | test('does not alter a lone sourcemap', () => { 41 | const map = remapping(rawMap, () => null); 42 | expect(map).toEqual(rawMap); 43 | }); 44 | 45 | test('traces SourceMapSegments through child sourcemaps', () => { 46 | const map = remapping(rawMap, (name: string) => { 47 | if (name === 'transpiled.js') { 48 | return transpiledMap; 49 | } 50 | }); 51 | 52 | expect(map).toEqual(translatedMap); 53 | }); 54 | 55 | test('traces transformations through sourcemap', () => { 56 | const maps = [rawMap, transpiledMap]; 57 | const map = remapping(maps, () => null); 58 | 59 | expect(map).toEqual(translatedMap); 60 | }); 61 | 62 | test('resolves sourcemaps realtive to sourceRoot', () => { 63 | const sourceRoot = 'foo/'; 64 | const map = remapping( 65 | { 66 | ...rawMap, 67 | sourceRoot, 68 | }, 69 | (name: string) => { 70 | if (name.endsWith('transpiled.js')) { 71 | return transpiledMap; 72 | } 73 | } 74 | ); 75 | 76 | expect(map).toEqual({ 77 | ...translatedMap, 78 | // TODO: support sourceRoot 79 | // sourceRoot, 80 | sources: ['foo/helloworld.js'], 81 | }); 82 | }); 83 | 84 | test('resolves sourcemaps realtive to absolute sourceRoot', () => { 85 | const sourceRoot = 'https://foo.com/'; 86 | const map = remapping( 87 | { 88 | ...rawMap, 89 | sourceRoot, 90 | }, 91 | (name: string) => { 92 | if (name.endsWith('transpiled.js')) { 93 | return transpiledMap; 94 | } 95 | } 96 | ); 97 | 98 | expect(map).toEqual({ 99 | ...translatedMap, 100 | // TODO: support sourceRoot 101 | // sourceRoot, 102 | sources: [`${sourceRoot}helloworld.js`], 103 | }); 104 | }); 105 | 106 | test('includes null sourceContent if sourcemap has no sourcesContent', () => { 107 | const map = remapping(rawMap, (name: string) => { 108 | if (name === 'transpiled.js') { 109 | return { 110 | ...transpiledMap, 111 | sourcesContent: undefined, 112 | }; 113 | } 114 | }); 115 | 116 | expect(map).toHaveProperty('sourcesContent', [null]); 117 | }); 118 | 119 | test('excludes null sourceContent if sourcemap is not self-containing', () => { 120 | const map = remapping(rawMap, (name: string) => { 121 | if (name === 'transpiled.js') { 122 | return { 123 | ...transpiledMap, 124 | sourcesContent: [null], 125 | }; 126 | } 127 | }); 128 | 129 | expect(map).toHaveProperty('sourcesContent', [null]); 130 | }); 131 | 132 | test('ignores if original source is ignored', () => { 133 | const map = remapping(rawMap, (name: string) => { 134 | if (name === 'transpiled.js') { 135 | return { 136 | ...transpiledMap, 137 | ignoreList: [0], 138 | }; 139 | } 140 | }); 141 | 142 | expect(map).toHaveProperty('ignoreList', [0]); 143 | }); 144 | 145 | test('unignores if sourcemap has no ignoreList', () => { 146 | const map = remapping(rawMap, (name: string) => { 147 | if (name === 'transpiled.js') { 148 | return { 149 | ...transpiledMap, 150 | ignoreList: undefined, 151 | }; 152 | } 153 | }); 154 | 155 | expect(map).toHaveProperty('ignoreList', []); 156 | }); 157 | 158 | test('unignores if sourcemap unignores original source', () => { 159 | const map = remapping(rawMap, (name: string) => { 160 | if (name === 'transpiled.js') { 161 | return { 162 | ...transpiledMap, 163 | ignoreList: [], 164 | }; 165 | } 166 | }); 167 | 168 | expect(map).toHaveProperty('ignoreList', []); 169 | }); 170 | 171 | describe('boolean options', () => { 172 | test('excludes sourcesContent if `excludeContent` is set', () => { 173 | const map = remapping( 174 | rawMap, 175 | (name: string) => { 176 | if (name === 'transpiled.js') { 177 | return transpiledMap; 178 | } 179 | }, 180 | true 181 | ); 182 | 183 | expect(map).not.toHaveProperty('sourcesContent'); 184 | }); 185 | }); 186 | 187 | describe('options bag', () => { 188 | test('excludes sourcesContent if `excludeContent` is set', () => { 189 | const map = remapping( 190 | rawMap, 191 | (name: string) => { 192 | if (name === 'transpiled.js') { 193 | return transpiledMap; 194 | } 195 | }, 196 | { excludeContent: true } 197 | ); 198 | 199 | expect(map).not.toHaveProperty('sourcesContent'); 200 | }); 201 | 202 | test('returns decoded sourcemap if `decodedMappings` is set', () => { 203 | const map = remapping( 204 | rawMap, 205 | (name: string) => { 206 | if (name === 'transpiled.js') { 207 | return transpiledMap; 208 | } 209 | }, 210 | { decodedMappings: true } 211 | ); 212 | 213 | expect(map).toHaveProperty('mappings', [[[0, 0, 2, 2, 0]]]); 214 | }); 215 | }); 216 | }); 217 | -------------------------------------------------------------------------------- /test/unit/source-map-tree.ts: -------------------------------------------------------------------------------- 1 | import { toDecodedMap } from '@jridgewell/gen-mapping'; 2 | import { TraceMap } from '@jridgewell/trace-mapping'; 3 | 4 | import { 5 | OriginalSource, 6 | MapSource, 7 | originalPositionFor, 8 | traceMappings, 9 | } from '../../src/source-map-tree'; 10 | import type { DecodedSourceMap } from '../../src/types'; 11 | 12 | describe('MapSource', () => { 13 | describe('traceMappings()', () => { 14 | const sourceRoot = 'foo'; 15 | const baseMap: DecodedSourceMap = { 16 | mappings: [], 17 | names: ['name'], 18 | sourceRoot, 19 | sources: ['child.js'], 20 | version: 3, 21 | }; 22 | const child = MapSource( 23 | new TraceMap({ 24 | mappings: [ 25 | [ 26 | [0, 0, 0, 0], 27 | [1, 0, 0, 0], 28 | [2, 0, 0, 0], 29 | [4, 0, 1, 1], 30 | ], // line 0 31 | [[1, 0, 0, 0, 0], [6]], // line 1 32 | ], 33 | names: ['child'], 34 | sources: ['original.js'], 35 | version: 3, 36 | }), 37 | [OriginalSource(`${sourceRoot}/original.js`, '', false)] 38 | ); 39 | 40 | test('records segment if segment is 1-length', () => { 41 | const map: DecodedSourceMap = { 42 | ...baseMap, 43 | mappings: [[[0, 0, 0, 4], [5]]], 44 | }; 45 | 46 | const tree = MapSource(new TraceMap(map), [child]); 47 | const traced = toDecodedMap(traceMappings(tree)); 48 | expect(traced.mappings).toEqual([[[0, 0, 1, 1], [5]]]); 49 | }); 50 | 51 | test('records segment if trace hits 1-length segment', () => { 52 | const map: DecodedSourceMap = { 53 | ...baseMap, 54 | mappings: [ 55 | [ 56 | [0, 0, 0, 4], 57 | [5, 0, 1, 6], 58 | ], 59 | ], 60 | }; 61 | 62 | const tree = MapSource(new TraceMap(map), [child]); 63 | const traced = toDecodedMap(traceMappings(tree)); 64 | expect(traced.mappings).toEqual([[[0, 0, 1, 1], [5]]]); 65 | }); 66 | 67 | test('skips segment if trace returns null', () => { 68 | const sourceIndex = 0; 69 | const line = 10; // There is no line 10 in child's mappings. 70 | const column = 0; 71 | const map: DecodedSourceMap = { 72 | ...baseMap, 73 | mappings: [[[0, sourceIndex, line, column]]], 74 | }; 75 | 76 | const tree = MapSource(new TraceMap(map), [child]); 77 | const traced = toDecodedMap(traceMappings(tree)); 78 | expect(traced.mappings).toEqual([]); 79 | }); 80 | 81 | test('traces name if segment is 5-length', () => { 82 | const sourceIndex = 0; 83 | const line = 0; 84 | const column = 0; 85 | const nameIndex = 0; 86 | const name = 'name'; 87 | const map: DecodedSourceMap = { 88 | ...baseMap, 89 | mappings: [[[0, sourceIndex, line, column, nameIndex]]], 90 | names: [name], 91 | }; 92 | 93 | const tree = MapSource(new TraceMap(map), [child]); 94 | const traced = toDecodedMap(traceMappings(tree)); 95 | expect(traced.mappings).toEqual([[[0, 0, 0, 0, 0]]]); 96 | expect(traced).toMatchObject({ 97 | names: [name], 98 | }); 99 | }); 100 | 101 | test('maps into traced segment', () => { 102 | const sourceIndex = 0; 103 | const line = 0; 104 | const column = 4; 105 | const map: DecodedSourceMap = { 106 | ...baseMap, 107 | mappings: [[[0, sourceIndex, line, column]]], 108 | }; 109 | 110 | const tree = MapSource(new TraceMap(map), [child]); 111 | const traced = toDecodedMap(traceMappings(tree)); 112 | expect(traced.mappings).toEqual([[[0, 0, 1, 1]]]); 113 | }); 114 | 115 | test('maps into traced segment with name', () => { 116 | const sourceIndex = 0; 117 | const line = 1; 118 | const column = 1; 119 | const map: DecodedSourceMap = { 120 | ...baseMap, 121 | mappings: [[[0, sourceIndex, line, column]]], 122 | }; 123 | 124 | const tree = MapSource(new TraceMap(map), [child]); 125 | const traced = toDecodedMap(traceMappings(tree)); 126 | expect(traced.mappings).toEqual([[[0, 0, 0, 0, 0]]]); 127 | expect(traced).toMatchObject({ 128 | names: ['child'], 129 | }); 130 | }); 131 | 132 | test('defaults decoded return map with original data', () => { 133 | const extras = { 134 | file: 'foobar.js', 135 | // TODO: support sourceRoot 136 | // sourceRoot: 'https://foobar.com/', 137 | }; 138 | const map: DecodedSourceMap = { 139 | ...baseMap, 140 | mappings: [], 141 | ...extras, 142 | }; 143 | 144 | const tree = MapSource(new TraceMap(map), [child]); 145 | const traced = toDecodedMap(traceMappings(tree)); 146 | expect(traced).toMatchObject(extras); 147 | }); 148 | 149 | test('resolves source files realtive to sourceRoot', () => { 150 | const map: DecodedSourceMap = { 151 | ...baseMap, 152 | mappings: [[[0, 0, 0, 0]]], 153 | }; 154 | 155 | const tree = MapSource(new TraceMap(map), [child]); 156 | const traced = toDecodedMap(traceMappings(tree)); 157 | expect(traced).toMatchObject({ 158 | // TODO: support sourceRoot 159 | sourceRoot: undefined, 160 | sources: ['foo/original.js'], 161 | }); 162 | }); 163 | 164 | test('truncates mappings to the last line with segment', () => { 165 | const map: DecodedSourceMap = { 166 | ...baseMap, 167 | mappings: [[[0, 0, 0, 0]], [], []], 168 | sourceRoot, 169 | }; 170 | 171 | const tree = MapSource(new TraceMap(map), [child]); 172 | const traced = toDecodedMap(traceMappings(tree)); 173 | expect(traced.mappings).toEqual([[[0, 0, 0, 0]]]); 174 | }); 175 | 176 | test('truncates empty mappings', () => { 177 | const map: DecodedSourceMap = { 178 | ...baseMap, 179 | mappings: [[], [], []], 180 | sourceRoot, 181 | }; 182 | 183 | const tree = MapSource(new TraceMap(map), [child]); 184 | const traced = toDecodedMap(traceMappings(tree)); 185 | expect(traced.mappings).toEqual([]); 186 | }); 187 | 188 | describe('redundant segments', () => { 189 | it('skips redundant segments on the same line', () => { 190 | const map: DecodedSourceMap = { 191 | ...baseMap, 192 | mappings: [ 193 | [ 194 | [0, 0, 0, 0], 195 | [1, 0, 0, 0], 196 | ], 197 | ], 198 | }; 199 | 200 | const tree = MapSource(new TraceMap(map), [child]); 201 | const traced = toDecodedMap(traceMappings(tree)); 202 | expect(traced.mappings).toEqual([[[0, 0, 0, 0]]]); 203 | }); 204 | 205 | it('keeps redundant segments on another line', () => { 206 | const map: DecodedSourceMap = { 207 | ...baseMap, 208 | mappings: [[[0, 0, 0, 0]], [[0, 0, 0, 0]]], 209 | }; 210 | 211 | const tree = MapSource(new TraceMap(map), [child]); 212 | const traced = toDecodedMap(traceMappings(tree)); 213 | expect(traced.mappings).toEqual([[[0, 0, 0, 0]], [[0, 0, 0, 0]]]); 214 | }); 215 | }); 216 | }); 217 | 218 | describe('originalPositionFor()', () => { 219 | const map: DecodedSourceMap = { 220 | mappings: [ 221 | [ 222 | [0, 0, 0, 0], 223 | [1, 0, 0, 0], 224 | [2, 0, 0, 0], 225 | [4, 0, 1, 1], 226 | ], // line 0 227 | [[2, 0, 0, 0]], // line 1 - maps to line 0 col 0 228 | [[0]], // line 2 has 1 length segment 229 | [[0, 0, 0, 0, 0]], // line 3 has a name 230 | [ 231 | [0, 0, 4, 0], 232 | [5, 0, 4, 6], 233 | ], // line 4 is identical to line 4 of source except col 5 was removed eg 01234567890 -> 012346789 234 | [[0, 0, 5, 0], [5], [6, 0, 5, 5]], // line 4 is identical to line 4 of source except a char was added at col 5 eg 01234*56789 -> 0123*456789 235 | ], 236 | names: ['name'], 237 | sources: ['child.js'], 238 | version: 3, 239 | }; 240 | const tree = MapSource(new TraceMap(map), [OriginalSource('child.js', '', false)]); 241 | 242 | test('traces LineSegments to the segment with matching generated column', () => { 243 | const trace = originalPositionFor(tree, 0, 4, ''); 244 | expect(trace).toMatchObject({ line: 1, column: 1 }); 245 | }); 246 | 247 | test('traces all generated cols on a line back to their source when source had characters removed', () => { 248 | const expectedCols = [0, 0, 0, 0, 0, 6, 6, 6, 6]; 249 | for (let genCol = 0; genCol < expectedCols.length; genCol++) { 250 | const trace = originalPositionFor(tree, 4, genCol, ''); 251 | expect(trace).toMatchObject({ line: 4, column: expectedCols[genCol] }); 252 | } 253 | }); 254 | 255 | test('traces all generated cols on a line back to their source when source had characters added', () => { 256 | const expectedCols = [0, 0, 0, 0, 0, null, 5, 5, 5, 5, 5]; 257 | for (let genCol = 0; genCol < expectedCols.length; genCol++) { 258 | const trace = originalPositionFor(tree, 5, genCol, ''); 259 | if (expectedCols[genCol] == null) { 260 | expect(trace).toMatchObject({ source: '' }); 261 | } else { 262 | expect(trace).toMatchObject({ line: 5, column: expectedCols[genCol] }); 263 | } 264 | } 265 | }); 266 | 267 | test('returns null if line is longer than mapping lines', () => { 268 | const trace = originalPositionFor(tree, 10, 0, ''); 269 | expect(trace).toBe(null); 270 | }); 271 | 272 | test('returns null if no matching segment column', () => { 273 | //line 1 col 0 of generated doesn't exist in the original source 274 | const trace = originalPositionFor(tree, 1, 0, ''); 275 | expect(trace).toBe(null); 276 | }); 277 | 278 | test('returns sourceless segment object if segment is 1-length', () => { 279 | const trace = originalPositionFor(tree, 2, 0, ''); 280 | expect(trace).toMatchObject({ source: '' }); 281 | }); 282 | 283 | test('passes in outer name to trace', () => { 284 | const trace = originalPositionFor(tree, 0, 0, 'foo'); 285 | expect(trace).toMatchObject({ name: 'foo' }); 286 | }); 287 | 288 | test('overrides name if segment is 5-length', () => { 289 | const trace = originalPositionFor(tree, 3, 0, 'foo'); 290 | expect(trace).toMatchObject({ name: 'name' }); 291 | }); 292 | 293 | describe('tracing same line multiple times', () => { 294 | describe('later column', () => { 295 | test('returns matching segment after match', () => { 296 | expect(originalPositionFor(tree, 0, 1, '')).not.toBe(null); 297 | const trace = originalPositionFor(tree, 0, 4, ''); 298 | expect(trace).toMatchObject({ line: 1, column: 1 }); 299 | }); 300 | 301 | test('returns matching segment after null match', () => { 302 | expect(originalPositionFor(tree, 1, 0, '')).toBe(null); 303 | const trace = originalPositionFor(tree, 1, 2, ''); 304 | expect(trace).toMatchObject({ line: 0, column: 0 }); 305 | }); 306 | 307 | test('returns null segment segment after null match', () => { 308 | expect(originalPositionFor(tree, 1, 0, '')).toBe(null); 309 | const trace = originalPositionFor(tree, 1, 1, ''); 310 | expect(trace).toBe(null); 311 | }); 312 | 313 | test('returns matching segment after almost match', () => { 314 | expect(originalPositionFor(tree, 4, 2, '')).not.toBe(null); 315 | const trace = originalPositionFor(tree, 4, 5, ''); 316 | expect(trace).toMatchObject({ line: 4, column: 6 }); 317 | }); 318 | }); 319 | 320 | describe('earlier column', () => { 321 | test('returns matching segment after match', () => { 322 | expect(originalPositionFor(tree, 0, 4, '')).not.toBe(null); 323 | const trace = originalPositionFor(tree, 0, 1, ''); 324 | expect(trace).toMatchObject({ line: 0, column: 0 }); 325 | }); 326 | 327 | test('returns null segment segment after null match', () => { 328 | expect(originalPositionFor(tree, 1, 1, '')).toBe(null); 329 | const trace = originalPositionFor(tree, 1, 0, ''); 330 | expect(trace).toBe(null); 331 | }); 332 | 333 | test('returns matching segment after almost match', () => { 334 | expect(originalPositionFor(tree, 4, 2, '')).not.toBe(null); 335 | const trace = originalPositionFor(tree, 4, 0, ''); 336 | expect(trace).toMatchObject({ line: 4, column: 0 }); 337 | }); 338 | }); 339 | }); 340 | }); 341 | }); 342 | -------------------------------------------------------------------------------- /test/unit/source-map.ts: -------------------------------------------------------------------------------- 1 | import { GenMapping, addSegment, setIgnore, setSourceContent } from '@jridgewell/gen-mapping'; 2 | import SourceMap from '../../src/source-map'; 3 | 4 | describe('SourceMap', () => { 5 | const opts = { 6 | excludeContent: false, 7 | decodedMappings: false, 8 | }; 9 | 10 | test('it is a compliant, v3 sourcemap', () => { 11 | const traced = new GenMapping(); 12 | addSegment(traced, 0, 0, 'file.js', 0, 0, ''); 13 | 14 | const map = new SourceMap(traced, opts); 15 | expect(map).toHaveProperty('mappings', 'AAAA'); 16 | expect(map).toHaveProperty('names', []); 17 | expect(map).toHaveProperty('sources', ['file.js']); 18 | expect(map).toHaveProperty('version', 3); 19 | }); 20 | 21 | test('it can include a file', () => { 22 | const file = 'foobar.js'; 23 | const traced = new GenMapping({ file }); 24 | addSegment(traced, 0, 0, 'file.js', 0, 0, ''); 25 | 26 | const map = new SourceMap(traced, opts); 27 | expect(map).toHaveProperty('file', file); 28 | }); 29 | 30 | // TODO: support sourceRoot 31 | test.skip('it can include a sourceRoot', () => { 32 | const sourceRoot = 'https://foo.com/'; 33 | const traced = new GenMapping({ sourceRoot }); 34 | addSegment(traced, 0, 0, 'file.js', 0, 0, ''); 35 | 36 | const map = new SourceMap(traced, opts); 37 | expect(map).toHaveProperty('sourceRoot', sourceRoot); 38 | }); 39 | 40 | test('it can include a sourcesContent', () => { 41 | const content = '1 + 1'; 42 | const traced = new GenMapping(); 43 | addSegment(traced, 0, 0, 'file.js', 0, 0, ''); 44 | setSourceContent(traced, 'file.js', content); 45 | 46 | const map = new SourceMap(traced, opts); 47 | expect(map).toHaveProperty('sourcesContent', [content]); 48 | }); 49 | 50 | test('sourcesContent can be manually excluded', () => { 51 | const content = '1 + 1'; 52 | const traced = new GenMapping(); 53 | addSegment(traced, 0, 0, 'file.js', 0, 0, ''); 54 | setSourceContent(traced, 'file.js', content); 55 | 56 | const map = new SourceMap(traced, { ...opts, excludeContent: true }); 57 | expect(map).not.toHaveProperty('sourcesContent'); 58 | }); 59 | 60 | test('it can include ignoreList', () => { 61 | const traced = new GenMapping(); 62 | addSegment(traced, 0, 0, 'file.js', 0, 0, ''); 63 | setIgnore(traced, 'file.js'); 64 | 65 | const map = new SourceMap(traced, opts); 66 | expect(map).toHaveProperty('ignoreList', [0]); 67 | }); 68 | 69 | test('mappings can be decoded', () => { 70 | const traced = new GenMapping(); 71 | addSegment(traced, 0, 0, 'file.js', 0, 0, ''); 72 | 73 | const map = new SourceMap(traced, { ...opts, decodedMappings: true }); 74 | expect(map).toHaveProperty('mappings', [[[0, 0, 0, 0]]]); 75 | }); 76 | 77 | describe('toString()', () => { 78 | test('returns the sourcemap in JSON', () => { 79 | const traced = new GenMapping(); 80 | addSegment(traced, 0, 0, 'file.js', 0, 0, ''); 81 | 82 | const map = new SourceMap(traced, opts); 83 | expect(JSON.parse(map.toString())).toEqual(map); 84 | }); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "emitDeclarationOnly": true, 5 | "rootDir": "src" 6 | }, 7 | "include": [ 8 | "src" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "esModuleInterop": true, 5 | "target": "es2015", 6 | "module": "es2015", 7 | "lib": ["es2015"], 8 | "strict": true, 9 | "sourceMap": true, 10 | "inlineSources": true, 11 | "declaration": true, 12 | "allowSyntheticDefaultImports": true, 13 | "declarationDir": "dist/types", 14 | "typeRoots": ["node_modules/@types"] 15 | }, 16 | "exclude": ["dist"], 17 | "include": ["src", "test"] 18 | } 19 | --------------------------------------------------------------------------------