├── .eslintrc.js ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── README.md ├── appveyor.yml ├── lib ├── coder.js ├── endsWith.js ├── index.js ├── source-map.js └── vlq.js ├── package.json ├── test ├── .gitignore ├── actual │ └── .gitkeep ├── eslint-test.js ├── expected │ ├── coffee-example.js │ ├── coffee-example.map │ ├── custom.map │ ├── custom2.map │ ├── external-content.js │ ├── external-content.map │ ├── final.js │ ├── final.map │ ├── from-inline.js │ ├── from-inline.map │ ├── from-string.js │ ├── from-string.map │ ├── hello-world-output-1.map │ ├── hello-world-output-2.map │ ├── hello-world-output.js │ ├── iife-wrapping.js │ ├── iife-wrapping.map │ ├── mapdird.js │ ├── mapdird2.js │ ├── no-sources-content-out.js │ ├── no-sources-content-out.map │ ├── sprintf-multi.js │ ├── sprintf-multi.map │ ├── test-short.js │ ├── test-short.map │ ├── too-few-sources-out.js │ ├── too-few-sources-out.map │ ├── too-many-sources-out.js │ ├── too-many-sources-out.map │ ├── with-broken-input-map.js │ └── with-broken-input-map.map ├── fixtures │ ├── coffee │ │ ├── aa-loader.js │ │ ├── rewriter.coffee │ │ ├── rewriter.js │ │ └── rewriter.js.map │ ├── emptyish │ │ ├── emptyish.js │ │ └── src │ │ │ ├── b.js │ │ │ ├── b.js.map │ │ │ └── b.ts │ ├── external-content │ │ ├── a.js │ │ ├── all-inner.js │ │ ├── all-inner.map │ │ ├── broken-link.js │ │ └── inside │ │ │ └── b.js │ ├── from-string │ │ ├── external-mapped.js │ │ └── external-mapped.js-map.map │ ├── iife-wrapping │ │ ├── iife-end │ │ └── iife-start │ ├── inline-mapped.js │ ├── inner │ │ ├── first.js │ │ └── second.js │ ├── other │ │ ├── fourth.js │ │ └── third.js │ ├── short │ │ ├── rewriter.coffee │ │ ├── rewriter.js │ │ └── rewriter.js.map │ ├── src │ │ └── sprintf.js │ └── typescript │ │ ├── 1 │ │ ├── hello-world.js │ │ └── hello-world.ts │ │ └── 2 │ │ ├── hello-world.js │ │ └── hello-world.ts ├── index.html └── test.js ├── tools └── dissect.js └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | ecmaVersion: 2017, 5 | sourceType: 'module', 6 | }, 7 | extends: [ 8 | 'eslint:recommended', 9 | ], 10 | env: { 11 | es6: true, 12 | node: true, 13 | }, 14 | rules: { 15 | 'no-console': 'off', 16 | 17 | 'no-var': 'error', 18 | }, 19 | overrides: [{ 20 | // test files 21 | files: ['test/**/*.js'], 22 | env: { 23 | mocha: true, 24 | }, 25 | }], 26 | }; 27 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | pull_request: {} 9 | 10 | concurrency: 11 | group: ci-${{ github.head_ref || github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | test: 16 | name: "Tests" 17 | runs-on: ubuntu-latest 18 | strategy: 19 | matrix: 20 | node: [ 10.x, 12.x, 14.x, 16.x, 18.x ] 21 | steps: 22 | - uses: actions/checkout@v3 23 | - name: Install Node 24 | uses: actions/setup-node@v3 25 | with: 26 | node-version: ${{ matrix.node }} 27 | cache: yarn 28 | - name: Install Dependencies 29 | run: yarn install --frozen-lockfile 30 | - name: Run Tests 31 | run: yarn run test 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Fast Source Map Concatenation 2 | ----------------------------- 3 | 4 | [![Build Status](https://travis-ci.org/ef4/fast-sourcemap-concat.svg?branch=master)](https://travis-ci.org/ef4/fast-sourcemap-concat) 5 | [![Build status](https://ci.appveyor.com/api/projects/status/0iy8on5vieoh3mp2/branch/master?svg=true)](https://ci.appveyor.com/project/embercli/fast-sourcemap-concat/branch/master) 6 | 7 | This library lets you concatenate files (with or without their own 8 | pre-generated sourcemaps), and get a single output file along with a 9 | sourcemap. 10 | 11 | It was written for use in ember-cli via broccoli-concat. 12 | 13 | available options 14 | ------- 15 | 16 | ### `baseDir` 17 | 18 | Type: `path` 19 | 20 | The root directory for resolving source and map files. 21 | 22 | If no value is given, will default to the current working directory. 23 | 24 | ### `cache` 25 | 26 | Type: `Object` 27 | 28 | (Optional) Used to cache encoder results. Passing this in from the outside allows for many instances of the plugin to share one cache. 29 | 30 | ### `file` 31 | 32 | Type: `string` 33 | 34 | The value assigned to the sourcemap's `"file"` key, as described in the [sourcemaps spec](http://sourcemaps.info/spec.html). 35 | 36 | If no value is given, will default to the basename of `outputFile`. 37 | 38 | ### `fs` 39 | 40 | Type: `Object` 41 | 42 | A custom Node File System module. 43 | 44 | If no value is given, will default to `require('fs-extra')`. 45 | 46 | ### `mapCommentType` 47 | 48 | Type: `string` 49 | 50 | If `'line'` is specified, `sourceMappingURL` will be written in a single-line comment (`//`). 51 | 52 | If `'none'` is specified, no reference to the source map is included in the source file. 53 | 54 | If anything else truthy is specified, `sourceMappingURL` will be written in a block comment (`/* */`). 55 | 56 | If no value is given, or a falsey value is given, will default to `'line'`. 57 | 58 | ### `mapFile` 59 | 60 | Type: `string` 61 | 62 | Filename where the concatenated sourcemap will be written. 63 | 64 | If no value is given, will default to the value of `outputFile`, but with `'.js'` replaced by `'.map'`. 65 | 66 | ### `mapStyle` 67 | 68 | Type: `string` 69 | 70 | If `'data'` is specified, `sourceMappingURL` will contain a data URL instead of `mapURL`. 71 | 72 | If `'file'` is specified, `sourceMappingURL` will contain `mapURL`. 73 | 74 | If anything else truthy is specified, the behavior is undefined. 75 | 76 | If no value is given, or a falsey value is given, will default to `'file'`. 77 | 78 | ### `mapURL` 79 | 80 | Type: `string` 81 | 82 | The value written to the `sourceMappingURL` comment. 83 | 84 | If no value is given, will default to the basename of `mapFile`. 85 | 86 | ### `outputFile` 87 | 88 | Type: `string` 89 | 90 | Filename where the concatenated source code will be written. 91 | 92 | If you don't specify this you must specify `mapURL` and `file`. 93 | 94 | ### `pluginId` 95 | 96 | Type: `number` 97 | 98 | A unique id for one instance of this lib. Ensures unique filenames when reporting stats via `CONCAT_STATS` env var. 99 | 100 | ### `sourceRoot` 101 | 102 | Type: `string` 103 | 104 | The value assigned to the sourcemap's `"sourceRoot"` key, as described in the [sourcemaps spec](http://sourcemaps.info/spec.html). 105 | 106 | source-map dependency 107 | --------------------- 108 | 109 | We depend on ([a fork of](https://github.com/jacobq/source-map)) 110 | mozilla's [`source-map`](https://github.com/mozilla/source-map) library, 111 | but only to use their base64-vlq implementation, 112 | which is in turn based on the version in the Closure Compiler. 113 | (The fork restores the vlq `decode` functionality, which `source-map` 114 | considers as private API and removed in v0.5.0, and is compatible 115 | with node 18.x, which `source-map` < 0.8.0-beta.0 was not.) 116 | 117 | We can concatenate much faster than source-map because we are 118 | specifically optimized for line-by-line concatenation. 119 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # https://www.appveyor.com/docs/appveyor-yml/ 2 | 3 | # Test against these versions of Node.js. 4 | environment: 5 | MOCHA_REPORTER: "mocha-appveyor-reporter" 6 | matrix: 7 | - nodejs_version: "10" 8 | - nodejs_version: "12" 9 | 10 | branches: 11 | except: 12 | - canary 13 | 14 | # Install scripts. (runs after repo cloning) 15 | install: 16 | - git rev-parse HEAD 17 | # Get the latest stable version of Node 0.STABLE.latest 18 | - ps: Install-Product node $env:nodejs_version 19 | # Install PhantomJS 20 | - npm install mocha-appveyor-reporter 21 | # Typical npm stuff. 22 | - npm version 23 | - npm install 24 | 25 | cache: 26 | - '%APPDATA%\npm-cache' 27 | 28 | 29 | # Post-install test scripts. 30 | test_script: 31 | # Output useful info for debugging. 32 | - npm version 33 | - cmd: npm run test 34 | 35 | # Don't actually build. 36 | build: off 37 | 38 | # Set build version format here instead of in the admin panel. 39 | version: "{build}" 40 | -------------------------------------------------------------------------------- /lib/coder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const vlq = require('./vlq'); 4 | 5 | const FIELDS = ['generatedColumn', 'source', 'originalLine', 'originalColumn', 'name']; 6 | 7 | class Coder { 8 | decode(mapping) { 9 | let value = this.rawDecode(mapping); 10 | let output = {}; 11 | 12 | for (let i=0; i 0 && this.nextFileSourceNeedsComma) { 102 | this.content.mappings += ','; 103 | this.nextFileSourceNeedsComma = false; 104 | } 105 | 106 | if (typeof inputSrcMap === 'string') { 107 | inputSrcMap = JSON.parse(inputSrcMap); 108 | } 109 | 110 | if (inputSrcMap === undefined && url) { 111 | inputSrcMap = this._resolveSourcemap(filename, url); 112 | } 113 | 114 | if (inputSrcMap) { 115 | let haveLines = countNewLines(source); 116 | source = this._addMap(filename, inputSrcMap, source, haveLines); 117 | } else { 118 | logger.info('generating new map: %s', filename); 119 | this.content.sources.push(filename); 120 | this.content.sourcesContent.push(source); 121 | this._generateNewMap(source); 122 | } 123 | 124 | this.stream.write(source); 125 | } 126 | 127 | _cacheEncoderResults(key, operations, filename) { 128 | let encoderState = this.encoder.copy(); 129 | let initialLinesMapped = this.linesMapped; 130 | let cacheEntry = this.cache[key]; 131 | let finalState; 132 | 133 | if (cacheEntry) { 134 | // The cache contains the encoder-differential for our file. So 135 | // this updates encoderState to the final value our encoder will 136 | // have after processing the file. 137 | encoderState.decode(cacheEntry.encoder); 138 | // We provide that final value as a performance hint. 139 | operations.call(this, { 140 | encoder: encoderState, 141 | lines: cacheEntry.lines 142 | }); 143 | 144 | logger.info('encoder cache hit: %s', filename); 145 | } else { 146 | 147 | // Run the operations with no hint because we don't have one yet. 148 | operations.call(this); 149 | // Then store the encoder differential in the cache. 150 | finalState = this.encoder.copy(); 151 | finalState.subtract(encoderState); 152 | this.cache[key] = { 153 | encoder: finalState.serialize(), 154 | lines: this.linesMapped - initialLinesMapped 155 | }; 156 | 157 | logger.info('encoder cache prime: %s', filename); 158 | } 159 | } 160 | 161 | // This is useful for things like separators that you're appending to 162 | // your JS file that don't need to have their own source mapping, but 163 | // will alter the line numbering for subsequent files. 164 | addSpace(source) { 165 | this.stream.write(source); 166 | let lineCount = countNewLines(source); 167 | if (lineCount === 0) { 168 | this.column += source.length; 169 | } else { 170 | this.column = 0; 171 | let mappings = this.content.mappings; 172 | for (let i = 0; i < lineCount; i++) { 173 | mappings += ';'; 174 | this.nextFileSourceNeedsComma = false; 175 | } 176 | this.content.mappings = mappings; 177 | } 178 | } 179 | 180 | _generateNewMap(source) { 181 | let mappings = this.content.mappings; 182 | let lineCount = countNewLines(source); 183 | const encodedVal = this.encoder.encode({ 184 | generatedColumn: this.column, 185 | source: this.content.sources.length-1, 186 | originalLine: 0, 187 | originalColumn: 0 188 | }); 189 | 190 | mappings += encodedVal; 191 | this.nextFileSourceNeedsComma = !(endsWith(encodedVal, ';') || endsWith(encodedVal, ',')); 192 | 193 | if (lineCount === 0) { 194 | // no newline in the source. Keep outputting one big line. 195 | this.column += source.length; 196 | } else { 197 | // end the line 198 | this.column = 0; 199 | this.encoder.resetColumn(); 200 | mappings += ';'; 201 | this.nextFileSourceNeedsComma = false; 202 | this.encoder.adjustLine(lineCount-1); 203 | } 204 | 205 | // For the remainder of the lines (if any), we're just following 206 | // one-to-one. 207 | for (let i = 0; i < lineCount-1; i++) { 208 | mappings += 'AACA;'; 209 | this.nextFileSourceNeedsComma = false; 210 | } 211 | this.linesMapped += lineCount; 212 | this.content.mappings = mappings; 213 | } 214 | 215 | _resolveSourcemap(filename, url) { 216 | let srcMap; 217 | let match = /^data:.+?;base64,/.exec(url); 218 | 219 | try { 220 | if (match) { 221 | srcMap = Buffer.from(url.slice(match[0].length), 'base64'); 222 | } else if (this.baseDir && url.slice(0,1) === '/') { 223 | srcMap = this.fs.readFileSync( 224 | path.join(this.baseDir, url), 225 | 'utf8' 226 | ); 227 | } else { 228 | srcMap = this.fs.readFileSync( 229 | path.join(path.dirname(this._resolveFile(filename)), url), 230 | 'utf8' 231 | ); 232 | } 233 | return JSON.parse(srcMap); 234 | } catch (err) { 235 | this._warn('Warning: ignoring input sourcemap for ' + filename + ' because ' + err.message); 236 | } 237 | } 238 | 239 | _addMap(filename, srcMap, source) { 240 | let initialLinesMapped = this.linesMapped; 241 | let haveLines = countNewLines(source); 242 | let self = this; 243 | 244 | if (this.cache) { 245 | this._cacheEncoderResults(hash(JSON.stringify(srcMap)), function(cacheHint) { 246 | self._assimilateExistingMap(filename, srcMap, cacheHint); 247 | }, filename); 248 | } else { 249 | this._assimilateExistingMap(filename, srcMap); 250 | } 251 | 252 | while (this.linesMapped - initialLinesMapped < haveLines) { 253 | // This cleans up after upstream sourcemaps that are too short 254 | // for their sourcecode so they don't break the rest of our 255 | // mapping. Coffeescript does this. 256 | this.content.mappings += ';'; 257 | this.nextFileSourceNeedsComma = false; 258 | this.linesMapped++; 259 | } 260 | while (haveLines < this.linesMapped - initialLinesMapped) { 261 | // Likewise, this cleans up after upstream sourcemaps that are 262 | // too long for their sourcecode. 263 | source += "\n"; 264 | haveLines++; 265 | } 266 | return source; 267 | } 268 | 269 | 270 | _assimilateExistingMap(filename, srcMap, cacheHint) { 271 | let content = this.content; 272 | let sourcesOffset = content.sources.length; 273 | let namesOffset = content.names.length; 274 | 275 | content.sources = content.sources.concat(this._resolveSources(srcMap)); 276 | content.sourcesContent = content.sourcesContent.concat(this._resolveSourcesContent(srcMap, filename)); 277 | while (content.sourcesContent.length > content.sources.length) { 278 | content.sourcesContent.pop(); 279 | } 280 | while (content.sourcesContent.length < content.sources.length) { 281 | content.sourcesContent.push(null); 282 | } 283 | content.names = content.names.concat(srcMap.names); 284 | this._scanMappings(srcMap, sourcesOffset, namesOffset, cacheHint); 285 | } 286 | 287 | _resolveSources(srcMap) { 288 | let baseDir = this.baseDir; 289 | if (!baseDir) { 290 | return srcMap.sources; 291 | } 292 | return srcMap.sources.map(function(src) { 293 | return src.replace(baseDir, ''); 294 | }); 295 | } 296 | 297 | _resolveSourcesContent(srcMap, filename) { 298 | if (srcMap.sourcesContent) { 299 | // Upstream srcmap already had inline content, so easy. 300 | return srcMap.sourcesContent; 301 | } else { 302 | // Look for original sources relative to our input source filename. 303 | return srcMap.sources.map(function(source){ 304 | let fullPath; 305 | if (path.isAbsolute(source)) { 306 | fullPath = source; 307 | } else { 308 | fullPath = path.join(path.dirname(this._resolveFile(filename)), source); 309 | } 310 | return ensurePosix(this.fs.readFileSync(fullPath, 'utf-8')); 311 | }.bind(this)); 312 | } 313 | } 314 | 315 | _scanMappings(srcMap, sourcesOffset, namesOffset, cacheHint) { 316 | let mappings = this.content.mappings; 317 | let decoder = new Coder(); 318 | let inputMappings = srcMap.mappings; 319 | let pattern = /^([;,]*)([^;,]*)/; 320 | let continuation = /^([;,]*)((?:AACA;)+)/; 321 | let initialMappedLines = this.linesMapped; 322 | let match; 323 | let lines; 324 | 325 | while (inputMappings.length > 0) { 326 | match = pattern.exec(inputMappings); 327 | 328 | // If the entry was preceded by separators, copy them through. 329 | if (match[1]) { 330 | mappings += match[1]; 331 | this.nextFileSourceNeedsComma = !(endsWith(match[1], ',') || endsWith(match[1], ';')); 332 | lines = match[1].replace(/,/g, '').length; 333 | if (lines > 0) { 334 | this.linesMapped += lines; 335 | this.encoder.resetColumn(); 336 | decoder.resetColumn(); 337 | } 338 | } 339 | 340 | // Re-encode the entry. 341 | if (match[2]){ 342 | let value = decoder.decode(match[2]); 343 | value.generatedColumn += this.column; 344 | this.column = 0; 345 | if (sourcesOffset && value.hasOwnProperty('source')) { 346 | value.source += sourcesOffset; 347 | decoder.prev_source += sourcesOffset; 348 | sourcesOffset = 0; 349 | } 350 | if (namesOffset && value.hasOwnProperty('name')) { 351 | value.name += namesOffset; 352 | decoder.prev_name += namesOffset; 353 | namesOffset = 0; 354 | } 355 | const encodedVal = this.encoder.encode(value); 356 | mappings += encodedVal; 357 | this.nextFileSourceNeedsComma = !(endsWith(encodedVal, ',') || endsWith(encodedVal, ';')); 358 | } 359 | 360 | inputMappings = inputMappings.slice(match[0].length); 361 | 362 | // Once we've applied any offsets, we can try to jump ahead. 363 | if (!sourcesOffset && !namesOffset) { 364 | if (cacheHint) { 365 | // Our cacheHint tells us what our final encoder state will be 366 | // after processing this file. And since we've got nothing 367 | // left ahead that needs rewriting, we can just copy the 368 | // remaining mappings over and jump to the final encoder 369 | // state. 370 | mappings += inputMappings; 371 | this.nextFileSourceNeedsComma = !(endsWith(inputMappings, ',') || endsWith(inputMappings, ';')); 372 | inputMappings = ''; 373 | this.linesMapped = initialMappedLines + cacheHint.lines; 374 | this.encoder = cacheHint.encoder; 375 | } 376 | 377 | if ((match = continuation.exec(inputMappings))) { 378 | // This is a significant optimization, especially when we're 379 | // doing simple line-for-line concatenations. 380 | lines = match[2].length / 5; 381 | this.encoder.adjustLine(lines); 382 | this.encoder.resetColumn(); 383 | decoder.adjustLine(lines); 384 | decoder.resetColumn(); 385 | this.linesMapped += lines + match[1].replace(/,/g, '').length; 386 | mappings += match[0]; 387 | this.nextFileSourceNeedsComma = !(endsWith(match[0], ',') || endsWith(match[0], ';')); 388 | inputMappings = inputMappings.slice(match[0].length); 389 | } 390 | } 391 | } 392 | 393 | // ensure we always reset the column. This is to ensure we remain resilient 394 | // to invalid input. 395 | this.encoder.resetColumn(); 396 | this.content.mappings = mappings; 397 | } 398 | 399 | writeConcatStatsSync(outputPath, content) { 400 | fs.mkdirpSync(path.dirname(outputPath)); 401 | fs.writeFileSync(outputPath, JSON.stringify(content, null, 2)); 402 | } 403 | 404 | end(cb, thisArg) { 405 | let stream = this.stream; 406 | let sourceMap = this; 407 | 408 | return new Promise(function(resolve, reject) { 409 | stream.on('error', function(error) { 410 | stream.on('close', function() { 411 | reject(error); 412 | }); 413 | 414 | stream.end(); 415 | }); 416 | 417 | let error, success; 418 | 419 | try { 420 | if (cb) { 421 | cb.call(thisArg, sourceMap); 422 | } 423 | 424 | if (process.env.CONCAT_STATS) { 425 | let concatStatsPath = process.env.CONCAT_STATS_PATH || `${process.cwd()}/concat-stats-for`; 426 | let outputPath = path.join(concatStatsPath, `${sourceMap.id}-${path.basename(sourceMap.outputFile)}.json`); 427 | 428 | sourceMap.writeConcatStatsSync( 429 | outputPath, 430 | { 431 | outputFile: sourceMap.outputFile, 432 | sizes: sourceMap._sizes 433 | } 434 | ); 435 | } 436 | 437 | let sourceMappingUrl = sourceMap.mapURL; 438 | 439 | if (sourceMap.mapStyle === 'data') { 440 | const dataUrlPrelude = 'data:application/json;charset=utf-8;base64,'; 441 | const base64Content = Buffer.from(JSON.stringify(sourceMap.content)).toString('base64'); 442 | sourceMappingUrl = `${dataUrlPrelude}${base64Content}`; 443 | } 444 | 445 | switch (sourceMap.mapCommentType) { 446 | case 'line': 447 | stream.write('//# sourceMappingURL=' + sourceMappingUrl + '\n'); 448 | break; 449 | case 'none': 450 | break; 451 | default: 452 | stream.write('/*# sourceMappingURL=' + sourceMappingUrl + ' */\n'); 453 | break; 454 | } 455 | 456 | if (sourceMap.mapFile) { 457 | fs.mkdirpSync(path.dirname(sourceMap.mapFile)); 458 | fs.writeFileSync(sourceMap.mapFile, JSON.stringify(sourceMap.content)); 459 | } 460 | success = true; 461 | } catch(e) { 462 | success = false; 463 | error = e; 464 | } finally { 465 | if (sourceMap.outputFile) { 466 | stream.on('close', function() { 467 | if (success) { 468 | resolve(); 469 | } else { 470 | reject(error); 471 | } 472 | }); 473 | stream.end(); 474 | } else { 475 | resolve({ 476 | code: stream.toString(), 477 | map: sourceMap.content 478 | }); 479 | } 480 | } 481 | }); 482 | } 483 | 484 | _warn(msg) { 485 | console.warn(chalk.yellow(msg)); 486 | } 487 | } 488 | 489 | module.exports = SourceMap; 490 | 491 | function countNewLines(src) { 492 | let newlinePattern = /(\r?\n)/g; 493 | let count = 0; 494 | while (newlinePattern.exec(src)) { 495 | count++; 496 | } 497 | return count; 498 | } 499 | 500 | 501 | function hash(string) { 502 | return crypto.createHash('md5').update(string).digest('hex'); 503 | } 504 | 505 | function ensurePosix(string) { 506 | if (EOL !== '\n') { 507 | string = string.split(EOL).join('\n'); 508 | } 509 | return string; 510 | } 511 | -------------------------------------------------------------------------------- /lib/vlq.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const vlq = require('@jacobq/source-map/lib/base64-vlq'); 4 | 5 | module.exports = vlq; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fast-sourcemap-concat", 3 | "version": "2.1.0", 4 | "description": "Concatenate files while generating or propagating sourcemaps.", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "test": "mocha --inline-diffs", 8 | "test:debug": "mocha --no-timeouts --inspect" 9 | }, 10 | "files": [ 11 | "lib" 12 | ], 13 | "repository": "https://github.com/ef4/fast-sourcemap-concat", 14 | "author": "Edward Faulkner ", 15 | "license": "MIT", 16 | "dependencies": { 17 | "chalk": "^2.0.0", 18 | "fs-extra": "^5.0.0", 19 | "heimdalljs-logger": "^0.1.9", 20 | "memory-streams": "^0.1.3", 21 | "mkdirp": "^0.5.0", 22 | "@jacobq/source-map": "^1.1.0", 23 | "source-map-url": "^0.3.0" 24 | }, 25 | "devDependencies": { 26 | "chai": "^4.1.2", 27 | "chai-files": "^1.4.0", 28 | "fs-merger": "^3.0.2", 29 | "mocha": "^5.2.0", 30 | "mocha-eslint": "^4.1.0", 31 | "rimraf": "^2.6.2", 32 | "sinon": "^1.12.2" 33 | }, 34 | "engines": { 35 | "node": "10.* || >= 12.*" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | tmp 2 | actual -------------------------------------------------------------------------------- /test/actual/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ef4/fast-sourcemap-concat/dba9de6fc8785acfeab4a68285c86d833c6e1cc1/test/actual/.gitkeep -------------------------------------------------------------------------------- /test/eslint-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const eslint = require('mocha-eslint'); 4 | 5 | let paths = [ 6 | 'lib', 7 | 'test/**/*.js', 8 | '!test/actual/**/*.js', 9 | '!test/expected/**/*.js', 10 | '!test/fixtures/**/*.js', 11 | ]; 12 | 13 | eslint(paths); 14 | -------------------------------------------------------------------------------- /test/expected/coffee-example.js: -------------------------------------------------------------------------------- 1 | exports = {}; 2 | // Generated by CoffeeScript 1.8.0 3 | (function() { 4 | var BALANCED_PAIRS, CALL_CLOSERS, EXPRESSION_CLOSE, EXPRESSION_END, EXPRESSION_START, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, IMPLICIT_UNSPACED_CALL, INVERSES, LINEBREAKS, SINGLE_CLOSERS, SINGLE_LINERS, generate, left, rite, _i, _len, _ref, 5 | __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, 6 | __slice = [].slice; 7 | 8 | generate = function(tag, value, origin) { 9 | var tok; 10 | tok = [tag, value]; 11 | tok.generated = true; 12 | if (origin) { 13 | tok.origin = origin; 14 | } 15 | return tok; 16 | }; 17 | 18 | exports.Rewriter = (function() { 19 | function Rewriter() {} 20 | 21 | Rewriter.prototype.rewrite = function(tokens) { 22 | this.tokens = tokens; 23 | this.removeLeadingNewlines(); 24 | this.closeOpenCalls(); 25 | this.closeOpenIndexes(); 26 | this.normalizeLines(); 27 | this.tagPostfixConditionals(); 28 | this.addImplicitBracesAndParens(); 29 | this.addLocationDataToGeneratedTokens(); 30 | return this.tokens; 31 | }; 32 | 33 | Rewriter.prototype.scanTokens = function(block) { 34 | var i, token, tokens; 35 | tokens = this.tokens; 36 | i = 0; 37 | while (token = tokens[i]) { 38 | i += block.call(this, token, i, tokens); 39 | } 40 | return true; 41 | }; 42 | 43 | Rewriter.prototype.detectEnd = function(i, condition, action) { 44 | var levels, token, tokens, _ref, _ref1; 45 | tokens = this.tokens; 46 | levels = 0; 47 | while (token = tokens[i]) { 48 | if (levels === 0 && condition.call(this, token, i)) { 49 | return action.call(this, token, i); 50 | } 51 | if (!token || levels < 0) { 52 | return action.call(this, token, i - 1); 53 | } 54 | if (_ref = token[0], __indexOf.call(EXPRESSION_START, _ref) >= 0) { 55 | levels += 1; 56 | } else if (_ref1 = token[0], __indexOf.call(EXPRESSION_END, _ref1) >= 0) { 57 | levels -= 1; 58 | } 59 | i += 1; 60 | } 61 | return i - 1; 62 | }; 63 | 64 | Rewriter.prototype.removeLeadingNewlines = function() { 65 | var i, tag, _i, _len, _ref; 66 | _ref = this.tokens; 67 | for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { 68 | tag = _ref[i][0]; 69 | if (tag !== 'TERMINATOR') { 70 | break; 71 | } 72 | } 73 | if (i) { 74 | return this.tokens.splice(0, i); 75 | } 76 | }; 77 | 78 | Rewriter.prototype.closeOpenCalls = function() { 79 | var action, condition; 80 | condition = function(token, i) { 81 | var _ref; 82 | return ((_ref = token[0]) === ')' || _ref === 'CALL_END') || token[0] === 'OUTDENT' && this.tag(i - 1) === ')'; 83 | }; 84 | action = function(token, i) { 85 | return this.tokens[token[0] === 'OUTDENT' ? i - 1 : i][0] = 'CALL_END'; 86 | }; 87 | return this.scanTokens(function(token, i) { 88 | if (token[0] === 'CALL_START') { 89 | this.detectEnd(i + 1, condition, action); 90 | } 91 | return 1; 92 | }); 93 | }; 94 | 95 | Rewriter.prototype.closeOpenIndexes = function() { 96 | var action, condition; 97 | condition = function(token, i) { 98 | var _ref; 99 | return (_ref = token[0]) === ']' || _ref === 'INDEX_END'; 100 | }; 101 | action = function(token, i) { 102 | return token[0] = 'INDEX_END'; 103 | }; 104 | return this.scanTokens(function(token, i) { 105 | if (token[0] === 'INDEX_START') { 106 | this.detectEnd(i + 1, condition, action); 107 | } 108 | return 1; 109 | }); 110 | }; 111 | 112 | Rewriter.prototype.matchTags = function() { 113 | var fuzz, i, j, pattern, _i, _ref, _ref1; 114 | i = arguments[0], pattern = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 115 | fuzz = 0; 116 | for (j = _i = 0, _ref = pattern.length; 0 <= _ref ? _i < _ref : _i > _ref; j = 0 <= _ref ? ++_i : --_i) { 117 | while (this.tag(i + j + fuzz) === 'HERECOMMENT') { 118 | fuzz += 2; 119 | } 120 | if (pattern[j] == null) { 121 | continue; 122 | } 123 | if (typeof pattern[j] === 'string') { 124 | pattern[j] = [pattern[j]]; 125 | } 126 | if (_ref1 = this.tag(i + j + fuzz), __indexOf.call(pattern[j], _ref1) < 0) { 127 | return false; 128 | } 129 | } 130 | return true; 131 | }; 132 | 133 | Rewriter.prototype.looksObjectish = function(j) { 134 | return this.matchTags(j, '@', null, ':') || this.matchTags(j, null, ':'); 135 | }; 136 | 137 | Rewriter.prototype.findTagsBackwards = function(i, tags) { 138 | var backStack, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; 139 | backStack = []; 140 | while (i >= 0 && (backStack.length || (_ref2 = this.tag(i), __indexOf.call(tags, _ref2) < 0) && ((_ref3 = this.tag(i), __indexOf.call(EXPRESSION_START, _ref3) < 0) || this.tokens[i].generated) && (_ref4 = this.tag(i), __indexOf.call(LINEBREAKS, _ref4) < 0))) { 141 | if (_ref = this.tag(i), __indexOf.call(EXPRESSION_END, _ref) >= 0) { 142 | backStack.push(this.tag(i)); 143 | } 144 | if ((_ref1 = this.tag(i), __indexOf.call(EXPRESSION_START, _ref1) >= 0) && backStack.length) { 145 | backStack.pop(); 146 | } 147 | i -= 1; 148 | } 149 | return _ref5 = this.tag(i), __indexOf.call(tags, _ref5) >= 0; 150 | }; 151 | 152 | Rewriter.prototype.addImplicitBracesAndParens = function() { 153 | var stack; 154 | stack = []; 155 | return this.scanTokens(function(token, i, tokens) { 156 | var endImplicitCall, endImplicitObject, forward, inImplicit, inImplicitCall, inImplicitControl, inImplicitObject, newLine, nextTag, offset, prevTag, prevToken, s, sameLine, stackIdx, stackTag, stackTop, startIdx, startImplicitCall, startImplicitObject, startsLine, tag, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; 157 | tag = token[0]; 158 | prevTag = (prevToken = i > 0 ? tokens[i - 1] : [])[0]; 159 | nextTag = (i < tokens.length - 1 ? tokens[i + 1] : [])[0]; 160 | stackTop = function() { 161 | return stack[stack.length - 1]; 162 | }; 163 | startIdx = i; 164 | forward = function(n) { 165 | return i - startIdx + n; 166 | }; 167 | inImplicit = function() { 168 | var _ref, _ref1; 169 | return (_ref = stackTop()) != null ? (_ref1 = _ref[2]) != null ? _ref1.ours : void 0 : void 0; 170 | }; 171 | inImplicitCall = function() { 172 | var _ref; 173 | return inImplicit() && ((_ref = stackTop()) != null ? _ref[0] : void 0) === '('; 174 | }; 175 | inImplicitObject = function() { 176 | var _ref; 177 | return inImplicit() && ((_ref = stackTop()) != null ? _ref[0] : void 0) === '{'; 178 | }; 179 | inImplicitControl = function() { 180 | var _ref; 181 | return inImplicit && ((_ref = stackTop()) != null ? _ref[0] : void 0) === 'CONTROL'; 182 | }; 183 | startImplicitCall = function(j) { 184 | var idx; 185 | idx = j != null ? j : i; 186 | stack.push([ 187 | '(', idx, { 188 | ours: true 189 | } 190 | ]); 191 | tokens.splice(idx, 0, generate('CALL_START', '(')); 192 | if (j == null) { 193 | return i += 1; 194 | } 195 | }; 196 | endImplicitCall = function() { 197 | stack.pop(); 198 | tokens.splice(i, 0, generate('CALL_END', ')')); 199 | return i += 1; 200 | }; 201 | startImplicitObject = function(j, startsLine) { 202 | var idx; 203 | if (startsLine == null) { 204 | startsLine = true; 205 | } 206 | idx = j != null ? j : i; 207 | stack.push([ 208 | '{', idx, { 209 | sameLine: true, 210 | startsLine: startsLine, 211 | ours: true 212 | } 213 | ]); 214 | tokens.splice(idx, 0, generate('{', generate(new String('{')), token)); 215 | if (j == null) { 216 | return i += 1; 217 | } 218 | }; 219 | endImplicitObject = function(j) { 220 | j = j != null ? j : i; 221 | stack.pop(); 222 | tokens.splice(j, 0, generate('}', '}', token)); 223 | return i += 1; 224 | }; 225 | if (inImplicitCall() && (tag === 'IF' || tag === 'TRY' || tag === 'FINALLY' || tag === 'CATCH' || tag === 'CLASS' || tag === 'SWITCH')) { 226 | stack.push([ 227 | 'CONTROL', i, { 228 | ours: true 229 | } 230 | ]); 231 | return forward(1); 232 | } 233 | if (tag === 'INDENT' && inImplicit()) { 234 | if (prevTag !== '=>' && prevTag !== '->' && prevTag !== '[' && prevTag !== '(' && prevTag !== ',' && prevTag !== '{' && prevTag !== 'TRY' && prevTag !== 'ELSE' && prevTag !== '=') { 235 | while (inImplicitCall()) { 236 | endImplicitCall(); 237 | } 238 | } 239 | if (inImplicitControl()) { 240 | stack.pop(); 241 | } 242 | stack.push([tag, i]); 243 | return forward(1); 244 | } 245 | if (__indexOf.call(EXPRESSION_START, tag) >= 0) { 246 | stack.push([tag, i]); 247 | return forward(1); 248 | } 249 | if (__indexOf.call(EXPRESSION_END, tag) >= 0) { 250 | while (inImplicit()) { 251 | if (inImplicitCall()) { 252 | endImplicitCall(); 253 | } else if (inImplicitObject()) { 254 | endImplicitObject(); 255 | } else { 256 | stack.pop(); 257 | } 258 | } 259 | stack.pop(); 260 | } 261 | if ((__indexOf.call(IMPLICIT_FUNC, tag) >= 0 && token.spaced && !token.stringEnd || tag === '?' && i > 0 && !tokens[i - 1].spaced) && (__indexOf.call(IMPLICIT_CALL, nextTag) >= 0 || __indexOf.call(IMPLICIT_UNSPACED_CALL, nextTag) >= 0 && !((_ref = tokens[i + 1]) != null ? _ref.spaced : void 0) && !((_ref1 = tokens[i + 1]) != null ? _ref1.newLine : void 0))) { 262 | if (tag === '?') { 263 | tag = token[0] = 'FUNC_EXIST'; 264 | } 265 | startImplicitCall(i + 1); 266 | return forward(2); 267 | } 268 | if (__indexOf.call(IMPLICIT_FUNC, tag) >= 0 && this.matchTags(i + 1, 'INDENT', null, ':') && !this.findTagsBackwards(i, ['CLASS', 'EXTENDS', 'IF', 'CATCH', 'SWITCH', 'LEADING_WHEN', 'FOR', 'WHILE', 'UNTIL'])) { 269 | startImplicitCall(i + 1); 270 | stack.push(['INDENT', i + 2]); 271 | return forward(3); 272 | } 273 | if (tag === ':') { 274 | if (this.tag(i - 2) === '@') { 275 | s = i - 2; 276 | } else { 277 | s = i - 1; 278 | } 279 | while (this.tag(s - 2) === 'HERECOMMENT') { 280 | s -= 2; 281 | } 282 | this.insideForDeclaration = nextTag === 'FOR'; 283 | startsLine = s === 0 || (_ref2 = this.tag(s - 1), __indexOf.call(LINEBREAKS, _ref2) >= 0) || tokens[s - 1].newLine; 284 | if (stackTop()) { 285 | _ref3 = stackTop(), stackTag = _ref3[0], stackIdx = _ref3[1]; 286 | if ((stackTag === '{' || stackTag === 'INDENT' && this.tag(stackIdx - 1) === '{') && (startsLine || this.tag(s - 1) === ',' || this.tag(s - 1) === '{')) { 287 | return forward(1); 288 | } 289 | } 290 | startImplicitObject(s, !!startsLine); 291 | return forward(2); 292 | } 293 | if (inImplicitObject() && __indexOf.call(LINEBREAKS, tag) >= 0) { 294 | stackTop()[2].sameLine = false; 295 | } 296 | newLine = prevTag === 'OUTDENT' || prevToken.newLine; 297 | if (__indexOf.call(IMPLICIT_END, tag) >= 0 || __indexOf.call(CALL_CLOSERS, tag) >= 0 && newLine) { 298 | while (inImplicit()) { 299 | _ref4 = stackTop(), stackTag = _ref4[0], stackIdx = _ref4[1], (_ref5 = _ref4[2], sameLine = _ref5.sameLine, startsLine = _ref5.startsLine); 300 | if (inImplicitCall() && prevTag !== ',') { 301 | endImplicitCall(); 302 | } else if (inImplicitObject() && !this.insideForDeclaration && sameLine && tag !== 'TERMINATOR' && prevTag !== ':' && endImplicitObject()) { 303 | 304 | } else if (inImplicitObject() && tag === 'TERMINATOR' && prevTag !== ',' && !(startsLine && this.looksObjectish(i + 1))) { 305 | endImplicitObject(); 306 | } else { 307 | break; 308 | } 309 | } 310 | } 311 | if (tag === ',' && !this.looksObjectish(i + 1) && inImplicitObject() && !this.insideForDeclaration && (nextTag !== 'TERMINATOR' || !this.looksObjectish(i + 2))) { 312 | offset = nextTag === 'OUTDENT' ? 1 : 0; 313 | while (inImplicitObject()) { 314 | endImplicitObject(i + offset); 315 | } 316 | } 317 | return forward(1); 318 | }); 319 | }; 320 | 321 | Rewriter.prototype.addLocationDataToGeneratedTokens = function() { 322 | return this.scanTokens(function(token, i, tokens) { 323 | var column, line, nextLocation, prevLocation, _ref, _ref1; 324 | if (token[2]) { 325 | return 1; 326 | } 327 | if (!(token.generated || token.explicit)) { 328 | return 1; 329 | } 330 | if (token[0] === '{' && (nextLocation = (_ref = tokens[i + 1]) != null ? _ref[2] : void 0)) { 331 | line = nextLocation.first_line, column = nextLocation.first_column; 332 | } else if (prevLocation = (_ref1 = tokens[i - 1]) != null ? _ref1[2] : void 0) { 333 | line = prevLocation.last_line, column = prevLocation.last_column; 334 | } else { 335 | line = column = 0; 336 | } 337 | token[2] = { 338 | first_line: line, 339 | first_column: column, 340 | last_line: line, 341 | last_column: column 342 | }; 343 | return 1; 344 | }); 345 | }; 346 | 347 | Rewriter.prototype.normalizeLines = function() { 348 | var action, condition, indent, outdent, starter; 349 | starter = indent = outdent = null; 350 | condition = function(token, i) { 351 | var _ref, _ref1, _ref2, _ref3; 352 | return token[1] !== ';' && (_ref = token[0], __indexOf.call(SINGLE_CLOSERS, _ref) >= 0) && !(token[0] === 'TERMINATOR' && (_ref1 = this.tag(i + 1), __indexOf.call(EXPRESSION_CLOSE, _ref1) >= 0)) && !(token[0] === 'ELSE' && starter !== 'THEN') && !(((_ref2 = token[0]) === 'CATCH' || _ref2 === 'FINALLY') && (starter === '->' || starter === '=>')) || (_ref3 = token[0], __indexOf.call(CALL_CLOSERS, _ref3) >= 0) && this.tokens[i - 1].newLine; 353 | }; 354 | action = function(token, i) { 355 | return this.tokens.splice((this.tag(i - 1) === ',' ? i - 1 : i), 0, outdent); 356 | }; 357 | return this.scanTokens(function(token, i, tokens) { 358 | var j, tag, _i, _ref, _ref1, _ref2; 359 | tag = token[0]; 360 | if (tag === 'TERMINATOR') { 361 | if (this.tag(i + 1) === 'ELSE' && this.tag(i - 1) !== 'OUTDENT') { 362 | tokens.splice.apply(tokens, [i, 1].concat(__slice.call(this.indentation()))); 363 | return 1; 364 | } 365 | if (_ref = this.tag(i + 1), __indexOf.call(EXPRESSION_CLOSE, _ref) >= 0) { 366 | tokens.splice(i, 1); 367 | return 0; 368 | } 369 | } 370 | if (tag === 'CATCH') { 371 | for (j = _i = 1; _i <= 2; j = ++_i) { 372 | if (!((_ref1 = this.tag(i + j)) === 'OUTDENT' || _ref1 === 'TERMINATOR' || _ref1 === 'FINALLY')) { 373 | continue; 374 | } 375 | tokens.splice.apply(tokens, [i + j, 0].concat(__slice.call(this.indentation()))); 376 | return 2 + j; 377 | } 378 | } 379 | if (__indexOf.call(SINGLE_LINERS, tag) >= 0 && this.tag(i + 1) !== 'INDENT' && !(tag === 'ELSE' && this.tag(i + 1) === 'IF')) { 380 | starter = tag; 381 | _ref2 = this.indentation(tokens[i]), indent = _ref2[0], outdent = _ref2[1]; 382 | if (starter === 'THEN') { 383 | indent.fromThen = true; 384 | } 385 | tokens.splice(i + 1, 0, indent); 386 | this.detectEnd(i + 2, condition, action); 387 | if (tag === 'THEN') { 388 | tokens.splice(i, 1); 389 | } 390 | return 1; 391 | } 392 | return 1; 393 | }); 394 | }; 395 | 396 | Rewriter.prototype.tagPostfixConditionals = function() { 397 | var action, condition, original; 398 | original = null; 399 | condition = function(token, i) { 400 | var prevTag, tag; 401 | tag = token[0]; 402 | prevTag = this.tokens[i - 1][0]; 403 | return tag === 'TERMINATOR' || (tag === 'INDENT' && __indexOf.call(SINGLE_LINERS, prevTag) < 0); 404 | }; 405 | action = function(token, i) { 406 | if (token[0] !== 'INDENT' || (token.generated && !token.fromThen)) { 407 | return original[0] = 'POST_' + original[0]; 408 | } 409 | }; 410 | return this.scanTokens(function(token, i) { 411 | if (token[0] !== 'IF') { 412 | return 1; 413 | } 414 | original = token; 415 | this.detectEnd(i + 1, condition, action); 416 | return 1; 417 | }); 418 | }; 419 | 420 | Rewriter.prototype.indentation = function(origin) { 421 | var indent, outdent; 422 | indent = ['INDENT', 2]; 423 | outdent = ['OUTDENT', 2]; 424 | if (origin) { 425 | indent.generated = outdent.generated = true; 426 | indent.origin = outdent.origin = origin; 427 | } else { 428 | indent.explicit = outdent.explicit = true; 429 | } 430 | return [indent, outdent]; 431 | }; 432 | 433 | Rewriter.prototype.generate = generate; 434 | 435 | Rewriter.prototype.tag = function(i) { 436 | var _ref; 437 | return (_ref = this.tokens[i]) != null ? _ref[0] : void 0; 438 | }; 439 | 440 | return Rewriter; 441 | 442 | })(); 443 | 444 | BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], ['INDENT', 'OUTDENT'], ['CALL_START', 'CALL_END'], ['PARAM_START', 'PARAM_END'], ['INDEX_START', 'INDEX_END']]; 445 | 446 | exports.INVERSES = INVERSES = {}; 447 | 448 | EXPRESSION_START = []; 449 | 450 | EXPRESSION_END = []; 451 | 452 | for (_i = 0, _len = BALANCED_PAIRS.length; _i < _len; _i++) { 453 | _ref = BALANCED_PAIRS[_i], left = _ref[0], rite = _ref[1]; 454 | EXPRESSION_START.push(INVERSES[rite] = left); 455 | EXPRESSION_END.push(INVERSES[left] = rite); 456 | } 457 | 458 | EXPRESSION_CLOSE = ['CATCH', 'THEN', 'ELSE', 'FINALLY'].concat(EXPRESSION_END); 459 | 460 | IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS']; 461 | 462 | IMPLICIT_CALL = ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL', 'UNDEFINED', 'UNARY', 'YIELD', 'UNARY_MATH', 'SUPER', 'THROW', '@', '->', '=>', '[', '(', '{', '--', '++']; 463 | 464 | IMPLICIT_UNSPACED_CALL = ['+', '-']; 465 | 466 | IMPLICIT_END = ['POST_IF', 'FOR', 'WHILE', 'UNTIL', 'WHEN', 'BY', 'LOOP', 'TERMINATOR']; 467 | 468 | SINGLE_LINERS = ['ELSE', '->', '=>', 'TRY', 'FINALLY', 'THEN']; 469 | 470 | SINGLE_CLOSERS = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN']; 471 | 472 | LINEBREAKS = ['TERMINATOR', 'INDENT', 'OUTDENT']; 473 | 474 | CALL_CLOSERS = ['.', '?.', '::', '?::']; 475 | 476 | }).call(this); 477 | 478 | /* My First Separator */function third(){ 479 | throw new Error("oh no"); 480 | } 481 | //# sourceMappingURL=coffee-example.map 482 | -------------------------------------------------------------------------------- /test/expected/custom.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["fixtures/inner/first.js"],"sourcesContent":["function meaningOfLife() {\n throw new Error(42);\n}\n\nfunction boom() {\n throw new Error('boom');\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;","file":"mapdird.js"} -------------------------------------------------------------------------------- /test/expected/custom2.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["fixtures/inner/first.js"],"sourcesContent":["function meaningOfLife() {\n throw new Error(42);\n}\n\nfunction boom() {\n throw new Error('boom');\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;","file":"mapdird2.js"} -------------------------------------------------------------------------------- /test/expected/external-content.js: -------------------------------------------------------------------------------- 1 | function third(){ 2 | throw new Error("oh no"); 3 | } 4 | /* My First Separator */function meaningOfLife() { 5 | throw new Error(42); 6 | } 7 | 8 | function boom() { 9 | throw new Error('boom'); 10 | } 11 | 12 | function somethingElse() { 13 | throw new Error("somethign else"); 14 | } 15 | /* My Second */ 16 | // Hello world 17 | 18 | function fourth(){ 19 | throw new Error('fourth'); 20 | } 21 | //# sourceMappingURL=external-content.map 22 | -------------------------------------------------------------------------------- /test/expected/external-content.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["other/third.js","a.js","inside/b.js","other/fourth.js"],"sourcesContent":["function third(){\n throw new Error(\"oh no\");\n}\n","function meaningOfLife() {\n throw new Error(42);\n}\n\nfunction boom() {\n throw new Error('boom');\n}\n","function somethingElse() {\n throw new Error(\"somethign else\");\n}\n","\n// Hello world\n\nfunction fourth(){\n throw new Error('fourth');\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;wBCFA;AACA;AACA;AACA;AACA;AACA;AACA;;ACNA;AACA;AACA;eCFA;AACA;AACA;AACA;AACA;AACA;","file":"external-content.js"} -------------------------------------------------------------------------------- /test/expected/final.js: -------------------------------------------------------------------------------- 1 | function meaningOfLife() { 2 | throw new Error(42); 3 | } 4 | 5 | function boom() { 6 | throw new Error('boom'); 7 | } 8 | 'x';function somethingElse() { 9 | throw new Error("somethign else"); 10 | } 11 | function third(){ 12 | throw new Error("oh no"); 13 | } 14 | 15 | // Hello world 16 | 17 | function fourth(){ 18 | throw new Error('fourth'); 19 | } 20 | //# sourceMappingURL=final.map 21 | -------------------------------------------------------------------------------- /test/expected/final.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["fixtures/inner/first.js","fixtures/inner/second.js","fixtures/other/third.js","fixtures/other/fourth.js"],"sourcesContent":["function meaningOfLife() {\n throw new Error(42);\n}\n\nfunction boom() {\n throw new Error('boom');\n}\n","function somethingElse() {\n throw new Error(\"somethign else\");\n}\n","function third(){\n throw new Error(\"oh no\");\n}\n","\n// Hello world\n\nfunction fourth(){\n throw new Error('fourth');\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;ICNA;AACA;AACA;ACFA;AACA;AACA;ACFA;AACA;AACA;AACA;AACA;AACA;","file":"final.js"} -------------------------------------------------------------------------------- /test/expected/from-inline.js: -------------------------------------------------------------------------------- 1 | function third(){ 2 | throw new Error("oh no"); 3 | } 4 | /* My First Separator */function meaningOfLife() { 5 | throw new Error(42); 6 | } 7 | 8 | function boom() { 9 | throw new Error('boom'); 10 | } 11 | 12 | function somethingElse() { 13 | throw new Error("somethign else"); 14 | } 15 | /* My Second */ 16 | // Hello world 17 | 18 | function fourth(){ 19 | throw new Error('fourth'); 20 | } 21 | //# sourceMappingURL=from-inline.map 22 | -------------------------------------------------------------------------------- /test/expected/from-inline.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["fixtures/other/third.js","inner/first.js","inner/second.js","fixtures/other/fourth.js"],"sourcesContent":["function third(){\n throw new Error(\"oh no\");\n}\n","function meaningOfLife() {\n throw new Error(42);\n}\n\nfunction boom() {\n throw new Error('boom');\n}\n","function somethingElse() {\n throw new Error(\"somethign else\");\n}\n","\n// Hello world\n\nfunction fourth(){\n throw new Error('fourth');\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;wBCFA;AACA;AACA;AACA;AACA;AACA;AACA;;ACNA;AACA;AACA;eCFA;AACA;AACA;AACA;AACA;AACA;","file":"from-inline.js"} -------------------------------------------------------------------------------- /test/expected/from-string.js: -------------------------------------------------------------------------------- 1 | function third(){ 2 | throw new Error("oh no"); 3 | } 4 | /* My First Separator */function meaningOfLife() { 5 | throw new Error(42); 6 | } 7 | 8 | function boom() { 9 | throw new Error('boom'); 10 | } 11 | 12 | function somethingElse() { 13 | throw new Error("somethign else"); 14 | } 15 | /* My Second */ 16 | // Hello world 17 | 18 | function fourth(){ 19 | throw new Error('fourth'); 20 | } 21 | //# sourceMappingURL=from-string.map 22 | -------------------------------------------------------------------------------- /test/expected/from-string.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["fixtures/other/third.js","inner/first.js","inner/second.js","fixtures/other/fourth.js"],"sourcesContent":["function third(){\n throw new Error(\"oh no\");\n}\n","function meaningOfLife() {\n throw new Error(42);\n}\n\nfunction boom() {\n throw new Error('boom');\n}\n","function somethingElse() {\n throw new Error(\"somethign else\");\n}\n","\n// Hello world\n\nfunction fourth(){\n throw new Error('fourth');\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;wBCFA;AACA;AACA;AACA;AACA;AACA;AACA;;ACNA;AACA;AACA;eCFA;AACA;AACA;AACA;AACA;AACA;","file":"from-string.js"} -------------------------------------------------------------------------------- /test/expected/hello-world-output-1.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["fixtures/inner/first.js","hello-world.ts","fixtures/inner/second.js"],"sourcesContent":["function meaningOfLife() {\n throw new Error(42);\n}\n\nfunction boom() {\n throw new Error('boom');\n}\n","export function helloWorld(string: string) {\n\n console.log(string);\n}\n","function somethingElse() {\n throw new Error(\"somethign else\");\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;ACNA,2BAA2B,MAAc;IAEvC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACtB,CAAC;ACHD;AACA;AACA;","file":"hello-world-output.js"} -------------------------------------------------------------------------------- /test/expected/hello-world-output-2.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["fixtures/inner/first.js","hello-world.ts","fixtures/inner/second.js"],"sourcesContent":["function meaningOfLife() {\n throw new Error(42);\n}\n\nfunction boom() {\n throw new Error('boom');\n}\n","export function helloWorld(string: string) {\n console.log(string);\n}\n","function somethingElse() {\n throw new Error(\"somethign else\");\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;ACNA,2BAA2B,MAAc;IACvC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACtB,CAAC;ACFD;AACA;AACA;","file":"hello-world-output.js"} -------------------------------------------------------------------------------- /test/expected/hello-world-output.js: -------------------------------------------------------------------------------- 1 | function meaningOfLife() { 2 | throw new Error(42); 3 | } 4 | 5 | function boom() { 6 | throw new Error('boom'); 7 | } 8 | export function helloWorld(string) { 9 | console.log(string); 10 | } 11 | function somethingElse() { 12 | throw new Error("somethign else"); 13 | } 14 | //# sourceMappingURL=hello-world-output.map 15 | -------------------------------------------------------------------------------- /test/expected/iife-wrapping.js: -------------------------------------------------------------------------------- 1 | 2 | // Hello world 3 | 4 | function fourth(){ 5 | throw new Error('fourth'); 6 | } 7 | 8 | (function() { 9 | function third(){ 10 | throw new Error("oh no"); 11 | } 12 | 13 | })();//# sourceMappingURL=iife-wrapping.map 14 | -------------------------------------------------------------------------------- /test/expected/iife-wrapping.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["fixtures/other/fourth.js","fixtures/iife-wrapping/iife-start","fixtures/other/third.js","fixtures/iife-wrapping/iife-end"],"sourcesContent":["\n// Hello world\n\nfunction fourth(){\n throw new Error('fourth');\n}\n","(function() {","function third(){\n throw new Error(\"oh no\");\n}\n","})();"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;;ACLA;ACAA;AACA;AACA;;ACFA","file":"iife-wrapping.js"} -------------------------------------------------------------------------------- /test/expected/mapdird.js: -------------------------------------------------------------------------------- 1 | function meaningOfLife() { 2 | throw new Error(42); 3 | } 4 | 5 | function boom() { 6 | throw new Error('boom'); 7 | } 8 | //# sourceMappingURL=/maps/custom.map 9 | -------------------------------------------------------------------------------- /test/expected/mapdird2.js: -------------------------------------------------------------------------------- 1 | function meaningOfLife() { 2 | throw new Error(42); 3 | } 4 | 5 | function boom() { 6 | throw new Error('boom'); 7 | } 8 | //# sourceMappingURL=/maps/custom2.map 9 | -------------------------------------------------------------------------------- /test/expected/no-sources-content-out.js: -------------------------------------------------------------------------------- 1 | 2 | // Hello world 3 | 4 | function fourth(){ 5 | throw new Error('fourth'); 6 | } 7 | function fooFromB() { 8 | console.log('i am b'); 9 | } 10 | function third(){ 11 | throw new Error("oh no"); 12 | } 13 | //# sourceMappingURL=no-sources-content-out.map 14 | -------------------------------------------------------------------------------- /test/expected/no-sources-content-out.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["fixtures/other/fourth.js","b.ts","fixtures/other/third.js"],"sourcesContent":["\n// Hello world\n\nfunction fourth(){\n throw new Error('fourth');\n}\n","function fooFromB() {\n\tconsole.log('i am b');\n}\n","function third(){\n throw new Error(\"oh no\");\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;ACLA;IACC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AACvB,CAAC;ACFD;AACA;AACA;","file":"no-sources-content-out.js"} -------------------------------------------------------------------------------- /test/expected/sprintf-multi.js: -------------------------------------------------------------------------------- 1 | /*! sprintf-js | Alexandru Marasteanu (http://alexei.ro/) | BSD-3-Clause */ 2 | 3 | !function(a){function b(){var a=arguments[0],c=b.cache;return c[a]&&c.hasOwnProperty(a)||(c[a]=b.parse(a)),b.format.call(null,c[a],arguments)}function c(a){return Object.prototype.toString.call(a).slice(8,-1).toLowerCase()}function d(a,b){return Array(b+1).join(a)}var e={not_string:/[^s]/,number:/[diefg]/,json:/[j]/,not_json:/[^j]/,text:/^[^\x25]+/,modulo:/^\x25{2}/,placeholder:/^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijosuxX])/,key:/^([a-z_][a-z_\d]*)/i,key_access:/^\.([a-z_][a-z_\d]*)/i,index_access:/^\[(\d+)\]/,sign:/^[\+\-]/};b.format=function(a,f){var g,h,i,j,k,l,m,n=1,o=a.length,p="",q=[],r=!0,s="";for(h=0;o>h;h++)if(p=c(a[h]),"string"===p)q[q.length]=a[h];else if("array"===p){if(j=a[h],j[2])for(g=f[n],i=0;i=0),j[8]){case"b":g=g.toString(2);break;case"c":g=String.fromCharCode(g);break;case"d":case"i":g=parseInt(g,10);break;case"j":g=JSON.stringify(g,null,j[6]?parseInt(j[6]):0);break;case"e":g=j[7]?g.toExponential(j[7]):g.toExponential();break;case"f":g=j[7]?parseFloat(g).toFixed(j[7]):parseFloat(g);break;case"g":g=j[7]?parseFloat(g).toPrecision(j[7]):parseFloat(g);break;case"o":g=g.toString(8);break;case"s":g=(g=String(g))&&j[7]?g.substring(0,j[7]):g;break;case"u":g>>>=0;break;case"x":g=g.toString(16);break;case"X":g=g.toString(16).toUpperCase()}e.json.test(j[8])?q[q.length]=g:(!e.number.test(j[8])||r&&!j[3]?s="":(s=r?"+":"-",g=g.toString().replace(e.sign,"")),l=j[4]?"0"===j[4]?"0":j[4].charAt(1):" ",m=j[6]-(s+g).length,k=j[6]&&m>0?d(l,m):"",q[q.length]=j[5]?s+g+k:"0"===l?s+k+g:k+s+g)}return q.join("")},b.cache={},b.parse=function(a){for(var b=a,c=[],d=[],f=0;b;){if(null!==(c=e.text.exec(b)))d[d.length]=c[0];else if(null!==(c=e.modulo.exec(b)))d[d.length]="%";else{if(null===(c=e.placeholder.exec(b)))throw new SyntaxError("[sprintf] unexpected placeholder");if(c[2]){f|=1;var g=[],h=c[2],i=[];if(null===(i=e.key.exec(h)))throw new SyntaxError("[sprintf] failed to parse named argument key");for(g[g.length]=i[1];""!==(h=h.substring(i[0].length));)if(null!==(i=e.key_access.exec(h)))g[g.length]=i[1];else{if(null===(i=e.index_access.exec(h)))throw new SyntaxError("[sprintf] failed to parse named argument key");g[g.length]=i[1]}c[2]=g}else f|=2;if(3===f)throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported");d[d.length]=c}b=b.substring(c[0].length)}return d};var f=function(a,c,d){return d=(c||[]).slice(0),d.splice(0,0,a),b.apply(null,d)};"undefined"!=typeof exports?(exports.sprintf=b,exports.vsprintf=f):(a.sprintf=b,a.vsprintf=f,"function"==typeof define&&define.amd&&define(function(){return{sprintf:b,vsprintf:f}}))}("undefined"==typeof window?this:window); 4 | function meaningOfLife() { 5 | throw new Error(42); 6 | } 7 | 8 | function boom() { 9 | throw new Error('boom'); 10 | } 11 | //# sourceMappingURL=sprintf-multi.map 12 | -------------------------------------------------------------------------------- /test/expected/sprintf-multi.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["fixtures/sprintf/sprintf.min.js","fixtures/sprintf/first.js"],"sourcesContent":["/*! sprintf-js | Alexandru Marasteanu (http://alexei.ro/) | BSD-3-Clause */\n\n!function(a){function b(){var a=arguments[0],c=b.cache;return c[a]&&c.hasOwnProperty(a)||(c[a]=b.parse(a)),b.format.call(null,c[a],arguments)}function c(a){return Object.prototype.toString.call(a).slice(8,-1).toLowerCase()}function d(a,b){return Array(b+1).join(a)}var e={not_string:/[^s]/,number:/[diefg]/,json:/[j]/,not_json:/[^j]/,text:/^[^\\x25]+/,modulo:/^\\x25{2}/,placeholder:/^\\x25(?:([1-9]\\d*)\\$|\\(([^\\)]+)\\))?(\\+)?(0|'[^$])?(-)?(\\d+)?(?:\\.(\\d+))?([b-gijosuxX])/,key:/^([a-z_][a-z_\\d]*)/i,key_access:/^\\.([a-z_][a-z_\\d]*)/i,index_access:/^\\[(\\d+)\\]/,sign:/^[\\+\\-]/};b.format=function(a,f){var g,h,i,j,k,l,m,n=1,o=a.length,p=\"\",q=[],r=!0,s=\"\";for(h=0;o>h;h++)if(p=c(a[h]),\"string\"===p)q[q.length]=a[h];else if(\"array\"===p){if(j=a[h],j[2])for(g=f[n],i=0;i=0),j[8]){case\"b\":g=g.toString(2);break;case\"c\":g=String.fromCharCode(g);break;case\"d\":case\"i\":g=parseInt(g,10);break;case\"j\":g=JSON.stringify(g,null,j[6]?parseInt(j[6]):0);break;case\"e\":g=j[7]?g.toExponential(j[7]):g.toExponential();break;case\"f\":g=j[7]?parseFloat(g).toFixed(j[7]):parseFloat(g);break;case\"g\":g=j[7]?parseFloat(g).toPrecision(j[7]):parseFloat(g);break;case\"o\":g=g.toString(8);break;case\"s\":g=(g=String(g))&&j[7]?g.substring(0,j[7]):g;break;case\"u\":g>>>=0;break;case\"x\":g=g.toString(16);break;case\"X\":g=g.toString(16).toUpperCase()}e.json.test(j[8])?q[q.length]=g:(!e.number.test(j[8])||r&&!j[3]?s=\"\":(s=r?\"+\":\"-\",g=g.toString().replace(e.sign,\"\")),l=j[4]?\"0\"===j[4]?\"0\":j[4].charAt(1):\" \",m=j[6]-(s+g).length,k=j[6]&&m>0?d(l,m):\"\",q[q.length]=j[5]?s+g+k:\"0\"===l?s+k+g:k+s+g)}return q.join(\"\")},b.cache={},b.parse=function(a){for(var b=a,c=[],d=[],f=0;b;){if(null!==(c=e.text.exec(b)))d[d.length]=c[0];else if(null!==(c=e.modulo.exec(b)))d[d.length]=\"%\";else{if(null===(c=e.placeholder.exec(b)))throw new SyntaxError(\"[sprintf] unexpected placeholder\");if(c[2]){f|=1;var g=[],h=c[2],i=[];if(null===(i=e.key.exec(h)))throw new SyntaxError(\"[sprintf] failed to parse named argument key\");for(g[g.length]=i[1];\"\"!==(h=h.substring(i[0].length));)if(null!==(i=e.key_access.exec(h)))g[g.length]=i[1];else{if(null===(i=e.index_access.exec(h)))throw new SyntaxError(\"[sprintf] failed to parse named argument key\");g[g.length]=i[1]}c[2]=g}else f|=2;if(3===f)throw new Error(\"[sprintf] mixing positional and named placeholders is not (yet) supported\");d[d.length]=c}b=b.substring(c[0].length)}return d};var f=function(a,c,d){return d=(c||[]).slice(0),d.splice(0,0,a),b.apply(null,d)};\"undefined\"!=typeof exports?(exports.sprintf=b,exports.vsprintf=f):(a.sprintf=b,a.vsprintf=f,\"function\"==typeof define&&define.amd&&define(function(){return{sprintf:b,vsprintf:f}}))}(\"undefined\"==typeof window?this:window);\n","function meaningOfLife() {\n throw new Error(42);\n}\n\nfunction boom() {\n throw new Error('boom');\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;ACFA;AACA;AACA;AACA;AACA;AACA;AACA;","file":"sprintf-multi.js"} -------------------------------------------------------------------------------- /test/expected/too-few-sources-out.js: -------------------------------------------------------------------------------- 1 | 2 | // Hello world 3 | 4 | function fourth(){ 5 | throw new Error('fourth'); 6 | } 7 | function third(){ 8 | throw new Error("oh no"); 9 | } 10 | //# sourceMappingURL=too-few-sources-out.map 11 | -------------------------------------------------------------------------------- /test/expected/too-few-sources-out.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["fixtures/other/fourth.js","fixtures/emptyish/too-few-sources.js","fixtures/other/third.js"],"sourcesContent":["\n// Hello world\n\nfunction fourth(){\n throw new Error('fourth');\n}\n","","function third(){\n throw new Error(\"oh no\");\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;ACLA,ACAA;AACA;AACA;","file":"too-few-sources-out.js"} -------------------------------------------------------------------------------- /test/expected/too-many-sources-out.js: -------------------------------------------------------------------------------- 1 | 2 | // Hello world 3 | 4 | function fourth(){ 5 | throw new Error('fourth'); 6 | } 7 | function third(){ 8 | throw new Error("oh no"); 9 | } 10 | //# sourceMappingURL=too-many-sources-out.map 11 | -------------------------------------------------------------------------------- /test/expected/too-many-sources-out.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["fixtures/other/fourth.js","fixtures/emptyish/too-many-sources.js","fixtures/other/third.js"],"sourcesContent":["\n// Hello world\n\nfunction fourth(){\n throw new Error('fourth');\n}\n","","function third(){\n throw new Error(\"oh no\");\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;ACLA,ACAA;AACA;AACA;","file":"too-many-sources-out.js"} -------------------------------------------------------------------------------- /test/expected/with-broken-input-map.js: -------------------------------------------------------------------------------- 1 | function third(){ 2 | throw new Error("oh no"); 3 | } 4 | /* My First Separator */function meaningOfLife() { 5 | throw new Error(42); 6 | } 7 | 8 | function boom() { 9 | throw new Error('boom'); 10 | } 11 | 12 | function somethingElse() { 13 | throw new Error("somethign else"); 14 | } 15 | /* My Second */ 16 | // Hello world 17 | 18 | function fourth(){ 19 | throw new Error('fourth'); 20 | } 21 | //# sourceMappingURL=with-broken-input-map.map 22 | -------------------------------------------------------------------------------- /test/expected/with-broken-input-map.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["other/third.js","external-content/broken-link.js","other/fourth.js"],"sourcesContent":["function third(){\n throw new Error(\"oh no\");\n}\n","function meaningOfLife() {\n throw new Error(42);\n}\n\nfunction boom() {\n throw new Error('boom');\n}\n\nfunction somethingElse() {\n throw new Error(\"somethign else\");\n}\n","\n// Hello world\n\nfunction fourth(){\n throw new Error('fourth');\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;wBCFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;eCVA;AACA;AACA;AACA;AACA;AACA;","file":"with-broken-input-map.js"} -------------------------------------------------------------------------------- /test/fixtures/coffee/aa-loader.js: -------------------------------------------------------------------------------- 1 | exports = {}; 2 | -------------------------------------------------------------------------------- /test/fixtures/coffee/rewriter.coffee: -------------------------------------------------------------------------------- 1 | # The CoffeeScript language has a good deal of optional syntax, implicit syntax, 2 | # and shorthand syntax. This can greatly complicate a grammar and bloat 3 | # the resulting parse table. Instead of making the parser handle it all, we take 4 | # a series of passes over the token stream, using this **Rewriter** to convert 5 | # shorthand into the unambiguous long form, add implicit indentation and 6 | # parentheses, and generally clean things up. 7 | 8 | # Create a generated token: one that exists due to a use of implicit syntax. 9 | generate = (tag, value, origin) -> 10 | tok = [tag, value] 11 | tok.generated = yes 12 | tok.origin = origin if origin 13 | tok 14 | 15 | # The **Rewriter** class is used by the [Lexer](lexer.html), directly against 16 | # its internal array of tokens. 17 | class exports.Rewriter 18 | 19 | # Helpful snippet for debugging: 20 | # 21 | # console.log (t[0] + '/' + t[1] for t in @tokens).join ' ' 22 | 23 | # Rewrite the token stream in multiple passes, one logical filter at 24 | # a time. This could certainly be changed into a single pass through the 25 | # stream, with a big ol' efficient switch, but it's much nicer to work with 26 | # like this. The order of these passes matters -- indentation must be 27 | # corrected before implicit parentheses can be wrapped around blocks of code. 28 | rewrite: (@tokens) -> 29 | @removeLeadingNewlines() 30 | @closeOpenCalls() 31 | @closeOpenIndexes() 32 | @normalizeLines() 33 | @tagPostfixConditionals() 34 | @addImplicitBracesAndParens() 35 | @addLocationDataToGeneratedTokens() 36 | @tokens 37 | 38 | # Rewrite the token stream, looking one token ahead and behind. 39 | # Allow the return value of the block to tell us how many tokens to move 40 | # forwards (or backwards) in the stream, to make sure we don't miss anything 41 | # as tokens are inserted and removed, and the stream changes length under 42 | # our feet. 43 | scanTokens: (block) -> 44 | {tokens} = this 45 | i = 0 46 | i += block.call this, token, i, tokens while token = tokens[i] 47 | true 48 | 49 | detectEnd: (i, condition, action) -> 50 | {tokens} = this 51 | levels = 0 52 | while token = tokens[i] 53 | return action.call this, token, i if levels is 0 and condition.call this, token, i 54 | return action.call this, token, i - 1 if not token or levels < 0 55 | if token[0] in EXPRESSION_START 56 | levels += 1 57 | else if token[0] in EXPRESSION_END 58 | levels -= 1 59 | i += 1 60 | i - 1 61 | 62 | # Leading newlines would introduce an ambiguity in the grammar, so we 63 | # dispatch them here. 64 | removeLeadingNewlines: -> 65 | break for [tag], i in @tokens when tag isnt 'TERMINATOR' 66 | @tokens.splice 0, i if i 67 | 68 | # The lexer has tagged the opening parenthesis of a method call. Match it with 69 | # its paired close. We have the mis-nested outdent case included here for 70 | # calls that close on the same line, just before their outdent. 71 | closeOpenCalls: -> 72 | condition = (token, i) -> 73 | token[0] in [')', 'CALL_END'] or 74 | token[0] is 'OUTDENT' and @tag(i - 1) is ')' 75 | 76 | action = (token, i) -> 77 | @tokens[if token[0] is 'OUTDENT' then i - 1 else i][0] = 'CALL_END' 78 | 79 | @scanTokens (token, i) -> 80 | @detectEnd i + 1, condition, action if token[0] is 'CALL_START' 81 | 1 82 | 83 | # The lexer has tagged the opening parenthesis of an indexing operation call. 84 | # Match it with its paired close. 85 | closeOpenIndexes: -> 86 | condition = (token, i) -> 87 | token[0] in [']', 'INDEX_END'] 88 | 89 | action = (token, i) -> 90 | token[0] = 'INDEX_END' 91 | 92 | @scanTokens (token, i) -> 93 | @detectEnd i + 1, condition, action if token[0] is 'INDEX_START' 94 | 1 95 | 96 | # Match tags in token stream starting at i with pattern, skipping HERECOMMENTs 97 | # Pattern may consist of strings (equality), an array of strings (one of) 98 | # or null (wildcard) 99 | matchTags: (i, pattern...) -> 100 | fuzz = 0 101 | for j in [0 ... pattern.length] 102 | fuzz += 2 while @tag(i + j + fuzz) is 'HERECOMMENT' 103 | continue if not pattern[j]? 104 | pattern[j] = [pattern[j]] if typeof pattern[j] is 'string' 105 | return no if @tag(i + j + fuzz) not in pattern[j] 106 | yes 107 | 108 | # yes iff standing in front of something looking like 109 | # @: or :, skipping over 'HERECOMMENT's 110 | looksObjectish: (j) -> 111 | @matchTags(j, '@', null, ':') or @matchTags(j, null, ':') 112 | 113 | # yes iff current line of tokens contain an element of tags on same 114 | # expression level. Stop searching at LINEBREAKS or explicit start of 115 | # containing balanced expression. 116 | findTagsBackwards: (i, tags) -> 117 | backStack = [] 118 | while i >= 0 and (backStack.length or 119 | @tag(i) not in tags and 120 | (@tag(i) not in EXPRESSION_START or @tokens[i].generated) and 121 | @tag(i) not in LINEBREAKS) 122 | backStack.push @tag(i) if @tag(i) in EXPRESSION_END 123 | backStack.pop() if @tag(i) in EXPRESSION_START and backStack.length 124 | i -= 1 125 | @tag(i) in tags 126 | 127 | # Look for signs of implicit calls and objects in the token stream and 128 | # add them. 129 | addImplicitBracesAndParens: -> 130 | # Track current balancing depth (both implicit and explicit) on stack. 131 | stack = [] 132 | 133 | @scanTokens (token, i, tokens) -> 134 | [tag] = token 135 | [prevTag] = prevToken = if i > 0 then tokens[i - 1] else [] 136 | [nextTag] = if i < tokens.length - 1 then tokens[i + 1] else [] 137 | stackTop = -> stack[stack.length - 1] 138 | startIdx = i 139 | 140 | # Helper function, used for keeping track of the number of tokens consumed 141 | # and spliced, when returning for getting a new token. 142 | forward = (n) -> i - startIdx + n 143 | 144 | # Helper functions 145 | inImplicit = -> stackTop()?[2]?.ours 146 | inImplicitCall = -> inImplicit() and stackTop()?[0] is '(' 147 | inImplicitObject = -> inImplicit() and stackTop()?[0] is '{' 148 | # Unclosed control statement inside implicit parens (like 149 | # class declaration or if-conditionals) 150 | inImplicitControl = -> inImplicit and stackTop()?[0] is 'CONTROL' 151 | 152 | startImplicitCall = (j) -> 153 | idx = j ? i 154 | stack.push ['(', idx, ours: yes] 155 | tokens.splice idx, 0, generate 'CALL_START', '(' 156 | i += 1 if not j? 157 | 158 | endImplicitCall = -> 159 | stack.pop() 160 | tokens.splice i, 0, generate 'CALL_END', ')' 161 | i += 1 162 | 163 | startImplicitObject = (j, startsLine = yes) -> 164 | idx = j ? i 165 | stack.push ['{', idx, sameLine: yes, startsLine: startsLine, ours: yes] 166 | tokens.splice idx, 0, generate '{', generate(new String('{')), token 167 | i += 1 if not j? 168 | 169 | endImplicitObject = (j) -> 170 | j = j ? i 171 | stack.pop() 172 | tokens.splice j, 0, generate '}', '}', token 173 | i += 1 174 | 175 | # Don't end an implicit call on next indent if any of these are in an argument 176 | if inImplicitCall() and tag in ['IF', 'TRY', 'FINALLY', 'CATCH', 177 | 'CLASS', 'SWITCH'] 178 | stack.push ['CONTROL', i, ours: true] 179 | return forward(1) 180 | 181 | if tag is 'INDENT' and inImplicit() 182 | 183 | # An `INDENT` closes an implicit call unless 184 | # 185 | # 1. We have seen a `CONTROL` argument on the line. 186 | # 2. The last token before the indent is part of the list below 187 | # 188 | if prevTag not in ['=>', '->', '[', '(', ',', '{', 'TRY', 'ELSE', '='] 189 | endImplicitCall() while inImplicitCall() 190 | stack.pop() if inImplicitControl() 191 | stack.push [tag, i] 192 | return forward(1) 193 | 194 | # Straightforward start of explicit expression 195 | if tag in EXPRESSION_START 196 | stack.push [tag, i] 197 | return forward(1) 198 | 199 | # Close all implicit expressions inside of explicitly closed expressions. 200 | if tag in EXPRESSION_END 201 | while inImplicit() 202 | if inImplicitCall() 203 | endImplicitCall() 204 | else if inImplicitObject() 205 | endImplicitObject() 206 | else 207 | stack.pop() 208 | stack.pop() 209 | 210 | # Recognize standard implicit calls like 211 | # f a, f() b, f? c, h[0] d etc. 212 | if (tag in IMPLICIT_FUNC and token.spaced and not token.stringEnd or 213 | tag is '?' and i > 0 and not tokens[i - 1].spaced) and 214 | (nextTag in IMPLICIT_CALL or 215 | nextTag in IMPLICIT_UNSPACED_CALL and 216 | not tokens[i + 1]?.spaced and not tokens[i + 1]?.newLine) 217 | tag = token[0] = 'FUNC_EXIST' if tag is '?' 218 | startImplicitCall i + 1 219 | return forward(2) 220 | 221 | # Implicit call taking an implicit indented object as first argument. 222 | # 223 | # f 224 | # a: b 225 | # c: d 226 | # 227 | # and 228 | # 229 | # f 230 | # 1 231 | # a: b 232 | # b: c 233 | # 234 | # Don't accept implicit calls of this type, when on the same line 235 | # as the control strucutures below as that may misinterpret constructs like: 236 | # 237 | # if f 238 | # a: 1 239 | # as 240 | # 241 | # if f(a: 1) 242 | # 243 | # which is probably always unintended. 244 | # Furthermore don't allow this in literal arrays, as 245 | # that creates grammatical ambiguities. 246 | if tag in IMPLICIT_FUNC and @matchTags(i + 1, 'INDENT', null, ':') and 247 | not @findTagsBackwards(i, ['CLASS', 'EXTENDS', 'IF', 'CATCH', 248 | 'SWITCH', 'LEADING_WHEN', 'FOR', 'WHILE', 'UNTIL']) 249 | startImplicitCall i + 1 250 | stack.push ['INDENT', i + 2] 251 | return forward(3) 252 | 253 | # Implicit objects start here 254 | if tag is ':' 255 | # Go back to the (implicit) start of the object 256 | if @tag(i - 2) is '@' then s = i - 2 else s = i - 1 257 | s -= 2 while @tag(s - 2) is 'HERECOMMENT' 258 | 259 | # Mark if the value is a for loop 260 | @insideForDeclaration = nextTag is 'FOR' 261 | 262 | startsLine = s is 0 or @tag(s - 1) in LINEBREAKS or tokens[s - 1].newLine 263 | # Are we just continuing an already declared object? 264 | if stackTop() 265 | [stackTag, stackIdx] = stackTop() 266 | if (stackTag is '{' or stackTag is 'INDENT' and @tag(stackIdx - 1) is '{') and 267 | (startsLine or @tag(s - 1) is ',' or @tag(s - 1) is '{') 268 | return forward(1) 269 | 270 | startImplicitObject(s, !!startsLine) 271 | return forward(2) 272 | 273 | # End implicit calls when chaining method calls 274 | # like e.g.: 275 | # 276 | # f -> 277 | # a 278 | # .g b, -> 279 | # c 280 | # .h a 281 | # 282 | # and also 283 | # 284 | # f a 285 | # .g b 286 | # .h a 287 | 288 | stackTop()[2].sameLine = no if inImplicitObject() and tag in LINEBREAKS 289 | 290 | newLine = prevTag is 'OUTDENT' or prevToken.newLine 291 | if tag in IMPLICIT_END or tag in CALL_CLOSERS and newLine 292 | while inImplicit() 293 | [stackTag, stackIdx, {sameLine, startsLine}] = stackTop() 294 | # Close implicit calls when reached end of argument list 295 | if inImplicitCall() and prevTag isnt ',' 296 | endImplicitCall() 297 | # Close implicit objects such as: 298 | # return a: 1, b: 2 unless true 299 | else if inImplicitObject() and not @insideForDeclaration and sameLine and 300 | tag isnt 'TERMINATOR' and prevTag isnt ':' and 301 | endImplicitObject() 302 | # Close implicit objects when at end of line, line didn't end with a comma 303 | # and the implicit object didn't start the line or the next line doesn't look like 304 | # the continuation of an object. 305 | else if inImplicitObject() and tag is 'TERMINATOR' and prevTag isnt ',' and 306 | not (startsLine and @looksObjectish(i + 1)) 307 | endImplicitObject() 308 | else 309 | break 310 | 311 | # Close implicit object if comma is the last character 312 | # and what comes after doesn't look like it belongs. 313 | # This is used for trailing commas and calls, like: 314 | # 315 | # x = 316 | # a: b, 317 | # c: d, 318 | # e = 2 319 | # 320 | # and 321 | # 322 | # f a, b: c, d: e, f, g: h: i, j 323 | # 324 | if tag is ',' and not @looksObjectish(i + 1) and inImplicitObject() and 325 | not @insideForDeclaration and 326 | (nextTag isnt 'TERMINATOR' or not @looksObjectish(i + 2)) 327 | # When nextTag is OUTDENT the comma is insignificant and 328 | # should just be ignored so embed it in the implicit object. 329 | # 330 | # When it isn't the comma go on to play a role in a call or 331 | # array further up the stack, so give it a chance. 332 | 333 | offset = if nextTag is 'OUTDENT' then 1 else 0 334 | while inImplicitObject() 335 | endImplicitObject i + offset 336 | return forward(1) 337 | 338 | # Add location data to all tokens generated by the rewriter. 339 | addLocationDataToGeneratedTokens: -> 340 | @scanTokens (token, i, tokens) -> 341 | return 1 if token[2] 342 | return 1 unless token.generated or token.explicit 343 | if token[0] is '{' and nextLocation=tokens[i + 1]?[2] 344 | {first_line: line, first_column: column} = nextLocation 345 | else if prevLocation = tokens[i - 1]?[2] 346 | {last_line: line, last_column: column} = prevLocation 347 | else 348 | line = column = 0 349 | token[2] = 350 | first_line: line 351 | first_column: column 352 | last_line: line 353 | last_column: column 354 | return 1 355 | 356 | # Because our grammar is LALR(1), it can't handle some single-line 357 | # expressions that lack ending delimiters. The **Rewriter** adds the implicit 358 | # blocks, so it doesn't need to. To keep the grammar clean and tidy, trailing 359 | # newlines within expressions are removed and the indentation tokens of empty 360 | # blocks are added. 361 | normalizeLines: -> 362 | starter = indent = outdent = null 363 | 364 | condition = (token, i) -> 365 | token[1] isnt ';' and token[0] in SINGLE_CLOSERS and 366 | not (token[0] is 'TERMINATOR' and @tag(i + 1) in EXPRESSION_CLOSE) and 367 | not (token[0] is 'ELSE' and starter isnt 'THEN') and 368 | not (token[0] in ['CATCH', 'FINALLY'] and starter in ['->', '=>']) or 369 | token[0] in CALL_CLOSERS and @tokens[i - 1].newLine 370 | 371 | action = (token, i) -> 372 | @tokens.splice (if @tag(i - 1) is ',' then i - 1 else i), 0, outdent 373 | 374 | @scanTokens (token, i, tokens) -> 375 | [tag] = token 376 | if tag is 'TERMINATOR' 377 | if @tag(i + 1) is 'ELSE' and @tag(i - 1) isnt 'OUTDENT' 378 | tokens.splice i, 1, @indentation()... 379 | return 1 380 | if @tag(i + 1) in EXPRESSION_CLOSE 381 | tokens.splice i, 1 382 | return 0 383 | if tag is 'CATCH' 384 | for j in [1..2] when @tag(i + j) in ['OUTDENT', 'TERMINATOR', 'FINALLY'] 385 | tokens.splice i + j, 0, @indentation()... 386 | return 2 + j 387 | if tag in SINGLE_LINERS and @tag(i + 1) isnt 'INDENT' and 388 | not (tag is 'ELSE' and @tag(i + 1) is 'IF') 389 | starter = tag 390 | [indent, outdent] = @indentation tokens[i] 391 | indent.fromThen = true if starter is 'THEN' 392 | tokens.splice i + 1, 0, indent 393 | @detectEnd i + 2, condition, action 394 | tokens.splice i, 1 if tag is 'THEN' 395 | return 1 396 | return 1 397 | 398 | # Tag postfix conditionals as such, so that we can parse them with a 399 | # different precedence. 400 | tagPostfixConditionals: -> 401 | 402 | original = null 403 | 404 | condition = (token, i) -> 405 | [tag] = token 406 | [prevTag] = @tokens[i - 1] 407 | tag is 'TERMINATOR' or (tag is 'INDENT' and prevTag not in SINGLE_LINERS) 408 | 409 | action = (token, i) -> 410 | if token[0] isnt 'INDENT' or (token.generated and not token.fromThen) 411 | original[0] = 'POST_' + original[0] 412 | 413 | @scanTokens (token, i) -> 414 | return 1 unless token[0] is 'IF' 415 | original = token 416 | @detectEnd i + 1, condition, action 417 | return 1 418 | 419 | # Generate the indentation tokens, based on another token on the same line. 420 | indentation: (origin) -> 421 | indent = ['INDENT', 2] 422 | outdent = ['OUTDENT', 2] 423 | if origin 424 | indent.generated = outdent.generated = yes 425 | indent.origin = outdent.origin = origin 426 | else 427 | indent.explicit = outdent.explicit = yes 428 | [indent, outdent] 429 | 430 | generate: generate 431 | 432 | # Look up a tag by token index. 433 | tag: (i) -> @tokens[i]?[0] 434 | 435 | # Constants 436 | # --------- 437 | 438 | # List of the token pairs that must be balanced. 439 | BALANCED_PAIRS = [ 440 | ['(', ')'] 441 | ['[', ']'] 442 | ['{', '}'] 443 | ['INDENT', 'OUTDENT'], 444 | ['CALL_START', 'CALL_END'] 445 | ['PARAM_START', 'PARAM_END'] 446 | ['INDEX_START', 'INDEX_END'] 447 | ] 448 | 449 | # The inverse mappings of `BALANCED_PAIRS` we're trying to fix up, so we can 450 | # look things up from either end. 451 | exports.INVERSES = INVERSES = {} 452 | 453 | # The tokens that signal the start/end of a balanced pair. 454 | EXPRESSION_START = [] 455 | EXPRESSION_END = [] 456 | 457 | for [left, rite] in BALANCED_PAIRS 458 | EXPRESSION_START.push INVERSES[rite] = left 459 | EXPRESSION_END .push INVERSES[left] = rite 460 | 461 | # Tokens that indicate the close of a clause of an expression. 462 | EXPRESSION_CLOSE = ['CATCH', 'THEN', 'ELSE', 'FINALLY'].concat EXPRESSION_END 463 | 464 | # Tokens that, if followed by an `IMPLICIT_CALL`, indicate a function invocation. 465 | IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS'] 466 | 467 | # If preceded by an `IMPLICIT_FUNC`, indicates a function invocation. 468 | IMPLICIT_CALL = [ 469 | 'IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS' 470 | 'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL', 'UNDEFINED', 'UNARY', 'YIELD' 471 | 'UNARY_MATH', 'SUPER', 'THROW', '@', '->', '=>', '[', '(', '{', '--', '++' 472 | ] 473 | 474 | IMPLICIT_UNSPACED_CALL = ['+', '-'] 475 | 476 | # Tokens that always mark the end of an implicit call for single-liners. 477 | IMPLICIT_END = ['POST_IF', 'FOR', 'WHILE', 'UNTIL', 'WHEN', 'BY', 478 | 'LOOP', 'TERMINATOR'] 479 | 480 | # Single-line flavors of block expressions that have unclosed endings. 481 | # The grammar can't disambiguate them, so we insert the implicit indentation. 482 | SINGLE_LINERS = ['ELSE', '->', '=>', 'TRY', 'FINALLY', 'THEN'] 483 | SINGLE_CLOSERS = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN'] 484 | 485 | # Tokens that end a line. 486 | LINEBREAKS = ['TERMINATOR', 'INDENT', 'OUTDENT'] 487 | 488 | # Tokens that close open calls when they follow a newline. 489 | CALL_CLOSERS = ['.', '?.', '::', '?::'] 490 | -------------------------------------------------------------------------------- /test/fixtures/coffee/rewriter.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.8.0 2 | (function() { 3 | var BALANCED_PAIRS, CALL_CLOSERS, EXPRESSION_CLOSE, EXPRESSION_END, EXPRESSION_START, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, IMPLICIT_UNSPACED_CALL, INVERSES, LINEBREAKS, SINGLE_CLOSERS, SINGLE_LINERS, generate, left, rite, _i, _len, _ref, 4 | __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, 5 | __slice = [].slice; 6 | 7 | generate = function(tag, value, origin) { 8 | var tok; 9 | tok = [tag, value]; 10 | tok.generated = true; 11 | if (origin) { 12 | tok.origin = origin; 13 | } 14 | return tok; 15 | }; 16 | 17 | exports.Rewriter = (function() { 18 | function Rewriter() {} 19 | 20 | Rewriter.prototype.rewrite = function(tokens) { 21 | this.tokens = tokens; 22 | this.removeLeadingNewlines(); 23 | this.closeOpenCalls(); 24 | this.closeOpenIndexes(); 25 | this.normalizeLines(); 26 | this.tagPostfixConditionals(); 27 | this.addImplicitBracesAndParens(); 28 | this.addLocationDataToGeneratedTokens(); 29 | return this.tokens; 30 | }; 31 | 32 | Rewriter.prototype.scanTokens = function(block) { 33 | var i, token, tokens; 34 | tokens = this.tokens; 35 | i = 0; 36 | while (token = tokens[i]) { 37 | i += block.call(this, token, i, tokens); 38 | } 39 | return true; 40 | }; 41 | 42 | Rewriter.prototype.detectEnd = function(i, condition, action) { 43 | var levels, token, tokens, _ref, _ref1; 44 | tokens = this.tokens; 45 | levels = 0; 46 | while (token = tokens[i]) { 47 | if (levels === 0 && condition.call(this, token, i)) { 48 | return action.call(this, token, i); 49 | } 50 | if (!token || levels < 0) { 51 | return action.call(this, token, i - 1); 52 | } 53 | if (_ref = token[0], __indexOf.call(EXPRESSION_START, _ref) >= 0) { 54 | levels += 1; 55 | } else if (_ref1 = token[0], __indexOf.call(EXPRESSION_END, _ref1) >= 0) { 56 | levels -= 1; 57 | } 58 | i += 1; 59 | } 60 | return i - 1; 61 | }; 62 | 63 | Rewriter.prototype.removeLeadingNewlines = function() { 64 | var i, tag, _i, _len, _ref; 65 | _ref = this.tokens; 66 | for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { 67 | tag = _ref[i][0]; 68 | if (tag !== 'TERMINATOR') { 69 | break; 70 | } 71 | } 72 | if (i) { 73 | return this.tokens.splice(0, i); 74 | } 75 | }; 76 | 77 | Rewriter.prototype.closeOpenCalls = function() { 78 | var action, condition; 79 | condition = function(token, i) { 80 | var _ref; 81 | return ((_ref = token[0]) === ')' || _ref === 'CALL_END') || token[0] === 'OUTDENT' && this.tag(i - 1) === ')'; 82 | }; 83 | action = function(token, i) { 84 | return this.tokens[token[0] === 'OUTDENT' ? i - 1 : i][0] = 'CALL_END'; 85 | }; 86 | return this.scanTokens(function(token, i) { 87 | if (token[0] === 'CALL_START') { 88 | this.detectEnd(i + 1, condition, action); 89 | } 90 | return 1; 91 | }); 92 | }; 93 | 94 | Rewriter.prototype.closeOpenIndexes = function() { 95 | var action, condition; 96 | condition = function(token, i) { 97 | var _ref; 98 | return (_ref = token[0]) === ']' || _ref === 'INDEX_END'; 99 | }; 100 | action = function(token, i) { 101 | return token[0] = 'INDEX_END'; 102 | }; 103 | return this.scanTokens(function(token, i) { 104 | if (token[0] === 'INDEX_START') { 105 | this.detectEnd(i + 1, condition, action); 106 | } 107 | return 1; 108 | }); 109 | }; 110 | 111 | Rewriter.prototype.matchTags = function() { 112 | var fuzz, i, j, pattern, _i, _ref, _ref1; 113 | i = arguments[0], pattern = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 114 | fuzz = 0; 115 | for (j = _i = 0, _ref = pattern.length; 0 <= _ref ? _i < _ref : _i > _ref; j = 0 <= _ref ? ++_i : --_i) { 116 | while (this.tag(i + j + fuzz) === 'HERECOMMENT') { 117 | fuzz += 2; 118 | } 119 | if (pattern[j] == null) { 120 | continue; 121 | } 122 | if (typeof pattern[j] === 'string') { 123 | pattern[j] = [pattern[j]]; 124 | } 125 | if (_ref1 = this.tag(i + j + fuzz), __indexOf.call(pattern[j], _ref1) < 0) { 126 | return false; 127 | } 128 | } 129 | return true; 130 | }; 131 | 132 | Rewriter.prototype.looksObjectish = function(j) { 133 | return this.matchTags(j, '@', null, ':') || this.matchTags(j, null, ':'); 134 | }; 135 | 136 | Rewriter.prototype.findTagsBackwards = function(i, tags) { 137 | var backStack, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; 138 | backStack = []; 139 | while (i >= 0 && (backStack.length || (_ref2 = this.tag(i), __indexOf.call(tags, _ref2) < 0) && ((_ref3 = this.tag(i), __indexOf.call(EXPRESSION_START, _ref3) < 0) || this.tokens[i].generated) && (_ref4 = this.tag(i), __indexOf.call(LINEBREAKS, _ref4) < 0))) { 140 | if (_ref = this.tag(i), __indexOf.call(EXPRESSION_END, _ref) >= 0) { 141 | backStack.push(this.tag(i)); 142 | } 143 | if ((_ref1 = this.tag(i), __indexOf.call(EXPRESSION_START, _ref1) >= 0) && backStack.length) { 144 | backStack.pop(); 145 | } 146 | i -= 1; 147 | } 148 | return _ref5 = this.tag(i), __indexOf.call(tags, _ref5) >= 0; 149 | }; 150 | 151 | Rewriter.prototype.addImplicitBracesAndParens = function() { 152 | var stack; 153 | stack = []; 154 | return this.scanTokens(function(token, i, tokens) { 155 | var endImplicitCall, endImplicitObject, forward, inImplicit, inImplicitCall, inImplicitControl, inImplicitObject, newLine, nextTag, offset, prevTag, prevToken, s, sameLine, stackIdx, stackTag, stackTop, startIdx, startImplicitCall, startImplicitObject, startsLine, tag, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; 156 | tag = token[0]; 157 | prevTag = (prevToken = i > 0 ? tokens[i - 1] : [])[0]; 158 | nextTag = (i < tokens.length - 1 ? tokens[i + 1] : [])[0]; 159 | stackTop = function() { 160 | return stack[stack.length - 1]; 161 | }; 162 | startIdx = i; 163 | forward = function(n) { 164 | return i - startIdx + n; 165 | }; 166 | inImplicit = function() { 167 | var _ref, _ref1; 168 | return (_ref = stackTop()) != null ? (_ref1 = _ref[2]) != null ? _ref1.ours : void 0 : void 0; 169 | }; 170 | inImplicitCall = function() { 171 | var _ref; 172 | return inImplicit() && ((_ref = stackTop()) != null ? _ref[0] : void 0) === '('; 173 | }; 174 | inImplicitObject = function() { 175 | var _ref; 176 | return inImplicit() && ((_ref = stackTop()) != null ? _ref[0] : void 0) === '{'; 177 | }; 178 | inImplicitControl = function() { 179 | var _ref; 180 | return inImplicit && ((_ref = stackTop()) != null ? _ref[0] : void 0) === 'CONTROL'; 181 | }; 182 | startImplicitCall = function(j) { 183 | var idx; 184 | idx = j != null ? j : i; 185 | stack.push([ 186 | '(', idx, { 187 | ours: true 188 | } 189 | ]); 190 | tokens.splice(idx, 0, generate('CALL_START', '(')); 191 | if (j == null) { 192 | return i += 1; 193 | } 194 | }; 195 | endImplicitCall = function() { 196 | stack.pop(); 197 | tokens.splice(i, 0, generate('CALL_END', ')')); 198 | return i += 1; 199 | }; 200 | startImplicitObject = function(j, startsLine) { 201 | var idx; 202 | if (startsLine == null) { 203 | startsLine = true; 204 | } 205 | idx = j != null ? j : i; 206 | stack.push([ 207 | '{', idx, { 208 | sameLine: true, 209 | startsLine: startsLine, 210 | ours: true 211 | } 212 | ]); 213 | tokens.splice(idx, 0, generate('{', generate(new String('{')), token)); 214 | if (j == null) { 215 | return i += 1; 216 | } 217 | }; 218 | endImplicitObject = function(j) { 219 | j = j != null ? j : i; 220 | stack.pop(); 221 | tokens.splice(j, 0, generate('}', '}', token)); 222 | return i += 1; 223 | }; 224 | if (inImplicitCall() && (tag === 'IF' || tag === 'TRY' || tag === 'FINALLY' || tag === 'CATCH' || tag === 'CLASS' || tag === 'SWITCH')) { 225 | stack.push([ 226 | 'CONTROL', i, { 227 | ours: true 228 | } 229 | ]); 230 | return forward(1); 231 | } 232 | if (tag === 'INDENT' && inImplicit()) { 233 | if (prevTag !== '=>' && prevTag !== '->' && prevTag !== '[' && prevTag !== '(' && prevTag !== ',' && prevTag !== '{' && prevTag !== 'TRY' && prevTag !== 'ELSE' && prevTag !== '=') { 234 | while (inImplicitCall()) { 235 | endImplicitCall(); 236 | } 237 | } 238 | if (inImplicitControl()) { 239 | stack.pop(); 240 | } 241 | stack.push([tag, i]); 242 | return forward(1); 243 | } 244 | if (__indexOf.call(EXPRESSION_START, tag) >= 0) { 245 | stack.push([tag, i]); 246 | return forward(1); 247 | } 248 | if (__indexOf.call(EXPRESSION_END, tag) >= 0) { 249 | while (inImplicit()) { 250 | if (inImplicitCall()) { 251 | endImplicitCall(); 252 | } else if (inImplicitObject()) { 253 | endImplicitObject(); 254 | } else { 255 | stack.pop(); 256 | } 257 | } 258 | stack.pop(); 259 | } 260 | if ((__indexOf.call(IMPLICIT_FUNC, tag) >= 0 && token.spaced && !token.stringEnd || tag === '?' && i > 0 && !tokens[i - 1].spaced) && (__indexOf.call(IMPLICIT_CALL, nextTag) >= 0 || __indexOf.call(IMPLICIT_UNSPACED_CALL, nextTag) >= 0 && !((_ref = tokens[i + 1]) != null ? _ref.spaced : void 0) && !((_ref1 = tokens[i + 1]) != null ? _ref1.newLine : void 0))) { 261 | if (tag === '?') { 262 | tag = token[0] = 'FUNC_EXIST'; 263 | } 264 | startImplicitCall(i + 1); 265 | return forward(2); 266 | } 267 | if (__indexOf.call(IMPLICIT_FUNC, tag) >= 0 && this.matchTags(i + 1, 'INDENT', null, ':') && !this.findTagsBackwards(i, ['CLASS', 'EXTENDS', 'IF', 'CATCH', 'SWITCH', 'LEADING_WHEN', 'FOR', 'WHILE', 'UNTIL'])) { 268 | startImplicitCall(i + 1); 269 | stack.push(['INDENT', i + 2]); 270 | return forward(3); 271 | } 272 | if (tag === ':') { 273 | if (this.tag(i - 2) === '@') { 274 | s = i - 2; 275 | } else { 276 | s = i - 1; 277 | } 278 | while (this.tag(s - 2) === 'HERECOMMENT') { 279 | s -= 2; 280 | } 281 | this.insideForDeclaration = nextTag === 'FOR'; 282 | startsLine = s === 0 || (_ref2 = this.tag(s - 1), __indexOf.call(LINEBREAKS, _ref2) >= 0) || tokens[s - 1].newLine; 283 | if (stackTop()) { 284 | _ref3 = stackTop(), stackTag = _ref3[0], stackIdx = _ref3[1]; 285 | if ((stackTag === '{' || stackTag === 'INDENT' && this.tag(stackIdx - 1) === '{') && (startsLine || this.tag(s - 1) === ',' || this.tag(s - 1) === '{')) { 286 | return forward(1); 287 | } 288 | } 289 | startImplicitObject(s, !!startsLine); 290 | return forward(2); 291 | } 292 | if (inImplicitObject() && __indexOf.call(LINEBREAKS, tag) >= 0) { 293 | stackTop()[2].sameLine = false; 294 | } 295 | newLine = prevTag === 'OUTDENT' || prevToken.newLine; 296 | if (__indexOf.call(IMPLICIT_END, tag) >= 0 || __indexOf.call(CALL_CLOSERS, tag) >= 0 && newLine) { 297 | while (inImplicit()) { 298 | _ref4 = stackTop(), stackTag = _ref4[0], stackIdx = _ref4[1], (_ref5 = _ref4[2], sameLine = _ref5.sameLine, startsLine = _ref5.startsLine); 299 | if (inImplicitCall() && prevTag !== ',') { 300 | endImplicitCall(); 301 | } else if (inImplicitObject() && !this.insideForDeclaration && sameLine && tag !== 'TERMINATOR' && prevTag !== ':' && endImplicitObject()) { 302 | 303 | } else if (inImplicitObject() && tag === 'TERMINATOR' && prevTag !== ',' && !(startsLine && this.looksObjectish(i + 1))) { 304 | endImplicitObject(); 305 | } else { 306 | break; 307 | } 308 | } 309 | } 310 | if (tag === ',' && !this.looksObjectish(i + 1) && inImplicitObject() && !this.insideForDeclaration && (nextTag !== 'TERMINATOR' || !this.looksObjectish(i + 2))) { 311 | offset = nextTag === 'OUTDENT' ? 1 : 0; 312 | while (inImplicitObject()) { 313 | endImplicitObject(i + offset); 314 | } 315 | } 316 | return forward(1); 317 | }); 318 | }; 319 | 320 | Rewriter.prototype.addLocationDataToGeneratedTokens = function() { 321 | return this.scanTokens(function(token, i, tokens) { 322 | var column, line, nextLocation, prevLocation, _ref, _ref1; 323 | if (token[2]) { 324 | return 1; 325 | } 326 | if (!(token.generated || token.explicit)) { 327 | return 1; 328 | } 329 | if (token[0] === '{' && (nextLocation = (_ref = tokens[i + 1]) != null ? _ref[2] : void 0)) { 330 | line = nextLocation.first_line, column = nextLocation.first_column; 331 | } else if (prevLocation = (_ref1 = tokens[i - 1]) != null ? _ref1[2] : void 0) { 332 | line = prevLocation.last_line, column = prevLocation.last_column; 333 | } else { 334 | line = column = 0; 335 | } 336 | token[2] = { 337 | first_line: line, 338 | first_column: column, 339 | last_line: line, 340 | last_column: column 341 | }; 342 | return 1; 343 | }); 344 | }; 345 | 346 | Rewriter.prototype.normalizeLines = function() { 347 | var action, condition, indent, outdent, starter; 348 | starter = indent = outdent = null; 349 | condition = function(token, i) { 350 | var _ref, _ref1, _ref2, _ref3; 351 | return token[1] !== ';' && (_ref = token[0], __indexOf.call(SINGLE_CLOSERS, _ref) >= 0) && !(token[0] === 'TERMINATOR' && (_ref1 = this.tag(i + 1), __indexOf.call(EXPRESSION_CLOSE, _ref1) >= 0)) && !(token[0] === 'ELSE' && starter !== 'THEN') && !(((_ref2 = token[0]) === 'CATCH' || _ref2 === 'FINALLY') && (starter === '->' || starter === '=>')) || (_ref3 = token[0], __indexOf.call(CALL_CLOSERS, _ref3) >= 0) && this.tokens[i - 1].newLine; 352 | }; 353 | action = function(token, i) { 354 | return this.tokens.splice((this.tag(i - 1) === ',' ? i - 1 : i), 0, outdent); 355 | }; 356 | return this.scanTokens(function(token, i, tokens) { 357 | var j, tag, _i, _ref, _ref1, _ref2; 358 | tag = token[0]; 359 | if (tag === 'TERMINATOR') { 360 | if (this.tag(i + 1) === 'ELSE' && this.tag(i - 1) !== 'OUTDENT') { 361 | tokens.splice.apply(tokens, [i, 1].concat(__slice.call(this.indentation()))); 362 | return 1; 363 | } 364 | if (_ref = this.tag(i + 1), __indexOf.call(EXPRESSION_CLOSE, _ref) >= 0) { 365 | tokens.splice(i, 1); 366 | return 0; 367 | } 368 | } 369 | if (tag === 'CATCH') { 370 | for (j = _i = 1; _i <= 2; j = ++_i) { 371 | if (!((_ref1 = this.tag(i + j)) === 'OUTDENT' || _ref1 === 'TERMINATOR' || _ref1 === 'FINALLY')) { 372 | continue; 373 | } 374 | tokens.splice.apply(tokens, [i + j, 0].concat(__slice.call(this.indentation()))); 375 | return 2 + j; 376 | } 377 | } 378 | if (__indexOf.call(SINGLE_LINERS, tag) >= 0 && this.tag(i + 1) !== 'INDENT' && !(tag === 'ELSE' && this.tag(i + 1) === 'IF')) { 379 | starter = tag; 380 | _ref2 = this.indentation(tokens[i]), indent = _ref2[0], outdent = _ref2[1]; 381 | if (starter === 'THEN') { 382 | indent.fromThen = true; 383 | } 384 | tokens.splice(i + 1, 0, indent); 385 | this.detectEnd(i + 2, condition, action); 386 | if (tag === 'THEN') { 387 | tokens.splice(i, 1); 388 | } 389 | return 1; 390 | } 391 | return 1; 392 | }); 393 | }; 394 | 395 | Rewriter.prototype.tagPostfixConditionals = function() { 396 | var action, condition, original; 397 | original = null; 398 | condition = function(token, i) { 399 | var prevTag, tag; 400 | tag = token[0]; 401 | prevTag = this.tokens[i - 1][0]; 402 | return tag === 'TERMINATOR' || (tag === 'INDENT' && __indexOf.call(SINGLE_LINERS, prevTag) < 0); 403 | }; 404 | action = function(token, i) { 405 | if (token[0] !== 'INDENT' || (token.generated && !token.fromThen)) { 406 | return original[0] = 'POST_' + original[0]; 407 | } 408 | }; 409 | return this.scanTokens(function(token, i) { 410 | if (token[0] !== 'IF') { 411 | return 1; 412 | } 413 | original = token; 414 | this.detectEnd(i + 1, condition, action); 415 | return 1; 416 | }); 417 | }; 418 | 419 | Rewriter.prototype.indentation = function(origin) { 420 | var indent, outdent; 421 | indent = ['INDENT', 2]; 422 | outdent = ['OUTDENT', 2]; 423 | if (origin) { 424 | indent.generated = outdent.generated = true; 425 | indent.origin = outdent.origin = origin; 426 | } else { 427 | indent.explicit = outdent.explicit = true; 428 | } 429 | return [indent, outdent]; 430 | }; 431 | 432 | Rewriter.prototype.generate = generate; 433 | 434 | Rewriter.prototype.tag = function(i) { 435 | var _ref; 436 | return (_ref = this.tokens[i]) != null ? _ref[0] : void 0; 437 | }; 438 | 439 | return Rewriter; 440 | 441 | })(); 442 | 443 | BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], ['INDENT', 'OUTDENT'], ['CALL_START', 'CALL_END'], ['PARAM_START', 'PARAM_END'], ['INDEX_START', 'INDEX_END']]; 444 | 445 | exports.INVERSES = INVERSES = {}; 446 | 447 | EXPRESSION_START = []; 448 | 449 | EXPRESSION_END = []; 450 | 451 | for (_i = 0, _len = BALANCED_PAIRS.length; _i < _len; _i++) { 452 | _ref = BALANCED_PAIRS[_i], left = _ref[0], rite = _ref[1]; 453 | EXPRESSION_START.push(INVERSES[rite] = left); 454 | EXPRESSION_END.push(INVERSES[left] = rite); 455 | } 456 | 457 | EXPRESSION_CLOSE = ['CATCH', 'THEN', 'ELSE', 'FINALLY'].concat(EXPRESSION_END); 458 | 459 | IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS']; 460 | 461 | IMPLICIT_CALL = ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL', 'UNDEFINED', 'UNARY', 'YIELD', 'UNARY_MATH', 'SUPER', 'THROW', '@', '->', '=>', '[', '(', '{', '--', '++']; 462 | 463 | IMPLICIT_UNSPACED_CALL = ['+', '-']; 464 | 465 | IMPLICIT_END = ['POST_IF', 'FOR', 'WHILE', 'UNTIL', 'WHEN', 'BY', 'LOOP', 'TERMINATOR']; 466 | 467 | SINGLE_LINERS = ['ELSE', '->', '=>', 'TRY', 'FINALLY', 'THEN']; 468 | 469 | SINGLE_CLOSERS = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN']; 470 | 471 | LINEBREAKS = ['TERMINATOR', 'INDENT', 'OUTDENT']; 472 | 473 | CALL_CLOSERS = ['.', '?.', '::', '?::']; 474 | 475 | }).call(this); 476 | 477 | //# sourceMappingURL=rewriter.js.map 478 | -------------------------------------------------------------------------------- /test/fixtures/coffee/rewriter.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "file": "rewriter.js", 4 | "sourceRoot": "", 5 | "sources": [ 6 | "rewriter.coffee" 7 | ], 8 | "names": [], 9 | "mappings": "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" 10 | } -------------------------------------------------------------------------------- /test/fixtures/emptyish/emptyish.js: -------------------------------------------------------------------------------- 1 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJmaXJzdCIsICJleHRyYSJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIiwic291cmNlc0NvbnRlbnQiOlsiZmlyc3QtY29udGVudCJdfQ== 2 | -------------------------------------------------------------------------------- /test/fixtures/emptyish/src/b.js: -------------------------------------------------------------------------------- 1 | function fooFromB() { 2 | console.log('i am b'); 3 | } 4 | //# sourceMappingURL=b.js.map -------------------------------------------------------------------------------- /test/fixtures/emptyish/src/b.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"b.js","sourceRoot":"","sources":["b.ts"], "names":[],"mappings":"AAAA;IACC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AACvB,CAAC"} -------------------------------------------------------------------------------- /test/fixtures/emptyish/src/b.ts: -------------------------------------------------------------------------------- 1 | function fooFromB() { 2 | console.log('i am b'); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/external-content/a.js: -------------------------------------------------------------------------------- 1 | function meaningOfLife() { 2 | throw new Error(42); 3 | } 4 | 5 | function boom() { 6 | throw new Error('boom'); 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/external-content/all-inner.js: -------------------------------------------------------------------------------- 1 | function meaningOfLife() { 2 | throw new Error(42); 3 | } 4 | 5 | function boom() { 6 | throw new Error('boom'); 7 | } 8 | 9 | function somethingElse() { 10 | throw new Error("somethign else"); 11 | } 12 | //# sourceMappingURL=all-inner.map -------------------------------------------------------------------------------- /test/fixtures/external-content/all-inner.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["a.js","inside/b.js"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;ACNA;AACA;AACA;","file":"all-inner.js"} -------------------------------------------------------------------------------- /test/fixtures/external-content/broken-link.js: -------------------------------------------------------------------------------- 1 | function meaningOfLife() { 2 | throw new Error(42); 3 | } 4 | 5 | function boom() { 6 | throw new Error('boom'); 7 | } 8 | 9 | function somethingElse() { 10 | throw new Error("somethign else"); 11 | } 12 | //# sourceMappingURL=foo/bad-link.map 13 | -------------------------------------------------------------------------------- /test/fixtures/external-content/inside/b.js: -------------------------------------------------------------------------------- 1 | function somethingElse() { 2 | throw new Error("somethign else"); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/from-string/external-mapped.js: -------------------------------------------------------------------------------- 1 | function meaningOfLife() { 2 | throw new Error(42); 3 | } 4 | 5 | function boom() { 6 | throw new Error('boom'); 7 | } 8 | 9 | function somethingElse() { 10 | throw new Error("somethign else"); 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/from-string/external-mapped.js-map.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["inner/first.js","inner/second.js"],"sourcesContent":["function meaningOfLife() {\n throw new Error(42);\n}\n\nfunction boom() {\n throw new Error('boom');\n}\n","function somethingElse() {\n throw new Error(\"somethign else\");\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;ACNA;AACA;AACA;","file":"all-inner.js"} 2 | -------------------------------------------------------------------------------- /test/fixtures/iife-wrapping/iife-end: -------------------------------------------------------------------------------- 1 | })(); -------------------------------------------------------------------------------- /test/fixtures/iife-wrapping/iife-start: -------------------------------------------------------------------------------- 1 | (function() { -------------------------------------------------------------------------------- /test/fixtures/inline-mapped.js: -------------------------------------------------------------------------------- 1 | function meaningOfLife() { 2 | throw new Error(42); 3 | } 4 | 5 | function boom() { 6 | throw new Error('boom'); 7 | } 8 | 9 | function somethingElse() { 10 | throw new Error("somethign else"); 11 | } 12 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImlubmVyL2ZpcnN0LmpzIiwiaW5uZXIvc2Vjb25kLmpzIl0sInNvdXJjZXNDb250ZW50IjpbImZ1bmN0aW9uIG1lYW5pbmdPZkxpZmUoKSB7XG4gIHRocm93IG5ldyBFcnJvcig0Mik7XG59XG5cbmZ1bmN0aW9uIGJvb20oKSB7XG4gIHRocm93IG5ldyBFcnJvcignYm9vbScpO1xufVxuIiwiZnVuY3Rpb24gc29tZXRoaW5nRWxzZSgpIHtcbiAgdGhyb3cgbmV3IEVycm9yKFwic29tZXRoaWduIGVsc2VcIik7XG59XG4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDTkE7QUFDQTtBQUNBOyIsImZpbGUiOiJhbGwtaW5uZXIuanMifQ== 13 | -------------------------------------------------------------------------------- /test/fixtures/inner/first.js: -------------------------------------------------------------------------------- 1 | function meaningOfLife() { 2 | throw new Error(42); 3 | } 4 | 5 | function boom() { 6 | throw new Error('boom'); 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/inner/second.js: -------------------------------------------------------------------------------- 1 | function somethingElse() { 2 | throw new Error("somethign else"); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/other/fourth.js: -------------------------------------------------------------------------------- 1 | 2 | // Hello world 3 | 4 | function fourth(){ 5 | throw new Error('fourth'); 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/other/third.js: -------------------------------------------------------------------------------- 1 | function third(){ 2 | throw new Error("oh no"); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/short/rewriter.coffee: -------------------------------------------------------------------------------- 1 | # The CoffeeScript language has a good deal of optional syntax, implicit syntax, 2 | # and shorthand syntax. This can greatly complicate a grammar and bloat 3 | # the resulting parse table. Instead of making the parser handle it all, we take 4 | # a series of passes over the token stream, using this **Rewriter** to convert 5 | # shorthand into the unambiguous long form, add implicit indentation and 6 | # parentheses, and generally clean things up. 7 | 8 | # Create a generated token: one that exists due to a use of implicit syntax. 9 | generate = (tag, value, origin) -> 10 | tok = [tag, value] 11 | tok.generated = yes 12 | tok.origin = origin if origin 13 | tok 14 | 15 | # The **Rewriter** class is used by the [Lexer](lexer.html), directly against 16 | # its internal array of tokens. 17 | class exports.Rewriter 18 | 19 | # Helpful snippet for debugging: 20 | # 21 | # console.log (t[0] + '/' + t[1] for t in @tokens).join ' ' 22 | 23 | # Rewrite the token stream in multiple passes, one logical filter at 24 | # a time. This could certainly be changed into a single pass through the 25 | # stream, with a big ol' efficient switch, but it's much nicer to work with 26 | # like this. The order of these passes matters -- indentation must be 27 | # corrected before implicit parentheses can be wrapped around blocks of code. 28 | rewrite: (@tokens) -> 29 | @removeLeadingNewlines() 30 | @closeOpenCalls() 31 | @closeOpenIndexes() 32 | @normalizeLines() 33 | @tagPostfixConditionals() 34 | @addImplicitBracesAndParens() 35 | @addLocationDataToGeneratedTokens() 36 | @tokens 37 | 38 | # Rewrite the token stream, looking one token ahead and behind. 39 | # Allow the return value of the block to tell us how many tokens to move 40 | # forwards (or backwards) in the stream, to make sure we don't miss anything 41 | # as tokens are inserted and removed, and the stream changes length under 42 | # our feet. 43 | scanTokens: (block) -> 44 | {tokens} = this 45 | i = 0 46 | i += block.call this, token, i, tokens while token = tokens[i] 47 | true 48 | 49 | detectEnd: (i, condition, action) -> 50 | {tokens} = this 51 | levels = 0 52 | while token = tokens[i] 53 | return action.call this, token, i if levels is 0 and condition.call this, token, i 54 | return action.call this, token, i - 1 if not token or levels < 0 55 | if token[0] in EXPRESSION_START 56 | levels += 1 57 | else if token[0] in EXPRESSION_END 58 | levels -= 1 59 | i += 1 60 | i - 1 61 | 62 | # Leading newlines would introduce an ambiguity in the grammar, so we 63 | # dispatch them here. 64 | removeLeadingNewlines: -> 65 | break for [tag], i in @tokens when tag isnt 'TERMINATOR' 66 | @tokens.splice 0, i if i 67 | 68 | # The lexer has tagged the opening parenthesis of a method call. Match it with 69 | # its paired close. We have the mis-nested outdent case included here for 70 | # calls that close on the same line, just before their outdent. 71 | closeOpenCalls: -> 72 | condition = (token, i) -> 73 | token[0] in [')', 'CALL_END'] or 74 | token[0] is 'OUTDENT' and @tag(i - 1) is ')' 75 | 76 | action = (token, i) -> 77 | @tokens[if token[0] is 'OUTDENT' then i - 1 else i][0] = 'CALL_END' 78 | 79 | @scanTokens (token, i) -> 80 | @detectEnd i + 1, condition, action if token[0] is 'CALL_START' 81 | 1 82 | 83 | # The lexer has tagged the opening parenthesis of an indexing operation call. 84 | # Match it with its paired close. 85 | closeOpenIndexes: -> 86 | condition = (token, i) -> 87 | token[0] in [']', 'INDEX_END'] 88 | 89 | action = (token, i) -> 90 | token[0] = 'INDEX_END' 91 | 92 | @scanTokens (token, i) -> 93 | @detectEnd i + 1, condition, action if token[0] is 'INDEX_START' 94 | 1 95 | 96 | # Match tags in token stream starting at i with pattern, skipping HERECOMMENTs 97 | # Pattern may consist of strings (equality), an array of strings (one of) 98 | # or null (wildcard) 99 | matchTags: (i, pattern...) -> 100 | fuzz = 0 101 | for j in [0 ... pattern.length] 102 | fuzz += 2 while @tag(i + j + fuzz) is 'HERECOMMENT' 103 | continue if not pattern[j]? 104 | pattern[j] = [pattern[j]] if typeof pattern[j] is 'string' 105 | return no if @tag(i + j + fuzz) not in pattern[j] 106 | yes 107 | 108 | # yes iff standing in front of something looking like 109 | # @: or :, skipping over 'HERECOMMENT's 110 | looksObjectish: (j) -> 111 | @matchTags(j, '@', null, ':') or @matchTags(j, null, ':') 112 | 113 | # yes iff current line of tokens contain an element of tags on same 114 | # expression level. Stop searching at LINEBREAKS or explicit start of 115 | # containing balanced expression. 116 | findTagsBackwards: (i, tags) -> 117 | backStack = [] 118 | while i >= 0 and (backStack.length or 119 | @tag(i) not in tags and 120 | (@tag(i) not in EXPRESSION_START or @tokens[i].generated) and 121 | @tag(i) not in LINEBREAKS) 122 | backStack.push @tag(i) if @tag(i) in EXPRESSION_END 123 | backStack.pop() if @tag(i) in EXPRESSION_START and backStack.length 124 | i -= 1 125 | @tag(i) in tags 126 | 127 | # Look for signs of implicit calls and objects in the token stream and 128 | # add them. 129 | addImplicitBracesAndParens: -> 130 | # Track current balancing depth (both implicit and explicit) on stack. 131 | stack = [] 132 | 133 | @scanTokens (token, i, tokens) -> 134 | [tag] = token 135 | [prevTag] = prevToken = if i > 0 then tokens[i - 1] else [] 136 | [nextTag] = if i < tokens.length - 1 then tokens[i + 1] else [] 137 | stackTop = -> stack[stack.length - 1] 138 | startIdx = i 139 | 140 | # Helper function, used for keeping track of the number of tokens consumed 141 | # and spliced, when returning for getting a new token. 142 | forward = (n) -> i - startIdx + n 143 | 144 | # Helper functions 145 | inImplicit = -> stackTop()?[2]?.ours 146 | inImplicitCall = -> inImplicit() and stackTop()?[0] is '(' 147 | inImplicitObject = -> inImplicit() and stackTop()?[0] is '{' 148 | # Unclosed control statement inside implicit parens (like 149 | # class declaration or if-conditionals) 150 | inImplicitControl = -> inImplicit and stackTop()?[0] is 'CONTROL' 151 | 152 | startImplicitCall = (j) -> 153 | idx = j ? i 154 | stack.push ['(', idx, ours: yes] 155 | tokens.splice idx, 0, generate 'CALL_START', '(' 156 | i += 1 if not j? 157 | 158 | endImplicitCall = -> 159 | stack.pop() 160 | tokens.splice i, 0, generate 'CALL_END', ')' 161 | i += 1 162 | 163 | startImplicitObject = (j, startsLine = yes) -> 164 | idx = j ? i 165 | stack.push ['{', idx, sameLine: yes, startsLine: startsLine, ours: yes] 166 | tokens.splice idx, 0, generate '{', generate(new String('{')), token 167 | i += 1 if not j? 168 | 169 | endImplicitObject = (j) -> 170 | j = j ? i 171 | stack.pop() 172 | tokens.splice j, 0, generate '}', '}', token 173 | i += 1 174 | 175 | # Don't end an implicit call on next indent if any of these are in an argument 176 | if inImplicitCall() and tag in ['IF', 'TRY', 'FINALLY', 'CATCH', 177 | 'CLASS', 'SWITCH'] 178 | stack.push ['CONTROL', i, ours: true] 179 | return forward(1) 180 | 181 | if tag is 'INDENT' and inImplicit() 182 | 183 | # An `INDENT` closes an implicit call unless 184 | # 185 | # 1. We have seen a `CONTROL` argument on the line. 186 | # 2. The last token before the indent is part of the list below 187 | # 188 | if prevTag not in ['=>', '->', '[', '(', ',', '{', 'TRY', 'ELSE', '='] 189 | endImplicitCall() while inImplicitCall() 190 | stack.pop() if inImplicitControl() 191 | stack.push [tag, i] 192 | return forward(1) 193 | 194 | # Straightforward start of explicit expression 195 | if tag in EXPRESSION_START 196 | stack.push [tag, i] 197 | return forward(1) 198 | 199 | # Close all implicit expressions inside of explicitly closed expressions. 200 | if tag in EXPRESSION_END 201 | while inImplicit() 202 | if inImplicitCall() 203 | endImplicitCall() 204 | else if inImplicitObject() 205 | endImplicitObject() 206 | else 207 | stack.pop() 208 | stack.pop() 209 | 210 | # Recognize standard implicit calls like 211 | # f a, f() b, f? c, h[0] d etc. 212 | if (tag in IMPLICIT_FUNC and token.spaced and not token.stringEnd or 213 | tag is '?' and i > 0 and not tokens[i - 1].spaced) and 214 | (nextTag in IMPLICIT_CALL or 215 | nextTag in IMPLICIT_UNSPACED_CALL and 216 | not tokens[i + 1]?.spaced and not tokens[i + 1]?.newLine) 217 | tag = token[0] = 'FUNC_EXIST' if tag is '?' 218 | startImplicitCall i + 1 219 | return forward(2) 220 | 221 | # Implicit call taking an implicit indented object as first argument. 222 | # 223 | # f 224 | # a: b 225 | # c: d 226 | # 227 | # and 228 | # 229 | # f 230 | # 1 231 | # a: b 232 | # b: c 233 | # 234 | # Don't accept implicit calls of this type, when on the same line 235 | # as the control strucutures below as that may misinterpret constructs like: 236 | # 237 | # if f 238 | # a: 1 239 | # as 240 | # 241 | # if f(a: 1) 242 | # 243 | # which is probably always unintended. 244 | # Furthermore don't allow this in literal arrays, as 245 | # that creates grammatical ambiguities. 246 | if tag in IMPLICIT_FUNC and @matchTags(i + 1, 'INDENT', null, ':') and 247 | not @findTagsBackwards(i, ['CLASS', 'EXTENDS', 'IF', 'CATCH', 248 | 'SWITCH', 'LEADING_WHEN', 'FOR', 'WHILE', 'UNTIL']) 249 | startImplicitCall i + 1 250 | stack.push ['INDENT', i + 2] 251 | return forward(3) 252 | 253 | # Implicit objects start here 254 | if tag is ':' 255 | # Go back to the (implicit) start of the object 256 | if @tag(i - 2) is '@' then s = i - 2 else s = i - 1 257 | s -= 2 while @tag(s - 2) is 'HERECOMMENT' 258 | 259 | # Mark if the value is a for loop 260 | @insideForDeclaration = nextTag is 'FOR' 261 | 262 | startsLine = s is 0 or @tag(s - 1) in LINEBREAKS or tokens[s - 1].newLine 263 | # Are we just continuing an already declared object? 264 | if stackTop() 265 | [stackTag, stackIdx] = stackTop() 266 | if (stackTag is '{' or stackTag is 'INDENT' and @tag(stackIdx - 1) is '{') and 267 | (startsLine or @tag(s - 1) is ',' or @tag(s - 1) is '{') 268 | return forward(1) 269 | 270 | startImplicitObject(s, !!startsLine) 271 | return forward(2) 272 | 273 | # End implicit calls when chaining method calls 274 | # like e.g.: 275 | # 276 | # f -> 277 | # a 278 | # .g b, -> 279 | # c 280 | # .h a 281 | # 282 | # and also 283 | # 284 | # f a 285 | # .g b 286 | # .h a 287 | 288 | stackTop()[2].sameLine = no if inImplicitObject() and tag in LINEBREAKS 289 | 290 | newLine = prevTag is 'OUTDENT' or prevToken.newLine 291 | if tag in IMPLICIT_END or tag in CALL_CLOSERS and newLine 292 | while inImplicit() 293 | [stackTag, stackIdx, {sameLine, startsLine}] = stackTop() 294 | # Close implicit calls when reached end of argument list 295 | if inImplicitCall() and prevTag isnt ',' 296 | endImplicitCall() 297 | # Close implicit objects such as: 298 | # return a: 1, b: 2 unless true 299 | else if inImplicitObject() and not @insideForDeclaration and sameLine and 300 | tag isnt 'TERMINATOR' and prevTag isnt ':' and 301 | endImplicitObject() 302 | # Close implicit objects when at end of line, line didn't end with a comma 303 | # and the implicit object didn't start the line or the next line doesn't look like 304 | # the continuation of an object. 305 | else if inImplicitObject() and tag is 'TERMINATOR' and prevTag isnt ',' and 306 | not (startsLine and @looksObjectish(i + 1)) 307 | endImplicitObject() 308 | else 309 | break 310 | 311 | # Close implicit object if comma is the last character 312 | # and what comes after doesn't look like it belongs. 313 | # This is used for trailing commas and calls, like: 314 | # 315 | # x = 316 | # a: b, 317 | # c: d, 318 | # e = 2 319 | # 320 | # and 321 | # 322 | # f a, b: c, d: e, f, g: h: i, j 323 | # 324 | if tag is ',' and not @looksObjectish(i + 1) and inImplicitObject() and 325 | not @insideForDeclaration and 326 | (nextTag isnt 'TERMINATOR' or not @looksObjectish(i + 2)) 327 | # When nextTag is OUTDENT the comma is insignificant and 328 | # should just be ignored so embed it in the implicit object. 329 | # 330 | # When it isn't the comma go on to play a role in a call or 331 | # array further up the stack, so give it a chance. 332 | 333 | offset = if nextTag is 'OUTDENT' then 1 else 0 334 | while inImplicitObject() 335 | endImplicitObject i + offset 336 | return forward(1) 337 | 338 | # Add location data to all tokens generated by the rewriter. 339 | addLocationDataToGeneratedTokens: -> 340 | @scanTokens (token, i, tokens) -> 341 | return 1 if token[2] 342 | return 1 unless token.generated or token.explicit 343 | if token[0] is '{' and nextLocation=tokens[i + 1]?[2] 344 | {first_line: line, first_column: column} = nextLocation 345 | else if prevLocation = tokens[i - 1]?[2] 346 | {last_line: line, last_column: column} = prevLocation 347 | else 348 | line = column = 0 349 | token[2] = 350 | first_line: line 351 | first_column: column 352 | last_line: line 353 | last_column: column 354 | return 1 355 | 356 | # Because our grammar is LALR(1), it can't handle some single-line 357 | # expressions that lack ending delimiters. The **Rewriter** adds the implicit 358 | # blocks, so it doesn't need to. To keep the grammar clean and tidy, trailing 359 | # newlines within expressions are removed and the indentation tokens of empty 360 | # blocks are added. 361 | normalizeLines: -> 362 | starter = indent = outdent = null 363 | 364 | condition = (token, i) -> 365 | token[1] isnt ';' and token[0] in SINGLE_CLOSERS and 366 | not (token[0] is 'TERMINATOR' and @tag(i + 1) in EXPRESSION_CLOSE) and 367 | not (token[0] is 'ELSE' and starter isnt 'THEN') and 368 | not (token[0] in ['CATCH', 'FINALLY'] and starter in ['->', '=>']) or 369 | token[0] in CALL_CLOSERS and @tokens[i - 1].newLine 370 | 371 | action = (token, i) -> 372 | @tokens.splice (if @tag(i - 1) is ',' then i - 1 else i), 0, outdent 373 | 374 | @scanTokens (token, i, tokens) -> 375 | [tag] = token 376 | if tag is 'TERMINATOR' 377 | if @tag(i + 1) is 'ELSE' and @tag(i - 1) isnt 'OUTDENT' 378 | tokens.splice i, 1, @indentation()... 379 | return 1 380 | if @tag(i + 1) in EXPRESSION_CLOSE 381 | tokens.splice i, 1 382 | return 0 383 | if tag is 'CATCH' 384 | for j in [1..2] when @tag(i + j) in ['OUTDENT', 'TERMINATOR', 'FINALLY'] 385 | tokens.splice i + j, 0, @indentation()... 386 | return 2 + j 387 | if tag in SINGLE_LINERS and @tag(i + 1) isnt 'INDENT' and 388 | not (tag is 'ELSE' and @tag(i + 1) is 'IF') 389 | starter = tag 390 | [indent, outdent] = @indentation tokens[i] 391 | indent.fromThen = true if starter is 'THEN' 392 | tokens.splice i + 1, 0, indent 393 | @detectEnd i + 2, condition, action 394 | tokens.splice i, 1 if tag is 'THEN' 395 | return 1 396 | return 1 397 | 398 | # Tag postfix conditionals as such, so that we can parse them with a 399 | # different precedence. 400 | tagPostfixConditionals: -> 401 | 402 | original = null 403 | 404 | condition = (token, i) -> 405 | [tag] = token 406 | [prevTag] = @tokens[i - 1] 407 | tag is 'TERMINATOR' or (tag is 'INDENT' and prevTag not in SINGLE_LINERS) 408 | 409 | action = (token, i) -> 410 | if token[0] isnt 'INDENT' or (token.generated and not token.fromThen) 411 | original[0] = 'POST_' + original[0] 412 | 413 | @scanTokens (token, i) -> 414 | return 1 unless token[0] is 'IF' 415 | original = token 416 | @detectEnd i + 1, condition, action 417 | return 1 418 | 419 | # Generate the indentation tokens, based on another token on the same line. 420 | indentation: (origin) -> 421 | indent = ['INDENT', 2] 422 | outdent = ['OUTDENT', 2] 423 | if origin 424 | indent.generated = outdent.generated = yes 425 | indent.origin = outdent.origin = origin 426 | else 427 | indent.explicit = outdent.explicit = yes 428 | [indent, outdent] 429 | 430 | generate: generate 431 | 432 | # Look up a tag by token index. 433 | tag: (i) -> @tokens[i]?[0] 434 | 435 | # Constants 436 | # --------- 437 | 438 | # List of the token pairs that must be balanced. 439 | BALANCED_PAIRS = [ 440 | ['(', ')'] 441 | ['[', ']'] 442 | ['{', '}'] 443 | ['INDENT', 'OUTDENT'], 444 | ['CALL_START', 'CALL_END'] 445 | ['PARAM_START', 'PARAM_END'] 446 | ['INDEX_START', 'INDEX_END'] 447 | ] 448 | 449 | # The inverse mappings of `BALANCED_PAIRS` we're trying to fix up, so we can 450 | # look things up from either end. 451 | exports.INVERSES = INVERSES = {} 452 | 453 | # The tokens that signal the start/end of a balanced pair. 454 | EXPRESSION_START = [] 455 | EXPRESSION_END = [] 456 | 457 | for [left, rite] in BALANCED_PAIRS 458 | EXPRESSION_START.push INVERSES[rite] = left 459 | EXPRESSION_END .push INVERSES[left] = rite 460 | 461 | # Tokens that indicate the close of a clause of an expression. 462 | EXPRESSION_CLOSE = ['CATCH', 'THEN', 'ELSE', 'FINALLY'].concat EXPRESSION_END 463 | 464 | # Tokens that, if followed by an `IMPLICIT_CALL`, indicate a function invocation. 465 | IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS'] 466 | 467 | # If preceded by an `IMPLICIT_FUNC`, indicates a function invocation. 468 | IMPLICIT_CALL = [ 469 | 'IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS' 470 | 'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL', 'UNDEFINED', 'UNARY', 'YIELD' 471 | 'UNARY_MATH', 'SUPER', 'THROW', '@', '->', '=>', '[', '(', '{', '--', '++' 472 | ] 473 | 474 | IMPLICIT_UNSPACED_CALL = ['+', '-'] 475 | 476 | # Tokens that always mark the end of an implicit call for single-liners. 477 | IMPLICIT_END = ['POST_IF', 'FOR', 'WHILE', 'UNTIL', 'WHEN', 'BY', 478 | 'LOOP', 'TERMINATOR'] 479 | 480 | # Single-line flavors of block expressions that have unclosed endings. 481 | # The grammar can't disambiguate them, so we insert the implicit indentation. 482 | SINGLE_LINERS = ['ELSE', '->', '=>', 'TRY', 'FINALLY', 'THEN'] 483 | SINGLE_CLOSERS = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN'] 484 | 485 | # Tokens that end a line. 486 | LINEBREAKS = ['TERMINATOR', 'INDENT', 'OUTDENT'] 487 | 488 | # Tokens that close open calls when they follow a newline. 489 | CALL_CLOSERS = ['.', '?.', '::', '?::'] 490 | -------------------------------------------------------------------------------- /test/fixtures/short/rewriter.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.8.0 2 | (function() { 3 | var BALANCED_PAIRS, CALL_CLOSERS, EXPRESSION_CLOSE, EXPRESSION_END, EXPRESSION_START, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, IMPLICIT_UNSPACED_CALL, INVERSES, LINEBREAKS, SINGLE_CLOSERS, SINGLE_LINERS, generate, left, rite, _i, _len, _ref, 4 | __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, 5 | __slice = [].slice; 6 | 7 | generate = function(tag, value, origin) { 8 | var tok; 9 | tok = [tag, value]; 10 | tok.generated = true; 11 | if (origin) { 12 | tok.origin = origin; 13 | } 14 | return tok; 15 | }; 16 | 17 | exports.Rewriter = (function() { 18 | function Rewriter() {} 19 | 20 | Rewriter.prototype.rewrite = function(tokens) { 21 | this.tokens = tokens; 22 | this.removeLeadingNewlines(); 23 | this.closeOpenCalls(); 24 | this.closeOpenIndexes(); 25 | this.normalizeLines(); 26 | this.tagPostfixConditionals(); 27 | this.addImplicitBracesAndParens(); 28 | this.addLocationDataToGeneratedTokens(); 29 | return this.tokens; 30 | }; 31 | 32 | Rewriter.prototype.scanTokens = function(block) { 33 | var i, token, tokens; 34 | tokens = this.tokens; 35 | i = 0; 36 | while (token = tokens[i]) { 37 | i += block.call(this, token, i, tokens); 38 | } 39 | return true; 40 | }; 41 | 42 | Rewriter.prototype.detectEnd = function(i, condition, action) { 43 | var levels, token, tokens, _ref, _ref1; 44 | tokens = this.tokens; 45 | levels = 0; 46 | while (token = tokens[i]) { 47 | if (levels === 0 && condition.call(this, token, i)) { 48 | return action.call(this, token, i); 49 | } 50 | if (!token || levels < 0) { 51 | return action.call(this, token, i - 1); 52 | } 53 | if (_ref = token[0], __indexOf.call(EXPRESSION_START, _ref) >= 0) { 54 | levels += 1; 55 | } else if (_ref1 = token[0], __indexOf.call(EXPRESSION_END, _ref1) >= 0) { 56 | levels -= 1; 57 | } 58 | i += 1; 59 | } 60 | return i - 1; 61 | }; 62 | 63 | Rewriter.prototype.removeLeadingNewlines = function() { 64 | var i, tag, _i, _len, _ref; 65 | _ref = this.tokens; 66 | for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { 67 | tag = _ref[i][0]; 68 | if (tag !== 'TERMINATOR') { 69 | break; 70 | } 71 | } 72 | if (i) { 73 | return this.tokens.splice(0, i); 74 | } 75 | }; 76 | 77 | Rewriter.prototype.closeOpenCalls = function() { 78 | var action, condition; 79 | condition = function(token, i) { 80 | var _ref; 81 | return ((_ref = token[0]) === ')' || _ref === 'CALL_END') || token[0] === 'OUTDENT' && this.tag(i - 1) === ')'; 82 | }; 83 | action = function(token, i) { 84 | return this.tokens[token[0] === 'OUTDENT' ? i - 1 : i][0] = 'CALL_END'; 85 | }; 86 | return this.scanTokens(function(token, i) { 87 | if (token[0] === 'CALL_START') { 88 | this.detectEnd(i + 1, condition, action); 89 | } 90 | return 1; 91 | }); 92 | }; 93 | 94 | Rewriter.prototype.closeOpenIndexes = function() { 95 | var action, condition; 96 | condition = function(token, i) { 97 | var _ref; 98 | return (_ref = token[0]) === ']' || _ref === 'INDEX_END'; 99 | }; 100 | action = function(token, i) { 101 | return token[0] = 'INDEX_END'; 102 | }; 103 | return this.scanTokens(function(token, i) { 104 | if (token[0] === 'INDEX_START') { 105 | this.detectEnd(i + 1, condition, action); 106 | } 107 | return 1; 108 | }); 109 | }; 110 | 111 | Rewriter.prototype.matchTags = function() { 112 | var fuzz, i, j, pattern, _i, _ref, _ref1; 113 | i = arguments[0], pattern = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 114 | fuzz = 0; 115 | for (j = _i = 0, _ref = pattern.length; 0 <= _ref ? _i < _ref : _i > _ref; j = 0 <= _ref ? ++_i : --_i) { 116 | while (this.tag(i + j + fuzz) === 'HERECOMMENT') { 117 | fuzz += 2; 118 | } 119 | if (pattern[j] == null) { 120 | continue; 121 | } 122 | if (typeof pattern[j] === 'string') { 123 | pattern[j] = [pattern[j]]; 124 | } 125 | if (_ref1 = this.tag(i + j + fuzz), __indexOf.call(pattern[j], _ref1) < 0) { 126 | return false; 127 | } 128 | } 129 | return true; 130 | }; 131 | 132 | Rewriter.prototype.looksObjectish = function(j) { 133 | return this.matchTags(j, '@', null, ':') || this.matchTags(j, null, ':'); 134 | }; 135 | 136 | Rewriter.prototype.findTagsBackwards = function(i, tags) { 137 | var backStack, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; 138 | backStack = []; 139 | while (i >= 0 && (backStack.length || (_ref2 = this.tag(i), __indexOf.call(tags, _ref2) < 0) && ((_ref3 = this.tag(i), __indexOf.call(EXPRESSION_START, _ref3) < 0) || this.tokens[i].generated) && (_ref4 = this.tag(i), __indexOf.call(LINEBREAKS, _ref4) < 0))) { 140 | if (_ref = this.tag(i), __indexOf.call(EXPRESSION_END, _ref) >= 0) { 141 | backStack.push(this.tag(i)); 142 | } 143 | if ((_ref1 = this.tag(i), __indexOf.call(EXPRESSION_START, _ref1) >= 0) && backStack.length) { 144 | backStack.pop(); 145 | } 146 | i -= 1; 147 | } 148 | return _ref5 = this.tag(i), __indexOf.call(tags, _ref5) >= 0; 149 | }; 150 | 151 | Rewriter.prototype.addImplicitBracesAndParens = function() { 152 | var stack; 153 | stack = []; 154 | return this.scanTokens(function(token, i, tokens) { 155 | var endImplicitCall, endImplicitObject, forward, inImplicit, inImplicitCall, inImplicitControl, inImplicitObject, newLine, nextTag, offset, prevTag, prevToken, s, sameLine, stackIdx, stackTag, stackTop, startIdx, startImplicitCall, startImplicitObject, startsLine, tag, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; 156 | tag = token[0]; 157 | prevTag = (prevToken = i > 0 ? tokens[i - 1] : [])[0]; 158 | nextTag = (i < tokens.length - 1 ? tokens[i + 1] : [])[0]; 159 | stackTop = function() { 160 | return stack[stack.length - 1]; 161 | }; 162 | startIdx = i; 163 | forward = function(n) { 164 | return i - startIdx + n; 165 | }; 166 | inImplicit = function() { 167 | var _ref, _ref1; 168 | return (_ref = stackTop()) != null ? (_ref1 = _ref[2]) != null ? _ref1.ours : void 0 : void 0; 169 | }; 170 | inImplicitCall = function() { 171 | var _ref; 172 | return inImplicit() && ((_ref = stackTop()) != null ? _ref[0] : void 0) === '('; 173 | }; 174 | inImplicitObject = function() { 175 | var _ref; 176 | return inImplicit() && ((_ref = stackTop()) != null ? _ref[0] : void 0) === '{'; 177 | }; 178 | inImplicitControl = function() { 179 | var _ref; 180 | return inImplicit && ((_ref = stackTop()) != null ? _ref[0] : void 0) === 'CONTROL'; 181 | }; 182 | startImplicitCall = function(j) { 183 | var idx; 184 | idx = j != null ? j : i; 185 | stack.push([ 186 | '(', idx, { 187 | ours: true 188 | } 189 | ]); 190 | tokens.splice(idx, 0, generate('CALL_START', '(')); 191 | if (j == null) { 192 | return i += 1; 193 | } 194 | }; 195 | endImplicitCall = function() { 196 | stack.pop(); 197 | tokens.splice(i, 0, generate('CALL_END', ')')); 198 | return i += 1; 199 | }; 200 | startImplicitObject = function(j, startsLine) { 201 | var idx; 202 | if (startsLine == null) { 203 | startsLine = true; 204 | } 205 | idx = j != null ? j : i; 206 | stack.push([ 207 | '{', idx, { 208 | sameLine: true, 209 | startsLine: startsLine, 210 | ours: true 211 | } 212 | ]); 213 | tokens.splice(idx, 0, generate('{', generate(new String('{')), token)); 214 | if (j == null) { 215 | return i += 1; 216 | } 217 | }; 218 | endImplicitObject = function(j) { 219 | j = j != null ? j : i; 220 | stack.pop(); 221 | tokens.splice(j, 0, generate('}', '}', token)); 222 | return i += 1; 223 | }; 224 | if (inImplicitCall() && (tag === 'IF' || tag === 'TRY' || tag === 'FINALLY' || tag === 'CATCH' || tag === 'CLASS' || tag === 'SWITCH')) { 225 | stack.push([ 226 | 'CONTROL', i, { 227 | ours: true 228 | } 229 | ]); 230 | return forward(1); 231 | } 232 | if (tag === 'INDENT' && inImplicit()) { 233 | if (prevTag !== '=>' && prevTag !== '->' && prevTag !== '[' && prevTag !== '(' && prevTag !== ',' && prevTag !== '{' && prevTag !== 'TRY' && prevTag !== 'ELSE' && prevTag !== '=') { 234 | while (inImplicitCall()) { 235 | endImplicitCall(); 236 | } 237 | } 238 | if (inImplicitControl()) { 239 | stack.pop(); 240 | } 241 | stack.push([tag, i]); 242 | return forward(1); 243 | } 244 | if (__indexOf.call(EXPRESSION_START, tag) >= 0) { 245 | stack.push([tag, i]); 246 | return forward(1); 247 | } 248 | if (__indexOf.call(EXPRESSION_END, tag) >= 0) { 249 | while (inImplicit()) { 250 | if (inImplicitCall()) { 251 | endImplicitCall(); 252 | } else if (inImplicitObject()) { 253 | endImplicitObject(); 254 | } else { 255 | stack.pop(); 256 | } 257 | } 258 | stack.pop(); 259 | } 260 | if ((__indexOf.call(IMPLICIT_FUNC, tag) >= 0 && token.spaced && !token.stringEnd || tag === '?' && i > 0 && !tokens[i - 1].spaced) && (__indexOf.call(IMPLICIT_CALL, nextTag) >= 0 || __indexOf.call(IMPLICIT_UNSPACED_CALL, nextTag) >= 0 && !((_ref = tokens[i + 1]) != null ? _ref.spaced : void 0) && !((_ref1 = tokens[i + 1]) != null ? _ref1.newLine : void 0))) { 261 | if (tag === '?') { 262 | tag = token[0] = 'FUNC_EXIST'; 263 | } 264 | startImplicitCall(i + 1); 265 | return forward(2); 266 | } 267 | if (__indexOf.call(IMPLICIT_FUNC, tag) >= 0 && this.matchTags(i + 1, 'INDENT', null, ':') && !this.findTagsBackwards(i, ['CLASS', 'EXTENDS', 'IF', 'CATCH', 'SWITCH', 'LEADING_WHEN', 'FOR', 'WHILE', 'UNTIL'])) { 268 | startImplicitCall(i + 1); 269 | stack.push(['INDENT', i + 2]); 270 | return forward(3); 271 | } 272 | if (tag === ':') { 273 | if (this.tag(i - 2) === '@') { 274 | s = i - 2; 275 | } else { 276 | s = i - 1; 277 | } 278 | while (this.tag(s - 2) === 'HERECOMMENT') { 279 | s -= 2; 280 | } 281 | this.insideForDeclaration = nextTag === 'FOR'; 282 | startsLine = s === 0 || (_ref2 = this.tag(s - 1), __indexOf.call(LINEBREAKS, _ref2) >= 0) || tokens[s - 1].newLine; 283 | if (stackTop()) { 284 | _ref3 = stackTop(), stackTag = _ref3[0], stackIdx = _ref3[1]; 285 | if ((stackTag === '{' || stackTag === 'INDENT' && this.tag(stackIdx - 1) === '{') && (startsLine || this.tag(s - 1) === ',' || this.tag(s - 1) === '{')) { 286 | return forward(1); 287 | } 288 | } 289 | startImplicitObject(s, !!startsLine); 290 | return forward(2); 291 | } 292 | if (inImplicitObject() && __indexOf.call(LINEBREAKS, tag) >= 0) { 293 | stackTop()[2].sameLine = false; 294 | } 295 | newLine = prevTag === 'OUTDENT' || prevToken.newLine; 296 | if (__indexOf.call(IMPLICIT_END, tag) >= 0 || __indexOf.call(CALL_CLOSERS, tag) >= 0 && newLine) { 297 | while (inImplicit()) { 298 | _ref4 = stackTop(), stackTag = _ref4[0], stackIdx = _ref4[1], (_ref5 = _ref4[2], sameLine = _ref5.sameLine, startsLine = _ref5.startsLine); 299 | if (inImplicitCall() && prevTag !== ',') { 300 | endImplicitCall(); 301 | } else if (inImplicitObject() && !this.insideForDeclaration && sameLine && tag !== 'TERMINATOR' && prevTag !== ':' && endImplicitObject()) { 302 | 303 | } else if (inImplicitObject() && tag === 'TERMINATOR' && prevTag !== ',' && !(startsLine && this.looksObjectish(i + 1))) { 304 | endImplicitObject(); 305 | } else { 306 | break; 307 | } 308 | } 309 | } 310 | if (tag === ',' && !this.looksObjectish(i + 1) && inImplicitObject() && !this.insideForDeclaration && (nextTag !== 'TERMINATOR' || !this.looksObjectish(i + 2))) { 311 | offset = nextTag === 'OUTDENT' ? 1 : 0; 312 | while (inImplicitObject()) { 313 | endImplicitObject(i + offset); 314 | } 315 | } 316 | return forward(1); 317 | }); 318 | }; 319 | 320 | Rewriter.prototype.addLocationDataToGeneratedTokens = function() { 321 | return this.scanTokens(function(token, i, tokens) { 322 | var column, line, nextLocation, prevLocation, _ref, _ref1; 323 | if (token[2]) { 324 | return 1; 325 | } 326 | if (!(token.generated || token.explicit)) { 327 | return 1; 328 | } 329 | if (token[0] === '{' && (nextLocation = (_ref = tokens[i + 1]) != null ? _ref[2] : void 0)) { 330 | line = nextLocation.first_line, column = nextLocation.first_column; 331 | } else if (prevLocation = (_ref1 = tokens[i - 1]) != null ? _ref1[2] : void 0) { 332 | line = prevLocation.last_line, column = prevLocation.last_column; 333 | } else { 334 | line = column = 0; 335 | } 336 | token[2] = { 337 | first_line: line, 338 | first_column: column, 339 | last_line: line, 340 | last_column: column 341 | }; 342 | return 1; 343 | }); 344 | }; 345 | 346 | Rewriter.prototype.normalizeLines = function() { 347 | var action, condition, indent, outdent, starter; 348 | starter = indent = outdent = null; 349 | condition = function(token, i) { 350 | var _ref, _ref1, _ref2, _ref3; 351 | return token[1] !== ';' && (_ref = token[0], __indexOf.call(SINGLE_CLOSERS, _ref) >= 0) && !(token[0] === 'TERMINATOR' && (_ref1 = this.tag(i + 1), __indexOf.call(EXPRESSION_CLOSE, _ref1) >= 0)) && !(token[0] === 'ELSE' && starter !== 'THEN') && !(((_ref2 = token[0]) === 'CATCH' || _ref2 === 'FINALLY') && (starter === '->' || starter === '=>')) || (_ref3 = token[0], __indexOf.call(CALL_CLOSERS, _ref3) >= 0) && this.tokens[i - 1].newLine; 352 | }; 353 | action = function(token, i) { 354 | return this.tokens.splice((this.tag(i - 1) === ',' ? i - 1 : i), 0, outdent); 355 | }; 356 | return this.scanTokens(function(token, i, tokens) { 357 | var j, tag, _i, _ref, _ref1, _ref2; 358 | tag = token[0]; 359 | if (tag === 'TERMINATOR') { 360 | if (this.tag(i + 1) === 'ELSE' && this.tag(i - 1) !== 'OUTDENT') { 361 | tokens.splice.apply(tokens, [i, 1].concat(__slice.call(this.indentation()))); 362 | return 1; 363 | } 364 | if (_ref = this.tag(i + 1), __indexOf.call(EXPRESSION_CLOSE, _ref) >= 0) { 365 | tokens.splice(i, 1); 366 | return 0; 367 | } 368 | } 369 | if (tag === 'CATCH') { 370 | for (j = _i = 1; _i <= 2; j = ++_i) { 371 | if (!((_ref1 = this.tag(i + j)) === 'OUTDENT' || _ref1 === 'TERMINATOR' || _ref1 === 'FINALLY')) { 372 | continue; 373 | } 374 | tokens.splice.apply(tokens, [i + j, 0].concat(__slice.call(this.indentation()))); 375 | return 2 + j; 376 | } 377 | } 378 | if (__indexOf.call(SINGLE_LINERS, tag) >= 0 && this.tag(i + 1) !== 'INDENT' && !(tag === 'ELSE' && this.tag(i + 1) === 'IF')) { 379 | starter = tag; 380 | _ref2 = this.indentation(tokens[i]), indent = _ref2[0], outdent = _ref2[1]; 381 | if (starter === 'THEN') { 382 | indent.fromThen = true; 383 | } 384 | tokens.splice(i + 1, 0, indent); 385 | this.detectEnd(i + 2, condition, action); 386 | if (tag === 'THEN') { 387 | tokens.splice(i, 1); 388 | } 389 | return 1; 390 | } 391 | return 1; 392 | }); 393 | }; 394 | 395 | Rewriter.prototype.tagPostfixConditionals = function() { 396 | var action, condition, original; 397 | original = null; 398 | condition = function(token, i) { 399 | var prevTag, tag; 400 | tag = token[0]; 401 | prevTag = this.tokens[i - 1][0]; 402 | return tag === 'TERMINATOR' || (tag === 'INDENT' && __indexOf.call(SINGLE_LINERS, prevTag) < 0); 403 | }; 404 | action = function(token, i) { 405 | if (token[0] !== 'INDENT' || (token.generated && !token.fromThen)) { 406 | return original[0] = 'POST_' + original[0]; 407 | } 408 | }; 409 | return this.scanTokens(function(token, i) { 410 | if (token[0] !== 'IF') { 411 | return 1; 412 | } 413 | original = token; 414 | this.detectEnd(i + 1, condition, action); 415 | return 1; 416 | }); 417 | }; 418 | 419 | Rewriter.prototype.indentation = function(origin) { 420 | var indent, outdent; 421 | indent = ['INDENT', 2]; 422 | outdent = ['OUTDENT', 2]; 423 | if (origin) { 424 | indent.generated = outdent.generated = true; 425 | indent.origin = outdent.origin = origin; 426 | } else { 427 | indent.explicit = outdent.explicit = true; 428 | } 429 | return [indent, outdent]; 430 | }; 431 | 432 | Rewriter.prototype.generate = generate; 433 | 434 | Rewriter.prototype.tag = function(i) { 435 | var _ref; 436 | return (_ref = this.tokens[i]) != null ? _ref[0] : void 0; 437 | }; 438 | 439 | return Rewriter; 440 | 441 | })(); 442 | 443 | BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], ['INDENT', 'OUTDENT'], ['CALL_START', 'CALL_END'], ['PARAM_START', 'PARAM_END'], ['INDEX_START', 'INDEX_END']]; 444 | 445 | exports.INVERSES = INVERSES = {}; 446 | 447 | EXPRESSION_START = []; 448 | 449 | EXPRESSION_END = []; 450 | 451 | for (_i = 0, _len = BALANCED_PAIRS.length; _i < _len; _i++) { 452 | _ref = BALANCED_PAIRS[_i], left = _ref[0], rite = _ref[1]; 453 | EXPRESSION_START.push(INVERSES[rite] = left); 454 | EXPRESSION_END.push(INVERSES[left] = rite); 455 | } 456 | 457 | EXPRESSION_CLOSE = ['CATCH', 'THEN', 'ELSE', 'FINALLY'].concat(EXPRESSION_END); 458 | 459 | IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS']; 460 | 461 | IMPLICIT_CALL = ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL', 'UNDEFINED', 'UNARY', 'YIELD', 'UNARY_MATH', 'SUPER', 'THROW', '@', '->', '=>', '[', '(', '{', '--', '++']; 462 | 463 | IMPLICIT_UNSPACED_CALL = ['+', '-']; 464 | 465 | IMPLICIT_END = ['POST_IF', 'FOR', 'WHILE', 'UNTIL', 'WHEN', 'BY', 'LOOP', 'TERMINATOR']; 466 | 467 | SINGLE_LINERS = ['ELSE', '->', '=>', 'TRY', 'FINALLY', 'THEN']; 468 | 469 | SINGLE_CLOSERS = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN']; 470 | 471 | LINEBREAKS = ['TERMINATOR', 'INDENT', 'OUTDENT']; 472 | 473 | CALL_CLOSERS = ['.', '?.', '::', '?::']; 474 | 475 | }).call(this); 476 | 477 | //# sourceMappingURL=rewriter.js.map 478 | -------------------------------------------------------------------------------- /test/fixtures/short/rewriter.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "file": "rewriter.js", 4 | "sourceRoot": "", 5 | "sources": [ 6 | "rewriter.coffee" 7 | ], 8 | "names": [], 9 | "mappings": "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" 10 | } -------------------------------------------------------------------------------- /test/fixtures/src/sprintf.js: -------------------------------------------------------------------------------- 1 | (function(window) { 2 | var re = { 3 | not_string: /[^s]/, 4 | number: /[diefg]/, 5 | json: /[j]/, 6 | not_json: /[^j]/, 7 | text: /^[^\x25]+/, 8 | modulo: /^\x25{2}/, 9 | placeholder: /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijosuxX])/, 10 | key: /^([a-z_][a-z_\d]*)/i, 11 | key_access: /^\.([a-z_][a-z_\d]*)/i, 12 | index_access: /^\[(\d+)\]/, 13 | sign: /^[\+\-]/ 14 | } 15 | 16 | function sprintf() { 17 | var key = arguments[0], cache = sprintf.cache 18 | if (!(cache[key] && cache.hasOwnProperty(key))) { 19 | cache[key] = sprintf.parse(key) 20 | } 21 | return sprintf.format.call(null, cache[key], arguments) 22 | } 23 | 24 | sprintf.format = function(parse_tree, argv) { 25 | var cursor = 1, tree_length = parse_tree.length, node_type = "", arg, output = [], i, k, match, pad, pad_character, pad_length, is_positive = true, sign = "" 26 | for (i = 0; i < tree_length; i++) { 27 | node_type = get_type(parse_tree[i]) 28 | if (node_type === "string") { 29 | output[output.length] = parse_tree[i] 30 | } 31 | else if (node_type === "array") { 32 | match = parse_tree[i] // convenience purposes only 33 | if (match[2]) { // keyword argument 34 | arg = argv[cursor] 35 | for (k = 0; k < match[2].length; k++) { 36 | if (!arg.hasOwnProperty(match[2][k])) { 37 | throw new Error(sprintf("[sprintf] property '%s' does not exist", match[2][k])) 38 | } 39 | arg = arg[match[2][k]] 40 | } 41 | } 42 | else if (match[1]) { // positional argument (explicit) 43 | arg = argv[match[1]] 44 | } 45 | else { // positional argument (implicit) 46 | arg = argv[cursor++] 47 | } 48 | 49 | if (get_type(arg) == "function") { 50 | arg = arg() 51 | } 52 | 53 | if (re.not_string.test(match[8]) && re.not_json.test(match[8]) && (get_type(arg) != "number" && isNaN(arg))) { 54 | throw new TypeError(sprintf("[sprintf] expecting number but found %s", get_type(arg))) 55 | } 56 | 57 | if (re.number.test(match[8])) { 58 | is_positive = arg >= 0 59 | } 60 | 61 | switch (match[8]) { 62 | case "b": 63 | arg = arg.toString(2) 64 | break 65 | case "c": 66 | arg = String.fromCharCode(arg) 67 | break 68 | case "d": 69 | case "i": 70 | arg = parseInt(arg, 10) 71 | break 72 | case "j": 73 | arg = JSON.stringify(arg, null, match[6] ? parseInt(match[6]) : 0) 74 | break 75 | case "e": 76 | arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential() 77 | break 78 | case "f": 79 | arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg) 80 | break 81 | case "g": 82 | arg = match[7] ? parseFloat(arg).toPrecision(match[7]) : parseFloat(arg) 83 | break 84 | case "o": 85 | arg = arg.toString(8) 86 | break 87 | case "s": 88 | arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg) 89 | break 90 | case "u": 91 | arg = arg >>> 0 92 | break 93 | case "x": 94 | arg = arg.toString(16) 95 | break 96 | case "X": 97 | arg = arg.toString(16).toUpperCase() 98 | break 99 | } 100 | if (re.json.test(match[8])) { 101 | output[output.length] = arg 102 | } 103 | else { 104 | if (re.number.test(match[8]) && (!is_positive || match[3])) { 105 | sign = is_positive ? "+" : "-" 106 | arg = arg.toString().replace(re.sign, "") 107 | } 108 | else { 109 | sign = "" 110 | } 111 | pad_character = match[4] ? match[4] === "0" ? "0" : match[4].charAt(1) : " " 112 | pad_length = match[6] - (sign + arg).length 113 | pad = match[6] ? (pad_length > 0 ? str_repeat(pad_character, pad_length) : "") : "" 114 | output[output.length] = match[5] ? sign + arg + pad : (pad_character === "0" ? sign + pad + arg : pad + sign + arg) 115 | } 116 | } 117 | } 118 | return output.join("") 119 | } 120 | 121 | sprintf.cache = {} 122 | 123 | sprintf.parse = function(fmt) { 124 | var _fmt = fmt, match = [], parse_tree = [], arg_names = 0 125 | while (_fmt) { 126 | if ((match = re.text.exec(_fmt)) !== null) { 127 | parse_tree[parse_tree.length] = match[0] 128 | } 129 | else if ((match = re.modulo.exec(_fmt)) !== null) { 130 | parse_tree[parse_tree.length] = "%" 131 | } 132 | else if ((match = re.placeholder.exec(_fmt)) !== null) { 133 | if (match[2]) { 134 | arg_names |= 1 135 | var field_list = [], replacement_field = match[2], field_match = [] 136 | if ((field_match = re.key.exec(replacement_field)) !== null) { 137 | field_list[field_list.length] = field_match[1] 138 | while ((replacement_field = replacement_field.substring(field_match[0].length)) !== "") { 139 | if ((field_match = re.key_access.exec(replacement_field)) !== null) { 140 | field_list[field_list.length] = field_match[1] 141 | } 142 | else if ((field_match = re.index_access.exec(replacement_field)) !== null) { 143 | field_list[field_list.length] = field_match[1] 144 | } 145 | else { 146 | throw new SyntaxError("[sprintf] failed to parse named argument key") 147 | } 148 | } 149 | } 150 | else { 151 | throw new SyntaxError("[sprintf] failed to parse named argument key") 152 | } 153 | match[2] = field_list 154 | } 155 | else { 156 | arg_names |= 2 157 | } 158 | if (arg_names === 3) { 159 | throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported") 160 | } 161 | parse_tree[parse_tree.length] = match 162 | } 163 | else { 164 | throw new SyntaxError("[sprintf] unexpected placeholder") 165 | } 166 | _fmt = _fmt.substring(match[0].length) 167 | } 168 | return parse_tree 169 | } 170 | 171 | var vsprintf = function(fmt, argv, _argv) { 172 | _argv = (argv || []).slice(0) 173 | _argv.splice(0, 0, fmt) 174 | return sprintf.apply(null, _argv) 175 | } 176 | 177 | /** 178 | * helpers 179 | */ 180 | function get_type(variable) { 181 | return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase() 182 | } 183 | 184 | function str_repeat(input, multiplier) { 185 | return Array(multiplier + 1).join(input) 186 | } 187 | 188 | /** 189 | * export to either browser or node.js 190 | */ 191 | if (typeof exports !== "undefined") { 192 | exports.sprintf = sprintf 193 | exports.vsprintf = vsprintf 194 | } 195 | else { 196 | window.sprintf = sprintf 197 | window.vsprintf = vsprintf 198 | 199 | if (typeof define === "function" && define.amd) { 200 | define(function() { 201 | return { 202 | sprintf: sprintf, 203 | vsprintf: vsprintf 204 | } 205 | }) 206 | } 207 | } 208 | })(typeof window === "undefined" ? this : window); 209 | -------------------------------------------------------------------------------- /test/fixtures/typescript/1/hello-world.js: -------------------------------------------------------------------------------- 1 | export function helloWorld(string) { 2 | console.log(string); 3 | } 4 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGVsbG8td29ybGQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJoZWxsby13b3JsZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwyQkFBMkIsTUFBYztJQUV2QyxPQUFPLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0FBQ3RCLENBQUMifQ== -------------------------------------------------------------------------------- /test/fixtures/typescript/1/hello-world.ts: -------------------------------------------------------------------------------- 1 | export function helloWorld(string: string) { 2 | 3 | console.log(string); 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/typescript/2/hello-world.js: -------------------------------------------------------------------------------- 1 | export function helloWorld(string) { 2 | console.log(string); 3 | } 4 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGVsbG8td29ybGQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJoZWxsby13b3JsZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwyQkFBMkIsTUFBYztJQUN2QyxPQUFPLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0FBQ3RCLENBQUMifQ== -------------------------------------------------------------------------------- /test/fixtures/typescript/2/hello-world.ts: -------------------------------------------------------------------------------- 1 | export function helloWorld(string: string) { 2 | console.log(string); 3 | } 4 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | If you visit the page after running the test suite, you can verify a 2 | resulting sourcemap by clicking these buttons. Each one throws an 3 | exception from sourcemapped code. 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const chaiFiles = require('chai-files'); 5 | chai.use(chaiFiles); 6 | 7 | const assert = chai.assert; 8 | const expect = chai.expect; 9 | const file = chaiFiles.file; 10 | 11 | const SourceMap = require('..'); 12 | const mkdirp = require('mkdirp'); 13 | const fs = require('fs'); 14 | const path = require('path'); 15 | const rimraf = require('rimraf'); 16 | const sinon = require('sinon'); 17 | const EOL = require('os').EOL; 18 | const FSMerger = require('fs-merger'); 19 | 20 | function createFS(rootPath = './') { 21 | return new FSMerger(rootPath).fs; 22 | } 23 | 24 | describe('fast sourcemap concat', function() { 25 | let initialCwd; 26 | 27 | beforeEach(function() { 28 | initialCwd = process.cwd(); 29 | process.chdir(__dirname); 30 | mkdirp('tmp'); 31 | }); 32 | afterEach(function() { 33 | rimraf.sync('tmp'); 34 | process.chdir(initialCwd); 35 | }); 36 | 37 | it('should pass basic smoke test', function() { 38 | let s = new SourceMap({outputFile: 'tmp/intermediate.js'}); 39 | s.addFile('fixtures/inner/first.js'); 40 | let filler = "'x';"; 41 | s.addSpace(filler); 42 | s.addFile('fixtures/inner/second.js'); 43 | 44 | return s.end().then(function(){ 45 | s = new SourceMap({outputFile: 'tmp/intermediate2.js'}); 46 | s.addFile('fixtures/other/fourth.js'); 47 | return s.end(); 48 | }).then(function(){ 49 | s = new SourceMap({outputFile: 'tmp/final.js'}); 50 | s.addFile('tmp/intermediate.js'); 51 | s.addFile('fixtures/other/third.js'); 52 | s.addFile('tmp/intermediate2.js'); 53 | return s.end(); 54 | }).then(function(){ 55 | expectFile('final.js').in('tmp'); 56 | expectFile('final.map').in('tmp'); 57 | }); 58 | }); 59 | 60 | it("should support file-less concatenation", function() { 61 | let s = new SourceMap({file: 'from-inline.js', mapURL: 'from-inline.map'}); 62 | s.addFile('fixtures/other/third.js'); 63 | s.addSpace("/* My First Separator */"); 64 | s.addFile('fixtures/inline-mapped.js'); 65 | s.addSpace("/* My Second */"); 66 | s.addFile('fixtures/other/fourth.js'); 67 | return s.end().then(function(r){ 68 | expect(r).to.be.a('object'); 69 | expect(r.map).to.be.a('object'); 70 | expectFile('from-inline.js', r.code || "empty").in('tmp'); 71 | expectFile('from-inline.map', JSON.stringify(r.map) || "empty").in('tmp'); 72 | }); 73 | }); 74 | 75 | it("should accept inline sourcemaps", function() { 76 | let s = new SourceMap({outputFile: 'tmp/from-inline.js'}); 77 | s.addFile('fixtures/other/third.js'); 78 | s.addSpace("/* My First Separator */"); 79 | s.addFile('fixtures/inline-mapped.js'); 80 | s.addSpace("/* My Second */"); 81 | s.addFile('fixtures/other/fourth.js'); 82 | return s.end().then(function(){ 83 | expectFile('from-inline.js').in('tmp'); 84 | expectFile('from-inline.map').in('tmp'); 85 | }); 86 | }); 87 | 88 | it("should allow adding file contents from string", function() { 89 | let filePath = 'fixtures/other/third.js'; 90 | let contents = fs.readFileSync(filePath, { encoding: 'utf8' }); 91 | 92 | let s = new SourceMap({outputFile: 'tmp/from-inline.js'}); 93 | s.addFileSource('fixtures/other/third.js', contents); 94 | s.addSpace("/* My First Separator */"); 95 | s.addFile('fixtures/inline-mapped.js'); 96 | s.addSpace("/* My Second */"); 97 | s.addFile('fixtures/other/fourth.js'); 98 | 99 | return s.end().then(function(){ 100 | expectFile('from-inline.js').in('tmp'); 101 | expectFile('from-inline.map').in('tmp'); 102 | }); 103 | }); 104 | 105 | it("should allow adding map contents from string", function() { 106 | let filePath = 'fixtures/from-string/external-mapped.js'; 107 | let contents = fs.readFileSync(filePath, { encoding: 'utf8' }); 108 | let map = fs.readFileSync(filePath+'-map.map', { encoding: 'utf8' }); 109 | 110 | let s = new SourceMap({outputFile: 'tmp/from-string.js'}); 111 | s.addFile('fixtures/other/third.js'); 112 | s.addSpace("/* My First Separator */"); 113 | s.addFileSource('fixtures/external-mapped.js', contents, map); 114 | s.addSpace("/* My Second */"); 115 | s.addFile('fixtures/other/fourth.js'); 116 | 117 | return s.end().then(function(){ 118 | expectFile('from-string.js').in('tmp'); 119 | expectFile('from-string.map').in('tmp'); 120 | }); 121 | }); 122 | 123 | it("should correctly concatenate a sourcemapped coffeescript example", function() { 124 | let s = new SourceMap({outputFile: 'tmp/coffee-example.js'}); 125 | s.addFile('fixtures/coffee/aa-loader.js'); 126 | s.addFile('fixtures/coffee/rewriter.js'); 127 | s.addSpace("/* My First Separator */"); 128 | s.addFile('fixtures/other/third.js'); 129 | return s.end().then(function(){ 130 | expectFile('coffee-example.js').in('tmp'); 131 | expectFile('coffee-example.map').in('tmp'); 132 | }); 133 | }); 134 | 135 | it("should discover external sources", function() { 136 | let s = new SourceMap({outputFile: 'tmp/external-content.js', baseDir: path.join(__dirname, 'fixtures')}); 137 | s.addFile('other/third.js'); 138 | s.addSpace("/* My First Separator */"); 139 | s.addFile('external-content/all-inner.js'); 140 | s.addSpace("/* My Second */"); 141 | s.addFile('other/fourth.js'); 142 | return s.end().then(function(){ 143 | expectFile('external-content.js').in('tmp'); 144 | expectFile('external-content.map').in('tmp'); 145 | }); 146 | }); 147 | 148 | it("should populate cache", function() { 149 | let cache = {}; 150 | let s = new SourceMap({outputFile: 'tmp/external-content.js', baseDir: path.join(__dirname, 'fixtures'), cache: cache}); 151 | s.addFile('other/third.js'); 152 | s.addSpace("/* My First Separator */"); 153 | s.addFile('external-content/all-inner.js'); 154 | s.addSpace("/* My Second */"); 155 | s.addFile('other/fourth.js'); 156 | return s.end().then(function(){ 157 | expectFile('external-content.js').in('tmp'); 158 | expectFile('external-content.map').in('tmp'); 159 | assert.deepEqual(cache, { 160 | "b02a65b427e623a118a1d7ee09aeecbd": { encoder: "AEAAA", lines: 11 } 161 | }); 162 | }); 163 | }); 164 | 165 | it("should use cache", function() { 166 | let cache = {}; 167 | 168 | function once(finalFile){ 169 | let s = new SourceMap({cache: cache, outputFile: 'tmp/intermediate.js'}); 170 | s.addFile('fixtures/inner/first.js'); 171 | let filler = "'x';"; 172 | s.addSpace(filler); 173 | s.addFile('fixtures/inner/second.js'); 174 | 175 | return s.end().then(function(){ 176 | s = new SourceMap({cache: cache, outputFile: 'tmp/intermediate2.js'}); 177 | s.addFile('fixtures/other/fourth.js'); 178 | return s.end(); 179 | }).then(function(){ 180 | s = new SourceMap({cache: cache, outputFile: 'tmp/' + finalFile}); 181 | sinon.spy(s, '_scanMappings'); 182 | s.addFile('tmp/intermediate.js'); 183 | s.addFile('fixtures/other/third.js'); 184 | s.addFile('tmp/intermediate2.js'); 185 | return s.end().then(function(){ 186 | return s._scanMappings; 187 | }); 188 | }); 189 | } 190 | 191 | return once('firstPass.js').then(function(){ 192 | return once('final.js'); 193 | }).then(function(spy){ 194 | expectFile('final.js').in('tmp'); 195 | expectFile('final.map').in('tmp'); 196 | expect(spy.getCall(0).args[3], 'should receive cacheHint').to.be.ok; 197 | expect(spy.getCall(1).args[3], 'should receive cacheHint').to.be.ok; 198 | }); 199 | }); 200 | 201 | it("supports mapFile & mapURL", function() { 202 | let s = new SourceMap({mapFile: 'tmp/maps/custom.map', mapURL: '/maps/custom.map', outputFile: 'tmp/assets/mapdird.js'}); 203 | s.addFile('fixtures/inner/first.js'); 204 | return s.end().then(function(){ 205 | expectFile('mapdird.js').in('tmp/assets'); 206 | expectFile('custom.map').in('tmp/maps'); 207 | s = new SourceMap({mapFile: 'tmp/maps/custom2.map', mapURL: '/maps/custom2.map', outputFile: 'tmp/assets/mapdird2.js', baseDir: path.resolve('tmp')}); 208 | s.addFile('assets/mapdird.js'); 209 | return s.end(); 210 | }).then(function(){ 211 | expectFile('mapdird2.js').in('tmp/assets'); 212 | expectFile('custom2.map').in('tmp/maps'); 213 | }); 214 | }); 215 | 216 | it("outputs block comments when 'mapCommentType' is 'block'", function() { 217 | let FILE = 'tmp/mapcommenttype.css'; 218 | let s = new SourceMap({outputFile: FILE, mapCommentType: 'block'}); 219 | return s.end().then(function() { 220 | let result = fs.readFileSync(FILE, 'utf-8'); 221 | let expected = "/*# sourceMappingURL=mapcommenttype.css.map */\n"; 222 | assert.equal(result, expected); 223 | }); 224 | }); 225 | 226 | it("outputs no comments when 'mapCommentType' is 'none'", function() { 227 | let FILE = 'tmp/mapcommenttype.css'; 228 | let s = new SourceMap({outputFile: FILE, mapCommentType: 'none'}); 229 | return s.end().then(function() { 230 | let result = fs.readFileSync(FILE, 'utf-8'); 231 | let expected = ""; 232 | assert.equal(result, expected); 233 | }); 234 | }); 235 | 236 | it("should warn but tolerate broken sourcemap URL", function() { 237 | let s = new SourceMap({outputFile: 'tmp/with-broken-input-map.js', baseDir: path.join(__dirname, 'fixtures')}); 238 | s._warn = sinon.spy(); 239 | s.addFile('other/third.js'); 240 | s.addSpace("/* My First Separator */"); 241 | s.addFile('external-content/broken-link.js'); 242 | s.addSpace("/* My Second */"); 243 | s.addFile('other/fourth.js'); 244 | return s.end().then(function(){ 245 | expectFile('with-broken-input-map.js').in('tmp'); 246 | expectFile('with-broken-input-map.map').in('tmp'); 247 | assert(s._warn.called, 'generates warning'); 248 | }); 249 | }); 250 | 251 | it("corrects upstream sourcemap that is too short", function() { 252 | let s = new SourceMap({outputFile: 'tmp/test-short.js'}); 253 | s.addFile('fixtures/other/third.js'); 254 | s.addFile('fixtures/short/rewriter.js'); 255 | s.addFile('fixtures/other/fourth.js'); 256 | return s.end().then(function(){ 257 | expectFile('test-short.js').in('tmp'); 258 | expectFile('test-short.map').in('tmp'); 259 | }); 260 | }); 261 | 262 | it("corrects upstream sourcemap that is too short, on cached second build", function() { 263 | let cache = {}; 264 | function once() { 265 | let s = new SourceMap({cache: cache, outputFile: 'tmp/test-short.js'}); 266 | s.addFile('fixtures/other/third.js'); 267 | s.addFile('fixtures/short/rewriter.js'); 268 | s.addFile('fixtures/other/fourth.js'); 269 | return s.end(); 270 | } 271 | return once().then(once).then(function(){ 272 | expectFile('test-short.js').in('tmp'); 273 | expectFile('test-short.map').in('tmp'); 274 | }); 275 | }); 276 | 277 | it("deals with missing newline followed by single newline", function() { 278 | let s = new SourceMap({outputFile: 'tmp/iife-wrapping.js'}); 279 | s.addFile('fixtures/other/fourth.js'); 280 | s.addSpace('\n'); 281 | s.addFile('fixtures/iife-wrapping/iife-start'); 282 | s.addSpace('\n'); 283 | s.addFile('fixtures/other/third.js'); 284 | s.addSpace('\n'); 285 | s.addFile('fixtures/iife-wrapping/iife-end'); 286 | 287 | return s.end().then(function(){ 288 | expectFile('iife-wrapping.js').in('tmp'); 289 | expectFile('iife-wrapping.map').in('tmp'); 290 | }); 291 | }); 292 | 293 | it("should tolerate sourceMaps that do not specify sourcesContent", function() { 294 | let s = new SourceMap({outputFile: 'tmp/no-sources-content-out.js'}); 295 | s.addFile('fixtures/other/fourth.js'); 296 | s.addFile('fixtures/emptyish/src/b.js'); 297 | s.addFile('fixtures/other/third.js'); 298 | return s.end().then(function(){ 299 | expectSourcemap('no-sources-content-out.js', 'no-sources-content-out.map').in('tmp'); 300 | }); 301 | }); 302 | 303 | it("should update when input source code is stable but sourcemap has changed", function() { 304 | // This case occurs when the user makes non-semantic changes to 305 | // their original source code, which therefore gets preprocessed 306 | // into identical output that has a different sourceMap. 307 | 308 | let cache = {}; 309 | 310 | function runOnce() { 311 | let s = new SourceMap({outputFile: 'tmp/hello-world-output.js', cache: cache}); 312 | s.addFile('fixtures/inner/first.js'); 313 | s.addFile('tmp/hello-world.js'); 314 | s.addFile('fixtures/inner/second.js'); 315 | return s.end(); 316 | } 317 | 318 | copySync('fixtures/typescript/1/hello-world.js', 'tmp/hello-world.js'); 319 | copySync('fixtures/typescript/1/hello-world.ts', 'tmp/hello-world.ts'); 320 | return runOnce().then(function(){ 321 | expectFile('hello-world-output.js').in('tmp'); 322 | copySync('tmp/hello-world-output.map', 'tmp/hello-world-output-1.map'); 323 | 324 | expectSourcemap('hello-world-output.js', 'hello-world-output-1.map').in('tmp'); 325 | 326 | copySync('fixtures/typescript/2/hello-world.js', 'tmp/hello-world.js'); 327 | copySync('fixtures/typescript/2/hello-world.ts', 'tmp/hello-world.ts'); 328 | return runOnce(); 329 | }).then(function() { 330 | expectFile('hello-world-output.js').in('tmp'); 331 | copySync('tmp/hello-world-output.map', 'tmp/hello-world-output-2.map'); 332 | expectSourcemap('hello-world-output.js', 'hello-world-output-2.map').in('tmp'); 333 | }); 334 | }); 335 | 336 | it('should write data URLs when requested via mapStyle', function () { 337 | const s = new SourceMap({ 338 | mapStyle: 'data', 339 | outputFile: 'tmp/map-style-data-test.js' 340 | }); 341 | 342 | s.addFile('fixtures/inner/first.js'); 343 | s.addFile('fixtures/inner/second.js'); 344 | 345 | return s.end().then(function () { 346 | expect(file('tmp/map-style-data-test.js')).to.exist; 347 | expect(file('tmp/map-style-data-test.map')).to.not.exist; 348 | expect(file('tmp/map-style-data-test.js')).to.contain('sourceMappingURL=data:application'); 349 | expect(file('tmp/map-style-data-test.js')).to.not.contain('sourceMappingURL=map-style-data-test.map'); 350 | }); 351 | }); 352 | 353 | it('should not allow mapStyle to be used with custom mapFiles', function () { 354 | const badUsage = function () { 355 | new SourceMap({ 356 | mapStyle: 'data', 357 | mapFile: 'foo.map', 358 | mapURL: 'foo.map', 359 | outputFile: 'tmp/map-style-throw-test.js' 360 | }); 361 | } 362 | expect(badUsage).to.throw('mapStyle'); 363 | }); 364 | 365 | describe('with custom fs', function() { 366 | it('should pass basic smoke test', function() { 367 | let s = new SourceMap({outputFile: 'tmp/intermediate.js', fs:createFS()}); 368 | s.addFile('fixtures/inner/first.js'); 369 | let filler = "'x';"; 370 | s.addSpace(filler); 371 | s.addFile('fixtures/inner/second.js'); 372 | 373 | return s.end().then(function(){ 374 | s = new SourceMap({outputFile: 'tmp/intermediate2.js', fs:createFS()}); 375 | s.addFile('fixtures/other/fourth.js'); 376 | return s.end(); 377 | }).then(function(){ 378 | s = new SourceMap({outputFile: 'tmp/final.js', fs:createFS()}); 379 | s.addFile('tmp/intermediate.js'); 380 | s.addFile('fixtures/other/third.js'); 381 | s.addFile('tmp/intermediate2.js'); 382 | return s.end(); 383 | }).then(function(){ 384 | expectFile('final.js').in('tmp'); 385 | expectFile('final.map').in('tmp'); 386 | }); 387 | }); 388 | }); 389 | 390 | describe('CONCAT_STATS', function() { 391 | let outputs; 392 | let concat; 393 | 394 | beforeEach(function() { 395 | process.env.CONCAT_STATS = true; 396 | outputs = []; 397 | 398 | concat = new SourceMap({ 399 | outputFile: 'tmp/hello-world-output.js', 400 | cache: {} 401 | }); 402 | 403 | concat.writeConcatStatsSync = function(outputPath, content) { 404 | outputs.push({ 405 | outputPath: outputPath, 406 | content: content 407 | }); 408 | }; 409 | }); 410 | 411 | afterEach(function() { 412 | delete process.env.CONCAT_STATS; 413 | delete process.env.CONCAT_STATS_PATH; 414 | }); 415 | 416 | let runEmitTest = (outputPath) => { 417 | concat.addFile('fixtures/inner/second.js'); 418 | concat.addFile('fixtures/inner/first.js'); 419 | 420 | expect(outputs.length).to.eql(0); 421 | 422 | return concat.end().then(function() { 423 | expect(outputs.length).to.eql(1); 424 | 425 | expect(outputs[0].outputPath).to.eql(outputPath); 426 | expect(outputs[0].content).to.eql({ 427 | outputFile: concat.outputFile, 428 | sizes: { 429 | 'fixtures/inner/first.js': 100, 430 | 'fixtures/inner/second.js': 66 431 | } 432 | }); 433 | }); 434 | }; 435 | 436 | it('correctly emits file for given concat with default path', function() { 437 | return runEmitTest(`${process.cwd()}/concat-stats-for/${concat.id}-${path.basename(concat.outputFile)}.json`); 438 | }); 439 | 440 | it('correctly emits file for given concat with path from env', function() { 441 | let statsPath = '/tmp/concat-stats-for'; 442 | process.env.CONCAT_STATS_PATH = statsPath; 443 | return runEmitTest(`${statsPath}/${concat.id}-${path.basename(concat.outputFile)}.json`); 444 | }); 445 | 446 | it('correctly DOES NOT emits file for given concat, if the flag is not set', function() { 447 | delete process.env.CONCAT_STATS; 448 | 449 | concat.addFile('fixtures/inner/second.js'); 450 | concat.addFile('fixtures/inner/first.js'); 451 | 452 | expect(outputs.length).to.eql(0); 453 | 454 | return concat.end().then(function() { 455 | expect(outputs.length).to.eql(0); 456 | }); 457 | }) 458 | }); 459 | }); 460 | 461 | function expectFile(filename, actualContent) { 462 | let stripURL = false; 463 | return { 464 | in: function(dir) { 465 | actualContent = actualContent || ensurePosix(fs.readFileSync(path.join(dir, filename), 'utf-8')); 466 | fs.writeFileSync(path.join(__dirname, 'actual', filename), actualContent); 467 | 468 | let expectedContent; 469 | try { 470 | expectedContent = ensurePosix(fs.readFileSync(path.join(__dirname, 'expected', filename), 'utf-8')); 471 | if (stripURL) { 472 | expectedContent = expectedContent.replace(/\/\/# sourceMappingURL=.*$/, ''); 473 | } 474 | 475 | } catch (err) { 476 | console.warn("Missing expcted file: " + path.join(__dirname, 'expected', filename)); 477 | } 478 | expect(actualContent).equals(expectedContent, "discrepancy in " + filename); 479 | return this; 480 | } 481 | }; 482 | } 483 | 484 | function expectSourcemap(jsFilename, mapFilename) { 485 | return { 486 | in: function (result, subdir) { 487 | if (!subdir) { 488 | subdir = '.'; 489 | } 490 | 491 | if (!mapFilename) { 492 | mapFilename = jsFilename.replace(/\.js$/, '.map'); 493 | } 494 | 495 | expectFile(jsFilename).in(result, subdir); 496 | expectFile(mapFilename).in(result, subdir); 497 | } 498 | } 499 | } 500 | function copySync(src, dest) { 501 | fs.writeFileSync(dest, fs.readFileSync(src)); 502 | } 503 | 504 | function ensurePosix(string) { 505 | if (EOL !== '\n') { 506 | string = string.split(EOL).join('\n'); 507 | } 508 | return string; 509 | } 510 | -------------------------------------------------------------------------------- /tools/dissect.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var srcURL = require("source-map-url"); 4 | var SourceMap = require("../lib/source-map"); 5 | 6 | if (process.argv.length < 3) { 7 | process.stderr.write("Usage: dissect.js []\n"); 8 | process.exit(-1); 9 | } 10 | 11 | var fs = require("fs"); 12 | var path = require("path"); 13 | var src = fs.readFileSync(process.argv[2], "utf-8"); 14 | var map; 15 | 16 | if (srcURL.existsIn(src) && process.argv.length < 4) { 17 | var url = srcURL.getFrom(src); 18 | src = srcURL.removeFrom(src); 19 | map = SourceMap.prototype._resolveSourcemap(process.argv[2], url); 20 | } else { 21 | map = JSON.parse(fs.readFileSync(process.argv[3], "utf-8")); 22 | } 23 | var Coder = require("../lib/coder"); 24 | var colWidth = 60; 25 | var newline = /\n\r?/; 26 | 27 | var lines = src.split(newline); 28 | var mappings = map.mappings.split(";"); 29 | var splitContents; 30 | if (map.sourcesContent) { 31 | splitContents = map.sourcesContent.map(function (src) { 32 | return src ? src.split(newline) : []; 33 | }); 34 | } else { 35 | splitContents = map.sources.map(function (name) { 36 | try { 37 | return fs 38 | .readFileSync(path.resolve(process.argv[2], name), "utf-8") 39 | .split(newline); 40 | } catch (err) { 41 | return `failed to load ${path.resolve(process.argv[2], name)}`; 42 | } 43 | }); 44 | } 45 | var decoder = new Coder(); 46 | 47 | function padUpTo(str, padding) { 48 | var extra = padding - str.length; 49 | while (extra > 0) { 50 | extra--; 51 | str += " "; 52 | } 53 | if (str.length > padding) { 54 | str = str.slice(0, padding - 3) + "..."; 55 | } 56 | return str; 57 | } 58 | 59 | var lastOrigLine; 60 | var value; 61 | function decode(mapping) { 62 | value = decoder.decode(mapping); 63 | } 64 | 65 | for (var i = 0; i < lines.length; i++) { 66 | if (i >= mappings.length) { 67 | console.log("Ran out of mappings"); 68 | process.exit(-1); 69 | } 70 | 71 | decoder.resetColumn(); 72 | mappings[i].split(",").forEach(decode); 73 | 74 | var differential = mappings[i] 75 | .split(",") 76 | .map(function (elt) { 77 | return decoder.debug(elt); 78 | }) 79 | .join("|"); 80 | 81 | var fileDesc = ""; 82 | var origLine = ""; 83 | 84 | if (value.hasOwnProperty("source")) { 85 | fileDesc += map.sources[value.source]; 86 | } 87 | if (value.hasOwnProperty("originalLine")) { 88 | fileDesc += ":" + value.originalLine; 89 | var whichSource = splitContents[value.source]; 90 | if (whichSource) { 91 | origLine = whichSource[value.originalLine]; 92 | } 93 | if (typeof origLine === "undefined") { 94 | origLine = 95 | ""; 96 | } 97 | } 98 | 99 | console.log( 100 | [ 101 | padUpTo(differential, colWidth), 102 | padUpTo(fileDesc, colWidth), 103 | padUpTo(origLine === lastOrigLine ? "" : origLine, colWidth), 104 | " | ", 105 | padUpTo(lines[i], colWidth), 106 | ].join("") 107 | ); 108 | lastOrigLine = origLine; 109 | if (i % 20 === 0) { 110 | var sep = ""; 111 | for (var col = 0; col < colWidth * 4; col++) { 112 | sep += "-"; 113 | } 114 | console.log(sep); 115 | } 116 | } 117 | --------------------------------------------------------------------------------