├── .editorconfig ├── .eslintrc ├── .gitignore ├── .gitlab-ci.yml ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── appveyor.yml ├── bin ├── buble ├── handleError.js ├── help.md ├── runBuble.js └── showHelp.js ├── bublé.gif ├── package-lock.json ├── package.json ├── register.js ├── rollup.config.js ├── src ├── index.js ├── program │ ├── BlockStatement.js │ ├── Node.js │ ├── Program.js │ ├── Scope.js │ ├── extractNames.js │ ├── keys.js │ ├── types │ │ ├── ArrayExpression.js │ │ ├── ArrowFunctionExpression.js │ │ ├── AssignmentExpression.js │ │ ├── BinaryExpression.js │ │ ├── BreakStatement.js │ │ ├── CallExpression.js │ │ ├── ClassBody.js │ │ ├── ClassDeclaration.js │ │ ├── ClassExpression.js │ │ ├── ContinueStatement.js │ │ ├── ExportDefaultDeclaration.js │ │ ├── ExportNamedDeclaration.js │ │ ├── ForInStatement.js │ │ ├── ForOfStatement.js │ │ ├── ForStatement.js │ │ ├── FunctionDeclaration.js │ │ ├── FunctionExpression.js │ │ ├── Identifier.js │ │ ├── IfStatement.js │ │ ├── ImportDeclaration.js │ │ ├── ImportDefaultSpecifier.js │ │ ├── ImportSpecifier.js │ │ ├── JSXAttribute.js │ │ ├── JSXClosingElement.js │ │ ├── JSXClosingFragment.js │ │ ├── JSXElement.js │ │ ├── JSXExpressionContainer.js │ │ ├── JSXFragment.js │ │ ├── JSXOpeningElement.js │ │ ├── JSXOpeningFragment.js │ │ ├── JSXSpreadAttribute.js │ │ ├── Literal.js │ │ ├── MemberExpression.js │ │ ├── NewExpression.js │ │ ├── ObjectExpression.js │ │ ├── Property.js │ │ ├── ReturnStatement.js │ │ ├── SpreadElement.js │ │ ├── Super.js │ │ ├── TaggedTemplateExpression.js │ │ ├── TemplateElement.js │ │ ├── TemplateLiteral.js │ │ ├── ThisExpression.js │ │ ├── UpdateExpression.js │ │ ├── VariableDeclaration.js │ │ ├── VariableDeclarator.js │ │ ├── WithStatement.js │ │ ├── index.js │ │ └── shared │ │ │ ├── LoopStatement.js │ │ │ └── ModuleDeclaration.js │ └── wrap.js ├── support.js └── utils │ ├── CompileError.js │ ├── array.js │ ├── checkConst.js │ ├── deindent.js │ ├── destructure.js │ ├── getSnippet.js │ ├── globals.js │ ├── isReference.js │ ├── locate.js │ ├── patterns.js │ ├── prependVm.js │ ├── removeTrailingComma.js │ ├── reserved.js │ └── spread.js └── test ├── cli ├── basic │ ├── command.sh │ ├── expected │ │ └── output.js │ └── input.js ├── compiles-directory │ ├── command.sh │ ├── expected │ │ ├── bar.js │ │ ├── bar.js.map │ │ ├── baz.js │ │ ├── baz.js.map │ │ ├── foo.js │ │ └── foo.js.map │ └── src │ │ ├── bar.jsm │ │ ├── baz.es6 │ │ ├── foo.js │ │ └── nope.txt ├── creates-inline-sourcemap │ ├── command.sh │ ├── expected │ │ └── output.js │ └── input.js ├── creates-sourcemap │ ├── command.sh │ ├── expected │ │ ├── output.js │ │ └── output.js.map │ └── input.js ├── supports-jsx-pragma-comment │ ├── command.sh │ ├── expected │ │ └── output.js │ └── input.js ├── supports-jsx-pragma │ ├── command.sh │ ├── expected │ │ └── output.js │ └── input.js ├── supports-jsx │ ├── command.sh │ ├── expected │ │ └── output.js │ └── input.jsx ├── uses-overrides │ ├── command.sh │ ├── expected │ │ └── output.js │ └── input.js ├── uses-targets │ ├── command.sh │ ├── expected │ │ └── output.js │ └── input.js └── writes-to-stdout │ ├── command.sh │ ├── expected │ └── output.js │ └── input.js ├── samples ├── arrow-functions.js ├── async.js ├── binary-and-octal.js ├── block-scoping.js ├── classes-no-named-function-expressions.js ├── classes.js ├── computed-properties.js ├── default-parameters.js ├── destructuring.js ├── dynamic-import.js ├── exponentiation-operator.js ├── for-of.js ├── generators.js ├── jsx.js ├── loops.js ├── misc.js ├── modules.js ├── object-properties-no-named-function-expressions.js ├── object-properties.js ├── object-rest-spread.js ├── regex.js ├── reserved-properties.js ├── rest-parameters.js ├── spread-operator.js ├── strip-with.js ├── template-strings.js └── trailing-function-commas.js ├── test.js └── utils └── getLocation.js /.editorconfig: -------------------------------------------------------------------------------- 1 | [**.js] 2 | indent_style = tab 3 | indent_size = 2 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "indent": [ 2, "tab", { "SwitchCase": 1 } ], 4 | "linebreak-style": [ 2, "unix" ], 5 | "semi": [ 2, "always" ], 6 | "keyword-spacing": [ 2, { "before": true, "after": true } ], 7 | "space-before-blocks": [ 2, "always" ], 8 | "no-mixed-spaces-and-tabs": [ 2, "smart-tabs" ], 9 | "no-cond-assign": [ 0 ] 10 | }, 11 | "env": { 12 | "es6": true, 13 | "browser": true, 14 | "mocha": true, 15 | "node": true 16 | }, 17 | "extends": "eslint:recommended", 18 | "parserOptions": { 19 | "ecmaVersion": 6, 20 | "sourceType": "module" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | test/**/actual 5 | sandbox 6 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | before_script: 2 | - npm install 3 | 4 | test:0.12: 5 | image: node:0.12 6 | script: 7 | - npm test 8 | 9 | test:latest: 10 | image: node:latest 11 | script: 12 | - npm test 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "stable" 4 | 5 | env: 6 | global: 7 | - BUILD_TIMEOUT=10000 8 | 9 | install: 10 | - npm install -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Rich Harris and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bublé 2 | 3 | ## The blazing fast, batteries-included ES2015 compiler 4 | 5 | * Try it out at [buble.surge.sh](https://buble.surge.sh) 6 | * Read the docs at [buble.surge.sh/guide](https://buble.surge.sh/guide) 7 | 8 | 9 | ## Quickstart 10 | 11 | Via the command line... 12 | 13 | ```bash 14 | npm install -g buble 15 | buble input.js > output.js 16 | ``` 17 | 18 | ...or via the JavaScript API: 19 | 20 | ```js 21 | var buble = require( 'buble' ); 22 | var result = buble.transform( source ); // { code: ..., map: ... } 23 | ``` 24 | 25 | 26 | ## License 27 | 28 | MIT 29 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: "stable" 4 | 5 | version: "{build}" 6 | build: off 7 | deploy: off 8 | 9 | install: 10 | - npm install 11 | 12 | test_script: 13 | - node --version 14 | - npm --version 15 | - npm test 16 | -------------------------------------------------------------------------------- /bin/buble: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var minimist = require('minimist'); 3 | 4 | var command = minimist(process.argv.slice(2), { 5 | alias: { 6 | // Short options 7 | h: 'help', 8 | i: 'input', 9 | m: 'sourcemap', 10 | o: 'output', 11 | v: 'version', 12 | t: 'target', 13 | y: 'yes', 14 | n: 'no' 15 | } 16 | }); 17 | 18 | if (command.help || (process.argv.length <= 2 && process.stdin.isTTY)) { 19 | require('./showHelp')(); 20 | } else if (command.version) { 21 | console.log('Bublé version ' + require('../package.json').version); // eslint-disable-line no-console 22 | } else { 23 | require('./runBuble')(command); 24 | } 25 | -------------------------------------------------------------------------------- /bin/handleError.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk'); 2 | 3 | function print(msg) { 4 | console.error(chalk.red(msg)); // eslint-disable-line no-console 5 | } 6 | 7 | var handlers = { 8 | MISSING_INPUT_OPTION: () => { 9 | print('You must specify an --input (-i) option'); 10 | }, 11 | 12 | MISSING_OUTPUT_DIR: () => { 13 | print( 14 | 'You must specify an --output (-o) option when compiling a directory of files' 15 | ); 16 | }, 17 | 18 | MISSING_OUTPUT_FILE: () => { 19 | print( 20 | 'You must specify an --output (-o) option when creating a file with a sourcemap' 21 | ); 22 | }, 23 | 24 | ONE_AT_A_TIME: () => { 25 | print('Bublé can only compile one file/directory at a time'); 26 | }, 27 | 28 | DUPLICATE_IMPORT_OPTIONS: () => { 29 | print('use --input, or pass input path as argument – not both'); 30 | }, 31 | 32 | BAD_TARGET: () => { 33 | print('illegal --target option'); 34 | } 35 | }; 36 | 37 | module.exports = function handleError(err) { 38 | var handler; 39 | 40 | if ((handler = handlers[err && err.code])) { 41 | handler(err); 42 | } else { 43 | if (err.snippet) print('---\n' + err.snippet); 44 | print(err.message || err); 45 | 46 | if (err.stack) { 47 | console.error(chalk.grey(err.stack)); // eslint-disable-line no-console 48 | } 49 | } 50 | 51 | console.error( // eslint-disable-line no-console 52 | 'Type ' + 53 | chalk.cyan('buble --help') + 54 | ' for help, or visit https://buble.surge.sh/guide/' 55 | ); 56 | 57 | process.exit(1); 58 | }; 59 | -------------------------------------------------------------------------------- /bin/help.md: -------------------------------------------------------------------------------- 1 | Bublé version <%= version %> 2 | ===================================== 3 | 4 | Usage: buble [options] 5 | 6 | Basic options: 7 | 8 | -v, --version Show version number 9 | -h, --help Show this help message 10 | -i, --input Input (alternative to ) 11 | -o, --output Output (if absent, prints to stdout) 12 | -m, --sourcemap Generate sourcemap (`-m inline` for inline map) 13 | -t, --target Select compilation targets 14 | -y, --yes Transforms to always apply (overrides --target) 15 | -n, --no Transforms to always skip (overrides --target) 16 | --jsx Custom JSX pragma 17 | --objectAssign Specify Object.assign or equivalent polyfill 18 | --no-named-function-expr Don't output named function expressions 19 | 20 | Examples: 21 | 22 | # Compile input.js to output.js 23 | buble input.js > output.js 24 | 25 | # Compile input.js to output.js, write sourcemap to output.js.map 26 | buble input.js -o output.js -m 27 | 28 | # Compile input.js to output.js with inline sourcemap 29 | buble input.js -o output.js -m inline 30 | 31 | # Only use transforms necessary for output.js to run in FF43 and Node 5 32 | buble input.js -o output.js -t firefox:43,node:5 33 | 34 | # As above, but use arrow function and destructuring transforms 35 | buble input.js -o output.js -t firefox:43,node:5 -y arrow,destructuring 36 | 37 | # Compile all the files in src/ to dest/ 38 | buble src -o dest 39 | 40 | Notes: 41 | 42 | * When piping to stdout, only inline sourcemaps are permitted 43 | 44 | For more information visit http://buble.surge.sh/guide 45 | -------------------------------------------------------------------------------- /bin/runBuble.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var buble = require('..'); 4 | var handleError = require('./handleError.js'); 5 | var EOL = require('os').EOL; 6 | 7 | function compile(from, to, command, options) { 8 | try { 9 | var stats = fs.statSync(from); 10 | if (stats.isDirectory()) { 11 | compileDir(from, to, command, options); 12 | } else { 13 | compileFile(from, to, command, options); 14 | } 15 | } catch (err) { 16 | handleError(err); 17 | } 18 | } 19 | 20 | function compileDir(from, to, command, options) { 21 | if (!command.output) handleError({ code: 'MISSING_OUTPUT_DIR' }); 22 | 23 | try { 24 | fs.mkdirSync(to); 25 | } catch (e) { 26 | if (e.code !== 'EEXIST') throw e; 27 | } 28 | 29 | fs.readdirSync(from).forEach(function(file) { 30 | compile(path.resolve(from, file), path.resolve(to, file), command, options); 31 | }); 32 | } 33 | 34 | function compileFile(from, to, command, options) { 35 | var ext = path.extname(from); 36 | 37 | if (ext !== '.js' && ext !== '.jsm' && ext !== '.es6' && ext !== '.jsx') 38 | return; 39 | 40 | if (to) { 41 | var extTo = path.extname(to); 42 | to = to.slice(0, -extTo.length) + '.js'; 43 | } 44 | 45 | var source = fs.readFileSync(from, 'utf-8'); 46 | var result = buble.transform(source, { 47 | target: options.target, 48 | transforms: options.transforms, 49 | source: from, 50 | file: to, 51 | jsx: options.jsx, 52 | objectAssign: options.objectAssign, 53 | namedFunctionExpressions: options.namedFunctionExpressions 54 | }); 55 | 56 | write(result, to, command); 57 | } 58 | 59 | function write(result, to, command) { 60 | if (command.sourcemap === 'inline') { 61 | result.code += EOL + '//# sourceMappingURL=' + result.map.toUrl(); 62 | } else if (command.sourcemap) { 63 | if (!to) { 64 | handleError({ code: 'MISSING_OUTPUT_FILE' }); 65 | } 66 | 67 | result.code += EOL + '//# sourceMappingURL=' + path.basename(to) + '.map'; 68 | fs.writeFileSync(to + '.map', result.map.toString()); 69 | } 70 | 71 | if (to) { 72 | fs.writeFileSync(to, result.code); 73 | } else { 74 | console.log(result.code); // eslint-disable-line no-console 75 | } 76 | } 77 | 78 | module.exports = function(command) { 79 | if (command._.length > 1) { 80 | handleError({ code: 'ONE_AT_A_TIME' }); 81 | } 82 | 83 | if (command._.length === 1) { 84 | if (command.input) { 85 | handleError({ code: 'DUPLICATE_IMPORT_OPTIONS' }); 86 | } 87 | 88 | command.input = command._[0]; 89 | } 90 | 91 | var options = { 92 | target: {}, 93 | transforms: {}, 94 | jsx: command.jsx, 95 | objectAssign: 96 | command.objectAssign === true ? 'Object.assign' : command.objectAssign, 97 | namedFunctionExpressions: command['named-function-expr'] !== false 98 | }; 99 | 100 | if (command.target) { 101 | if (!/^(?:(\w+):([\d\.]+),)*(\w+):([\d\.]+)$/.test(command.target)) { 102 | handleError({ code: 'BAD_TARGET' }); 103 | } 104 | 105 | command.target 106 | .split(',') 107 | .map(function(target) { 108 | return target.split(':'); 109 | }) 110 | .forEach(function(pair) { 111 | options.target[pair[0]] = pair[1]; 112 | }); 113 | } 114 | 115 | if (command.yes) { 116 | command.yes.split(',').forEach(function(transform) { 117 | options.transforms[transform] = true; 118 | }); 119 | } 120 | 121 | if (command.no) { 122 | command.no.split(',').forEach(function(transform) { 123 | options.transforms[transform] = false; 124 | }); 125 | } 126 | 127 | if (command.input) { 128 | compile(command.input, command.output, command, options); 129 | } else { 130 | process.stdin.resume(); 131 | process.stdin.setEncoding('utf8'); 132 | 133 | var source = ''; 134 | 135 | process.stdin.on('data', function(chunk) { 136 | source += chunk; 137 | }); 138 | 139 | process.stdin.on('end', function() { 140 | options.source = command.input = 'stdin'; 141 | options.file = command.output; 142 | try { 143 | var result = buble.transform(source, options); 144 | write(result, command.output, command); 145 | } catch (err) { 146 | handleError(err); 147 | } 148 | }); 149 | } 150 | }; 151 | -------------------------------------------------------------------------------- /bin/showHelp.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | 4 | module.exports = function() { 5 | fs.readFile(path.join(__dirname, 'help.md'), (err, result) => { 6 | var help; 7 | 8 | if (err) throw err; 9 | 10 | help = result 11 | .toString() 12 | .replace('<%= version %>', require('../package.json').version); 13 | 14 | console.log('\n' + help + '\n'); // eslint-disable-line no-console 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /bublé.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yyx990803/buble/f5996c9cdb2e61cb7dddf0f6c6f25d0f3f600055/bublé.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "buble", 3 | "version": "0.19.3", 4 | "description": "The blazing fast, batteries-included ES2015 compiler", 5 | "main": "dist/buble.cjs.js", 6 | "module": "dist/buble.es.js", 7 | "browser": { 8 | "dist/buble.cjs.js": "./dist/buble-browser.cjs.js", 9 | "dist/buble.es.js": "./dist/buble-browser.es.js" 10 | }, 11 | "unpkg": "dist/buble-browser-deps.umd.js", 12 | "files": [ 13 | "bin", 14 | "src", 15 | "dist", 16 | "register.js", 17 | "README.md" 18 | ], 19 | "scripts": { 20 | "build": "rollup -c", 21 | "test": "mocha test/test.js", 22 | "pretest": "npm run build", 23 | "prepublish": "npm test", 24 | "lint": "eslint src" 25 | }, 26 | "bin": { 27 | "buble": "./bin/buble" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/Rich-Harris/buble.git" 32 | }, 33 | "keywords": [ 34 | "javascript", 35 | "transpilation", 36 | "compilation", 37 | "esnext", 38 | "es2015", 39 | "es2017", 40 | "es6", 41 | "es7" 42 | ], 43 | "author": "Rich Harris", 44 | "license": "MIT", 45 | "bugs": { 46 | "url": "https://github.com/Rich-Harris/buble/issues" 47 | }, 48 | "homepage": "https://github.com/Rich-Harris/buble#README", 49 | "devDependencies": { 50 | "console-group": "^0.3.3", 51 | "eslint": "^4.17.0", 52 | "glob": "^7.0.3", 53 | "mocha": "^5.0.0", 54 | "regexpu-core": "^4.1.3", 55 | "rimraf": "^2.5.2", 56 | "rollup": "^0.55.5", 57 | "rollup-plugin-buble": "^0.19.1", 58 | "rollup-plugin-commonjs": "^8.3.0", 59 | "rollup-plugin-json": "^2.3.0", 60 | "rollup-plugin-node-resolve": "^3.0.2", 61 | "source-map": "^0.6.1", 62 | "source-map-support": "^0.5.3" 63 | }, 64 | "dependencies": { 65 | "acorn": "^5.4.1", 66 | "acorn-dynamic-import": "^3.0.0", 67 | "acorn-jsx": "^4.1.1", 68 | "chalk": "^2.3.1", 69 | "magic-string": "^0.22.4", 70 | "minimist": "^1.2.0", 71 | "os-homedir": "^1.0.1", 72 | "vlq": "^1.0.0" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /register.js: -------------------------------------------------------------------------------- 1 | var fs = require( 'fs' ); 2 | var path = require( 'path' ); 3 | var crypto = require( 'crypto' ); 4 | var homedir = require( 'os-homedir' ); 5 | var buble = require( './' ); 6 | 7 | var original = require.extensions[ '.js' ]; 8 | var nodeModulesPattern = path.sep === '/' ? /\/node_modules\// : /\\node_modules\\/; 9 | 10 | var nodeVersion = /(?:0\.)?\d+/.exec( process.version )[0]; 11 | var versions = [ '0.10', '0.12', '4', '5', '6' ]; 12 | 13 | if ( !~versions.indexOf( nodeVersion ) ) { 14 | if ( +nodeVersion > 6 ) { 15 | nodeVersion = '6'; 16 | } else { 17 | throw new Error( 'Unsupported version (' + nodeVersion + '). Please raise an issue at https://github.com/Rich-Harris/buble/issues' ); 18 | } 19 | } 20 | 21 | var options = { 22 | target: { 23 | node: nodeVersion 24 | } 25 | }; 26 | 27 | function mkdirp ( dir ) { 28 | var parent = path.dirname( dir ); 29 | if ( dir === parent ) return; 30 | mkdirp( parent ); 31 | 32 | try { 33 | fs.mkdirSync( dir ); 34 | } catch ( err ) { 35 | if ( err.code !== 'EEXIST' ) throw err; 36 | } 37 | } 38 | 39 | var home = homedir(); 40 | if ( home ) { 41 | var cachedir = path.join( home, '.buble-cache', String(nodeVersion) ); 42 | mkdirp( cachedir ); 43 | fs.writeFileSync( path.join( home, '.buble-cache/README.txt' ), 'These files enable a faster startup when using buble/register. You can safely delete this folder at any time. See https://buble.surge.sh/guide/ for more information.' ); 44 | } 45 | 46 | var optionsStringified = JSON.stringify( options ); 47 | 48 | require.extensions[ '.js' ] = function ( m, filename ) { 49 | if ( nodeModulesPattern.test( filename ) ) return original( m, filename ); 50 | 51 | var source = fs.readFileSync( filename, 'utf-8' ); 52 | var hash = crypto.createHash( 'sha256' ); 53 | hash.update( buble.VERSION ); 54 | hash.update( optionsStringified ); 55 | hash.update( source ); 56 | var key = hash.digest( 'hex' ) + '.json'; 57 | var cachepath = path.join( cachedir, key ); 58 | 59 | var compiled; 60 | 61 | if ( cachedir ) { 62 | try { 63 | compiled = JSON.parse( fs.readFileSync( cachepath, 'utf-8' ) ); 64 | } catch ( err ) { 65 | // noop 66 | } 67 | } 68 | 69 | if ( !compiled ) { 70 | try { 71 | compiled = buble.transform( source, options ); 72 | 73 | if ( cachedir ) { 74 | fs.writeFileSync( cachepath, JSON.stringify( compiled ) ); 75 | } 76 | } catch ( err ) { 77 | if ( err.snippet ) { 78 | console.log( 'Error compiling ' + filename + ':\n---' ); 79 | console.log( err.snippet ); 80 | console.log( err.message ); 81 | console.log( '' ) 82 | process.exit( 1 ); 83 | } 84 | 85 | throw err; 86 | } 87 | } 88 | 89 | m._compile( '"use strict";\n' + compiled.code, filename ); 90 | }; 91 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import buble from 'rollup-plugin-buble'; 2 | import json from 'rollup-plugin-json'; 3 | import resolve from 'rollup-plugin-node-resolve'; 4 | import commonjs from 'rollup-plugin-commonjs'; 5 | import pkg from './package.json'; 6 | 7 | const ensureArray = maybeArr => Array.isArray(maybeArr) ? maybeArr : [maybeArr]; 8 | 9 | const createConfig = (opts) => { 10 | opts = opts || {}; 11 | const browser = opts.browser || false; 12 | const external = opts.external || ['acorn', 'magic-string']; 13 | const output = ensureArray(opts.output); 14 | 15 | return { 16 | input: 'src/index.js', 17 | output: output.map(format => Object.assign({}, format, { 18 | name: 'buble', 19 | sourcemap: true 20 | })), 21 | external: external, 22 | plugins: [ 23 | json(), 24 | commonjs(), 25 | buble({ 26 | target: !browser ? { node: 4 } : null, 27 | include: [ 28 | 'src/**', 29 | 'node_modules/regexpu-core/**', 30 | 'node_modules/unicode-match-property-ecmascript/**', 31 | 'node_modules/unicode-match-property-value-ecmascript/**', 32 | ], 33 | transforms: { 34 | dangerousForOf: true 35 | } 36 | }), 37 | resolve() 38 | ], 39 | }; 40 | }; 41 | 42 | const configs = [ 43 | /* node ESM/CJS builds */ 44 | createConfig({ 45 | output: [ 46 | { format: 'es', file: pkg.module }, 47 | { format: 'cjs', file: pkg.main } 48 | ], 49 | }), 50 | /* browser ESM/CJS builds (for bundlers) */ 51 | createConfig({ 52 | browser: true, 53 | output: [ 54 | { format: 'es', file: pkg.browser[pkg.module] }, 55 | { format: 'cjs', file: pkg.browser[pkg.main] } 56 | ], 57 | }), 58 | /* UMD with bundled dependencies, ready for browsers */ 59 | createConfig({ 60 | browser: true, 61 | external: [], 62 | output: { format: 'umd', file: pkg.unpkg }, 63 | }), 64 | ]; 65 | 66 | export default configs; 67 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import * as acorn from 'acorn'; 2 | import acornJsx from 'acorn-jsx/inject'; 3 | import acornDynamicImport from 'acorn-dynamic-import/lib/inject'; 4 | import Program from './program/Program.js'; 5 | import { features, matrix } from './support.js'; 6 | import getSnippet from './utils/getSnippet.js'; 7 | 8 | const { parse } = [acornJsx, acornDynamicImport].reduce( 9 | (final, plugin) => plugin(final), 10 | acorn 11 | ); 12 | 13 | const dangerousTransforms = ['dangerousTaggedTemplateString', 'dangerousForOf']; 14 | 15 | export function target(target) { 16 | const targets = Object.keys(target); 17 | let bitmask = targets.length 18 | ? 0b11111111111111111111 19 | : 0b01000000000000000000; 20 | 21 | Object.keys(target).forEach(environment => { 22 | const versions = matrix[environment]; 23 | if (!versions) 24 | throw new Error( 25 | `Unknown environment '${environment}'. Please raise an issue at https://github.com/Rich-Harris/buble/issues` 26 | ); 27 | 28 | const targetVersion = target[environment]; 29 | if (!(targetVersion in versions)) 30 | throw new Error( 31 | `Support data exists for the following versions of ${environment}: ${Object.keys( 32 | versions 33 | ).join( 34 | ', ' 35 | )}. Please raise an issue at https://github.com/Rich-Harris/buble/issues` 36 | ); 37 | const support = versions[targetVersion]; 38 | 39 | bitmask &= support; 40 | }); 41 | 42 | let transforms = Object.create(null); 43 | features.forEach((name, i) => { 44 | transforms[name] = !(bitmask & (1 << i)); 45 | }); 46 | 47 | dangerousTransforms.forEach(name => { 48 | transforms[name] = false; 49 | }); 50 | 51 | transforms.stripWith = false 52 | transforms.stripWithFunctional = false 53 | return transforms; 54 | } 55 | 56 | export function transform ( source, options = {} ) { 57 | let transforms = target( options.target || {} ); 58 | Object.keys( options.transforms || {} ).forEach( name => { 59 | if ( name === 'modules' ) { 60 | if ( !( 'moduleImport' in options.transforms ) ) transforms.moduleImport = options.transforms.modules; 61 | if ( !( 'moduleExport' in options.transforms ) ) transforms.moduleExport = options.transforms.modules; 62 | return; 63 | } 64 | 65 | if ( !( name in transforms ) ) throw new Error( `Unknown transform '${name}'` ); 66 | transforms[ name ] = options.transforms[ name ]; 67 | }); 68 | 69 | if (transforms.stripWith) { 70 | // necessary for { key } to be prefixed properly 71 | // in case the user uses a target environment that supports this 72 | transforms.conciseMethodProperty = true 73 | } 74 | 75 | let ast; 76 | let jsx = null; 77 | 78 | try { 79 | ast = parse(source, { 80 | ecmaVersion: 9, 81 | preserveParens: true, 82 | sourceType: transforms.stripWith ? 'script' : 'module', 83 | onComment: (block, text) => { 84 | if (!jsx) { 85 | let match = /@jsx\s+([^\s]+)/.exec(text); 86 | if (match) jsx = match[1]; 87 | } 88 | }, 89 | plugins: { 90 | jsx: true, 91 | dynamicImport: true 92 | } 93 | }); 94 | options.jsx = jsx || options.jsx; 95 | } catch (err) { 96 | err.snippet = getSnippet(source, err.loc); 97 | err.toString = () => `${err.name}: ${err.message}\n${err.snippet}`; 98 | throw err; 99 | } 100 | 101 | return new Program( source, ast, transforms, options ).export( options ); 102 | } 103 | 104 | export { version as VERSION } from '../package.json'; 105 | -------------------------------------------------------------------------------- /src/program/Node.js: -------------------------------------------------------------------------------- 1 | // used for debugging, without the noise created by 2 | // circular references 3 | function toJSON(node) { 4 | var obj = {}; 5 | 6 | Object.keys(node).forEach(key => { 7 | if ( 8 | key === 'parent' || 9 | key === 'program' || 10 | key === 'keys' || 11 | key === '__wrapped' 12 | ) 13 | return; 14 | 15 | if (Array.isArray(node[key])) { 16 | obj[key] = node[key].map(toJSON); 17 | } else if (node[key] && node[key].toJSON) { 18 | obj[key] = node[key].toJSON(); 19 | } else { 20 | obj[key] = node[key]; 21 | } 22 | }); 23 | 24 | return obj; 25 | } 26 | 27 | export default class Node { 28 | ancestor(level) { 29 | let node = this; 30 | while (level--) { 31 | node = node.parent; 32 | if (!node) return null; 33 | } 34 | 35 | return node; 36 | } 37 | 38 | contains(node) { 39 | while (node) { 40 | if (node === this) return true; 41 | node = node.parent; 42 | } 43 | 44 | return false; 45 | } 46 | 47 | findLexicalBoundary() { 48 | return this.parent.findLexicalBoundary(); 49 | } 50 | 51 | findNearest(type) { 52 | if (typeof type === 'string') type = new RegExp(`^${type}$`); 53 | if (type.test(this.type)) return this; 54 | return this.parent.findNearest(type); 55 | } 56 | 57 | unparenthesizedParent() { 58 | let node = this.parent; 59 | while (node && node.type === 'ParenthesizedExpression') { 60 | node = node.parent; 61 | } 62 | return node; 63 | } 64 | 65 | unparenthesize() { 66 | let node = this; 67 | while (node.type === 'ParenthesizedExpression') { 68 | node = node.expression; 69 | } 70 | return node; 71 | } 72 | 73 | findScope(functionScope) { 74 | return this.parent.findScope(functionScope); 75 | } 76 | 77 | getIndentation() { 78 | return this.parent.getIndentation(); 79 | } 80 | 81 | initialise(transforms) { 82 | for (var key of this.keys) { 83 | const value = this[key]; 84 | 85 | if (Array.isArray(value)) { 86 | value.forEach(node => node && node.initialise(transforms)); 87 | } else if (value && typeof value === 'object') { 88 | value.initialise(transforms); 89 | } 90 | } 91 | } 92 | 93 | toJSON() { 94 | return toJSON(this); 95 | } 96 | 97 | toString() { 98 | return this.program.magicString.original.slice(this.start, this.end); 99 | } 100 | 101 | transpile(code, transforms) { 102 | for (const key of this.keys) { 103 | const value = this[key]; 104 | 105 | if (Array.isArray(value)) { 106 | value.forEach(node => node && node.transpile(code, transforms)); 107 | } else if (value && typeof value === 'object') { 108 | value.transpile(code, transforms); 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/program/Program.js: -------------------------------------------------------------------------------- 1 | import MagicString from 'magic-string'; 2 | import BlockStatement from './BlockStatement.js'; 3 | import wrap from './wrap.js'; 4 | 5 | export default function Program(source, ast, transforms, options) { 6 | this.type = 'Root'; 7 | 8 | // options 9 | this.jsx = options.jsx || 'React.createElement'; 10 | this.options = options; 11 | 12 | this.source = source; 13 | this.magicString = new MagicString(source); 14 | 15 | this.ast = ast; 16 | this.depth = 0; 17 | 18 | wrap((this.body = ast), this); 19 | this.body.__proto__ = BlockStatement.prototype; 20 | 21 | this.templateLiteralQuasis = Object.create(null); 22 | for (let i = 0; i < this.body.body.length; ++i) { 23 | if (!this.body.body[i].directive) { 24 | this.prependAt = this.body.body[i].start; 25 | break; 26 | } 27 | } 28 | this.objectWithoutPropertiesHelper = null; 29 | 30 | this.indentExclusionElements = []; 31 | this.body.initialise(transforms); 32 | 33 | this.indentExclusions = Object.create(null); 34 | for (const node of this.indentExclusionElements) { 35 | for (let i = node.start; i < node.end; i += 1) { 36 | this.indentExclusions[i] = true; 37 | } 38 | } 39 | 40 | this.body.transpile(this.magicString, transforms); 41 | } 42 | 43 | Program.prototype = { 44 | export(options = {}) { 45 | return { 46 | code: this.magicString.toString(), 47 | map: this.magicString.generateMap({ 48 | file: options.file, 49 | source: options.source, 50 | includeContent: options.includeContent !== false 51 | }) 52 | }; 53 | }, 54 | 55 | findNearest() { 56 | return null; 57 | }, 58 | 59 | findScope() { 60 | return null; 61 | }, 62 | 63 | getObjectWithoutPropertiesHelper(code) { 64 | if (!this.objectWithoutPropertiesHelper) { 65 | this.objectWithoutPropertiesHelper = this.body.scope.createIdentifier('objectWithoutProperties'); 66 | code.prependLeft(this.prependAt, `function ${this.objectWithoutPropertiesHelper} (obj, exclude) { ` + 67 | `var target = {}; for (var k in obj) ` + 68 | `if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) ` + 69 | `target[k] = obj[k]; return target; }\n` 70 | ); 71 | } 72 | return this.objectWithoutPropertiesHelper; 73 | } 74 | }; 75 | -------------------------------------------------------------------------------- /src/program/Scope.js: -------------------------------------------------------------------------------- 1 | import extractNames from './extractNames.js'; 2 | import reserved from '../utils/reserved.js'; 3 | 4 | export default function Scope(options) { 5 | options = options || {}; 6 | 7 | this.parent = options.parent; 8 | this.isBlockScope = !!options.block; 9 | this.createDeclarationCallback = options.declare; 10 | 11 | let scope = this; 12 | while (scope.isBlockScope) scope = scope.parent; 13 | this.functionScope = scope; 14 | 15 | this.identifiers = []; 16 | this.declarations = Object.create(null); 17 | this.references = Object.create(null); 18 | this.blockScopedDeclarations = this.isBlockScope ? null : Object.create(null); 19 | this.aliases = Object.create(null); 20 | } 21 | 22 | Scope.prototype = { 23 | addDeclaration(node, kind) { 24 | for (const identifier of extractNames(node)) { 25 | const name = identifier.name; 26 | 27 | const declaration = { name, node: identifier, kind, instances: [] }; 28 | this.declarations[name] = declaration; 29 | 30 | if (this.isBlockScope) { 31 | if (!this.functionScope.blockScopedDeclarations[name]) 32 | this.functionScope.blockScopedDeclarations[name] = []; 33 | this.functionScope.blockScopedDeclarations[name].push(declaration); 34 | } 35 | } 36 | }, 37 | 38 | addReference(identifier) { 39 | if (this.consolidated) { 40 | this.consolidateReference(identifier); 41 | } else { 42 | this.identifiers.push(identifier); 43 | } 44 | }, 45 | 46 | consolidate() { 47 | for (let i = 0; i < this.identifiers.length; i += 1) { 48 | // we might push to the array during consolidation, so don't cache length 49 | const identifier = this.identifiers[i]; 50 | this.consolidateReference(identifier); 51 | } 52 | 53 | this.consolidated = true; // TODO understand why this is necessary... seems bad 54 | }, 55 | 56 | consolidateReference(identifier) { 57 | const declaration = this.declarations[identifier.name]; 58 | if (declaration) { 59 | declaration.instances.push(identifier); 60 | } else { 61 | this.references[identifier.name] = true; 62 | if (this.parent) this.parent.addReference(identifier); 63 | } 64 | }, 65 | 66 | contains(name) { 67 | return ( 68 | this.declarations[name] || 69 | (this.parent ? this.parent.contains(name) : false) 70 | ); 71 | }, 72 | 73 | createIdentifier(base) { 74 | if (typeof base === 'number') base = base.toString(); 75 | 76 | base = base 77 | .replace(/\s/g, '') 78 | .replace(/\[([^\]]+)\]/g, '_$1') 79 | .replace(/[^a-zA-Z0-9_$]/g, '_') 80 | .replace(/_{2,}/, '_'); 81 | 82 | let name = base; 83 | let counter = 1; 84 | 85 | while ( 86 | this.declarations[name] || 87 | this.references[name] || 88 | this.aliases[name] || 89 | name in reserved 90 | ) { 91 | name = `${base}$${counter++}`; 92 | } 93 | 94 | this.aliases[name] = true; 95 | return name; 96 | }, 97 | 98 | createDeclaration(base) { 99 | const id = this.createIdentifier(base); 100 | this.createDeclarationCallback(id); 101 | return id; 102 | }, 103 | 104 | findDeclaration(name) { 105 | return ( 106 | this.declarations[name] || 107 | (this.parent && this.parent.findDeclaration(name)) 108 | ); 109 | }, 110 | 111 | // Sometimes, block scope declarations change name during transpilation 112 | resolveName(name) { 113 | const declaration = this.findDeclaration(name); 114 | return declaration ? declaration.name : name; 115 | } 116 | }; 117 | -------------------------------------------------------------------------------- /src/program/extractNames.js: -------------------------------------------------------------------------------- 1 | export default function extractNames(node) { 2 | const names = []; 3 | extractors[node.type](names, node); 4 | return names; 5 | } 6 | 7 | const extractors = { 8 | Identifier(names, node) { 9 | names.push(node); 10 | }, 11 | 12 | ObjectPattern(names, node) { 13 | for (const prop of node.properties) { 14 | extractors[prop.type](names, prop); 15 | } 16 | }, 17 | 18 | Property(names, node) { 19 | extractors[node.value.type](names, node.value); 20 | }, 21 | 22 | ArrayPattern(names, node) { 23 | for (const element of node.elements) { 24 | if (element) extractors[element.type](names, element); 25 | } 26 | }, 27 | 28 | RestElement(names, node) { 29 | extractors[node.argument.type](names, node.argument); 30 | }, 31 | 32 | AssignmentPattern(names, node) { 33 | extractors[node.left.type](names, node.left); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /src/program/keys.js: -------------------------------------------------------------------------------- 1 | export default { 2 | Program: ['body'], 3 | Literal: [] 4 | }; 5 | -------------------------------------------------------------------------------- /src/program/types/ArrayExpression.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import spread, { isArguments } from '../../utils/spread.js'; 3 | 4 | export default class ArrayExpression extends Node { 5 | initialise(transforms) { 6 | if (transforms.spreadRest && this.elements.length) { 7 | const lexicalBoundary = this.findLexicalBoundary(); 8 | 9 | let i = this.elements.length; 10 | while (i--) { 11 | const element = this.elements[i]; 12 | if ( 13 | element && 14 | element.type === 'SpreadElement' && 15 | isArguments(element.argument) 16 | ) { 17 | this.argumentsArrayAlias = lexicalBoundary.getArgumentsArrayAlias(); 18 | } 19 | } 20 | } 21 | 22 | super.initialise(transforms); 23 | } 24 | 25 | transpile(code, transforms) { 26 | super.transpile(code, transforms); 27 | 28 | if (transforms.spreadRest) { 29 | // erase trailing comma after last array element if not an array hole 30 | if (this.elements.length) { 31 | let lastElement = this.elements[this.elements.length - 1]; 32 | if ( 33 | lastElement && 34 | /\s*,/.test(code.original.slice(lastElement.end, this.end)) 35 | ) { 36 | code.overwrite(lastElement.end, this.end - 1, ' '); 37 | } 38 | } 39 | 40 | if (this.elements.length === 1) { 41 | const element = this.elements[0]; 42 | 43 | if (element && element.type === 'SpreadElement') { 44 | // special case – [ ...arguments ] 45 | if (isArguments(element.argument)) { 46 | code.overwrite( 47 | this.start, 48 | this.end, 49 | `[].concat( ${this.argumentsArrayAlias} )` 50 | ); // TODO if this is the only use of argsArray, don't bother concating 51 | } else { 52 | code.overwrite(this.start, element.argument.start, '[].concat( '); 53 | code.overwrite(element.end, this.end, ' )'); 54 | } 55 | } 56 | } else { 57 | const hasSpreadElements = spread( 58 | code, 59 | this.elements, 60 | this.start, 61 | this.argumentsArrayAlias 62 | ); 63 | 64 | if (hasSpreadElements) { 65 | code.overwrite(this.end - 1, this.end, ')'); 66 | } 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/program/types/ArrowFunctionExpression.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import removeTrailingComma from '../../utils/removeTrailingComma.js'; 3 | 4 | export default class ArrowFunctionExpression extends Node { 5 | initialise(transforms) { 6 | this.body.createScope(); 7 | super.initialise(transforms); 8 | } 9 | 10 | transpile(code, transforms) { 11 | const naked = this.params.length === 1 && this.start === this.params[0].start; 12 | 13 | if (transforms.arrow || this.needsArguments(transforms)) { 14 | // remove arrow 15 | let charIndex = this.body.start; 16 | while (code.original[charIndex] !== '=') { 17 | charIndex -= 1; 18 | } 19 | code.remove(charIndex, this.body.start); 20 | 21 | super.transpile(code, transforms); 22 | 23 | // wrap naked parameter 24 | if (naked) { 25 | code.prependRight(this.params[0].start, '('); 26 | code.appendLeft(this.params[0].end, ')'); 27 | } 28 | 29 | // add function 30 | if (this.parent && this.parent.type === 'ExpressionStatement') { 31 | // standalone expression statement 32 | code.prependRight(this.start, '!function'); 33 | } else { 34 | code.prependRight(this.start, 'function '); 35 | } 36 | } else { 37 | super.transpile(code, transforms); 38 | } 39 | 40 | if (transforms.trailingFunctionCommas && this.params.length && !naked) { 41 | removeTrailingComma(code, this.params[this.params.length - 1].end); 42 | } 43 | } 44 | 45 | // Returns whether any transforms that will happen use `arguments` 46 | needsArguments(transforms) { 47 | return ( 48 | transforms.spreadRest && 49 | this.params.filter(param => param.type === 'RestElement').length > 0 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/program/types/AssignmentExpression.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import checkConst from '../../utils/checkConst.js'; 3 | import destructure from '../../utils/destructure.js'; 4 | 5 | export default class AssignmentExpression extends Node { 6 | initialise(transforms) { 7 | if (this.left.type === 'Identifier') { 8 | const declaration = this.findScope(false).findDeclaration(this.left.name); 9 | // special case – https://gitlab.com/Rich-Harris/buble/issues/11 10 | const statement = declaration && declaration.node.ancestor(3); 11 | if ( 12 | statement && 13 | statement.type === 'ForStatement' && 14 | statement.body.contains(this) 15 | ) { 16 | statement.reassigned[this.left.name] = true; 17 | } 18 | } 19 | 20 | super.initialise(transforms); 21 | } 22 | 23 | transpile(code, transforms) { 24 | if (this.left.type === 'Identifier') { 25 | // Do this check after everything has been initialized to find 26 | // shadowing declarations after this expression 27 | checkConst(this.left, this.findScope(false)); 28 | } 29 | 30 | if (this.operator === '**=' && transforms.exponentiation) { 31 | this.transpileExponentiation(code, transforms); 32 | } else if (/Pattern/.test(this.left.type) && transforms.destructuring) { 33 | this.transpileDestructuring(code, transforms); 34 | } 35 | 36 | super.transpile(code, transforms); 37 | } 38 | 39 | transpileDestructuring(code) { 40 | const writeScope = this.findScope(true); 41 | const lookupScope = this.findScope(false); 42 | const assign = writeScope.createDeclaration('assign'); 43 | code.appendRight(this.left.end, `(${assign}`); 44 | 45 | code.appendLeft(this.right.end, ', '); 46 | const statementGenerators = []; 47 | destructure( 48 | code, 49 | id => writeScope.createDeclaration(id), 50 | node => { 51 | const name = lookupScope.resolveName(node.name); 52 | checkConst(node, lookupScope); 53 | return name; 54 | }, 55 | this.left, 56 | assign, 57 | true, 58 | statementGenerators 59 | ); 60 | 61 | let suffix = ', '; 62 | statementGenerators.forEach((fn, j) => { 63 | if (j === statementGenerators.length - 1) { 64 | suffix = ''; 65 | } 66 | 67 | fn(this.end, '', suffix); 68 | }); 69 | 70 | if (this.unparenthesizedParent().type === 'ExpressionStatement') { 71 | // no rvalue needed for expression statement 72 | code.appendRight(this.end, `)`); 73 | } else { 74 | // destructuring is part of an expression - need an rvalue 75 | code.appendRight(this.end, `, ${assign})`); 76 | } 77 | } 78 | 79 | transpileExponentiation(code) { 80 | const scope = this.findScope(false); 81 | 82 | // first, the easy part – `**=` -> `=` 83 | let charIndex = this.left.end; 84 | while (code.original[charIndex] !== '*') charIndex += 1; 85 | code.remove(charIndex, charIndex + 2); 86 | 87 | // how we do the next part depends on a number of factors – whether 88 | // this is a top-level statement, and whether we're updating a 89 | // simple or complex reference 90 | let base; 91 | 92 | const left = this.left.unparenthesize(); 93 | 94 | if (left.type === 'Identifier') { 95 | base = scope.resolveName(left.name); 96 | } else if (left.type === 'MemberExpression') { 97 | let object; 98 | let needsObjectVar = false; 99 | let property; 100 | let needsPropertyVar = false; 101 | 102 | const statement = this.findNearest(/(?:Statement|Declaration)$/); 103 | const i0 = statement.getIndentation(); 104 | 105 | if (left.property.type === 'Identifier') { 106 | property = left.computed 107 | ? scope.resolveName(left.property.name) 108 | : left.property.name; 109 | } else { 110 | property = scope.createDeclaration('property'); 111 | needsPropertyVar = true; 112 | } 113 | 114 | if (left.object.type === 'Identifier') { 115 | object = scope.resolveName(left.object.name); 116 | } else { 117 | object = scope.createDeclaration('object'); 118 | needsObjectVar = true; 119 | } 120 | 121 | if (left.start === statement.start) { 122 | if (needsObjectVar && needsPropertyVar) { 123 | code.prependRight(statement.start, `${object} = `); 124 | code.overwrite( 125 | left.object.end, 126 | left.property.start, 127 | `;\n${i0}${property} = ` 128 | ); 129 | code.overwrite( 130 | left.property.end, 131 | left.end, 132 | `;\n${i0}${object}[${property}]` 133 | ); 134 | } else if (needsObjectVar) { 135 | code.prependRight(statement.start, `${object} = `); 136 | code.appendLeft(left.object.end, `;\n${i0}`); 137 | code.appendLeft(left.object.end, object); 138 | } else if (needsPropertyVar) { 139 | code.prependRight(left.property.start, `${property} = `); 140 | code.appendLeft(left.property.end, `;\n${i0}`); 141 | code.move(left.property.start, left.property.end, this.start); 142 | 143 | code.appendLeft(left.object.end, `[${property}]`); 144 | code.remove(left.object.end, left.property.start); 145 | code.remove(left.property.end, left.end); 146 | } 147 | } else { 148 | if (needsObjectVar && needsPropertyVar) { 149 | code.prependRight(left.start, `( ${object} = `); 150 | code.overwrite( 151 | left.object.end, 152 | left.property.start, 153 | `, ${property} = ` 154 | ); 155 | code.overwrite( 156 | left.property.end, 157 | left.end, 158 | `, ${object}[${property}]` 159 | ); 160 | } else if (needsObjectVar) { 161 | code.prependRight(left.start, `( ${object} = `); 162 | code.appendLeft(left.object.end, `, ${object}`); 163 | } else if (needsPropertyVar) { 164 | code.prependRight(left.property.start, `( ${property} = `); 165 | code.appendLeft(left.property.end, `, `); 166 | code.move(left.property.start, left.property.end, left.start); 167 | 168 | code.overwrite(left.object.end, left.property.start, `[${property}]`); 169 | code.remove(left.property.end, left.end); 170 | } 171 | 172 | if (needsPropertyVar) { 173 | code.appendLeft(this.end, ` )`); 174 | } 175 | } 176 | 177 | base = 178 | object + 179 | (left.computed || needsPropertyVar ? `[${property}]` : `.${property}`); 180 | } 181 | 182 | code.prependRight(this.right.start, `Math.pow( ${base}, `); 183 | code.appendLeft(this.right.end, ` )`); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/program/types/BinaryExpression.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | 3 | export default class BinaryExpression extends Node { 4 | transpile(code, transforms) { 5 | if (this.operator === '**' && transforms.exponentiation) { 6 | code.prependRight(this.start, `Math.pow( `); 7 | code.overwrite(this.left.end, this.right.start, `, `); 8 | code.appendLeft(this.end, ` )`); 9 | } 10 | super.transpile(code, transforms); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/program/types/BreakStatement.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import CompileError from '../../utils/CompileError.js'; 3 | import { loopStatement } from '../../utils/patterns.js'; 4 | 5 | export default class BreakStatement extends Node { 6 | initialise() { 7 | const loop = this.findNearest(loopStatement); 8 | const switchCase = this.findNearest('SwitchCase'); 9 | 10 | if (loop && (!switchCase || loop.depth > switchCase.depth)) { 11 | loop.canBreak = true; 12 | this.loop = loop; 13 | } 14 | } 15 | 16 | transpile(code) { 17 | if (this.loop && this.loop.shouldRewriteAsFunction) { 18 | if (this.label) 19 | throw new CompileError( 20 | 'Labels are not currently supported in a loop with locally-scoped variables', 21 | this 22 | ); 23 | code.overwrite(this.start, this.start + 5, `return 'break'`); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/program/types/CallExpression.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import spread, { isArguments } from '../../utils/spread.js'; 3 | import removeTrailingComma from '../../utils/removeTrailingComma.js'; 4 | import { shouldPrependVm } from '../../utils/prependVm.js'; 5 | 6 | export default class CallExpression extends Node { 7 | initialise(transforms) { 8 | if (transforms.spreadRest && this.arguments.length > 1) { 9 | const lexicalBoundary = this.findLexicalBoundary(); 10 | 11 | let i = this.arguments.length; 12 | while (i--) { 13 | const arg = this.arguments[i]; 14 | if (arg.type === 'SpreadElement' && isArguments(arg.argument)) { 15 | this.argumentsArrayAlias = lexicalBoundary.getArgumentsArrayAlias(); 16 | } 17 | } 18 | } 19 | 20 | super.initialise(transforms); 21 | } 22 | 23 | transpile(code, transforms) { 24 | if (transforms.spreadRest && this.arguments.length) { 25 | let hasSpreadElements = false; 26 | let context; 27 | 28 | const firstArgument = this.arguments[0]; 29 | 30 | if (this.arguments.length === 1) { 31 | if (firstArgument.type === 'SpreadElement') { 32 | code.remove(firstArgument.start, firstArgument.argument.start); 33 | hasSpreadElements = true; 34 | } 35 | } else { 36 | hasSpreadElements = spread( 37 | code, 38 | this.arguments, 39 | firstArgument.start, 40 | this.argumentsArrayAlias 41 | ); 42 | } 43 | 44 | if (hasSpreadElements) { 45 | // we need to handle super() and super.method() differently 46 | // due to its instance 47 | let _super = null; 48 | if (this.callee.type === 'Super') { 49 | _super = this.callee; 50 | } else if ( 51 | this.callee.type === 'MemberExpression' && 52 | this.callee.object.type === 'Super' 53 | ) { 54 | _super = this.callee.object; 55 | } 56 | 57 | if ( !_super && this.callee.type === 'MemberExpression' ) { 58 | if ( this.callee.object.type === 'Identifier' ) { 59 | const callee = this.callee.object; 60 | context = shouldPrependVm(callee) ? `_vm.${callee.name}` : callee.name; 61 | } else { 62 | context = this.findScope(true).createDeclaration('ref'); 63 | const callExpression = this.callee.object; 64 | code.prependRight(callExpression.start, `(${context} = `); 65 | code.appendLeft(callExpression.end, `)`); 66 | } 67 | } else { 68 | context = 'void 0'; 69 | } 70 | 71 | code.appendLeft(this.callee.end, '.apply'); 72 | 73 | if (_super) { 74 | _super.noCall = true; // bit hacky... 75 | 76 | if (this.arguments.length > 1) { 77 | if (firstArgument.type !== 'SpreadElement') { 78 | code.prependRight(firstArgument.start, `[ `); 79 | } 80 | 81 | code.appendLeft( 82 | this.arguments[this.arguments.length - 1].end, 83 | ' )' 84 | ); 85 | } 86 | } else if (this.arguments.length === 1) { 87 | code.prependRight(firstArgument.start, `${context}, `); 88 | } else { 89 | if (firstArgument.type === 'SpreadElement') { 90 | code.appendLeft(firstArgument.start, `${context}, `); 91 | } else { 92 | code.appendLeft(firstArgument.start, `${context}, [ `); 93 | } 94 | 95 | code.appendLeft(this.arguments[this.arguments.length - 1].end, ' )'); 96 | } 97 | } 98 | } 99 | 100 | if (transforms.trailingFunctionCommas && this.arguments.length) { 101 | removeTrailingComma(code, this.arguments[this.arguments.length - 1].end); 102 | } 103 | 104 | super.transpile(code, transforms); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/program/types/ClassBody.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import { findIndex } from '../../utils/array.js'; 3 | import reserved from '../../utils/reserved.js'; 4 | 5 | // TODO this code is pretty wild, tidy it up 6 | export default class ClassBody extends Node { 7 | transpile(code, transforms, inFunctionExpression, superName) { 8 | if (transforms.classes) { 9 | const name = this.parent.name; 10 | 11 | const indentStr = code.getIndentString(); 12 | const i0 = 13 | this.getIndentation() + (inFunctionExpression ? indentStr : ''); 14 | const i1 = i0 + indentStr; 15 | 16 | const constructorIndex = findIndex( 17 | this.body, 18 | node => node.kind === 'constructor' 19 | ); 20 | const constructor = this.body[constructorIndex]; 21 | 22 | let introBlock = ''; 23 | let outroBlock = ''; 24 | 25 | if (this.body.length) { 26 | code.remove(this.start, this.body[0].start); 27 | code.remove(this.body[this.body.length - 1].end, this.end); 28 | } else { 29 | code.remove(this.start, this.end); 30 | } 31 | 32 | if (constructor) { 33 | constructor.value.body.isConstructorBody = true; 34 | 35 | const previousMethod = this.body[constructorIndex - 1]; 36 | const nextMethod = this.body[constructorIndex + 1]; 37 | 38 | // ensure constructor is first 39 | if (constructorIndex > 0) { 40 | code.remove(previousMethod.end, constructor.start); 41 | code.move( 42 | constructor.start, 43 | nextMethod ? nextMethod.start : this.end - 1, 44 | this.body[0].start 45 | ); 46 | } 47 | 48 | if (!inFunctionExpression) code.appendLeft(constructor.end, ';'); 49 | } 50 | 51 | let namedFunctions = 52 | this.program.options.namedFunctionExpressions !== false; 53 | let namedConstructor = 54 | namedFunctions || 55 | this.parent.superClass || 56 | this.parent.type !== 'ClassDeclaration'; 57 | if (this.parent.superClass) { 58 | let inheritanceBlock = `if ( ${superName} ) ${name}.__proto__ = ${ 59 | superName 60 | };\n${i0}${name}.prototype = Object.create( ${superName} && ${ 61 | superName 62 | }.prototype );\n${i0}${name}.prototype.constructor = ${name};`; 63 | 64 | if (constructor) { 65 | introBlock += `\n\n${i0}` + inheritanceBlock; 66 | } else { 67 | const fn = 68 | `function ${name} () {` + 69 | (superName 70 | ? `\n${i1}${superName}.apply(this, arguments);\n${i0}}` 71 | : `}`) + 72 | (inFunctionExpression ? '' : ';') + 73 | (this.body.length ? `\n\n${i0}` : ''); 74 | 75 | inheritanceBlock = fn + inheritanceBlock; 76 | introBlock += inheritanceBlock + `\n\n${i0}`; 77 | } 78 | } else if (!constructor) { 79 | let fn = 'function ' + (namedConstructor ? name + ' ' : '') + '() {}'; 80 | if (this.parent.type === 'ClassDeclaration') fn += ';'; 81 | if (this.body.length) fn += `\n\n${i0}`; 82 | 83 | introBlock += fn; 84 | } 85 | 86 | const scope = this.findScope(false); 87 | 88 | let prototypeGettersAndSetters = []; 89 | let staticGettersAndSetters = []; 90 | let prototypeAccessors; 91 | let staticAccessors; 92 | 93 | this.body.forEach((method, i) => { 94 | if (method.kind === 'constructor') { 95 | let constructorName = namedConstructor ? ' ' + name : ''; 96 | code.overwrite( 97 | method.key.start, 98 | method.key.end, 99 | `function${constructorName}` 100 | ); 101 | return; 102 | } 103 | 104 | if (method.static) { 105 | const len = code.original[method.start + 6] == ' ' ? 7 : 6; 106 | code.remove(method.start, method.start + len); 107 | } 108 | 109 | const isAccessor = method.kind !== 'method'; 110 | let lhs; 111 | 112 | let methodName = method.key.name; 113 | if ( 114 | reserved[methodName] || 115 | method.value.body.scope.references[methodName] 116 | ) { 117 | methodName = scope.createIdentifier(methodName); 118 | } 119 | 120 | // when method name is a string or a number let's pretend it's a computed method 121 | 122 | let fake_computed = false; 123 | if (!method.computed && method.key.type === 'Literal') { 124 | fake_computed = true; 125 | method.computed = true; 126 | } 127 | 128 | if (isAccessor) { 129 | if (method.computed) { 130 | throw new Error( 131 | 'Computed accessor properties are not currently supported' 132 | ); 133 | } 134 | 135 | code.remove(method.start, method.key.start); 136 | 137 | if (method.static) { 138 | if (!~staticGettersAndSetters.indexOf(method.key.name)) 139 | staticGettersAndSetters.push(method.key.name); 140 | if (!staticAccessors) 141 | staticAccessors = scope.createIdentifier('staticAccessors'); 142 | 143 | lhs = `${staticAccessors}`; 144 | } else { 145 | if (!~prototypeGettersAndSetters.indexOf(method.key.name)) 146 | prototypeGettersAndSetters.push(method.key.name); 147 | if (!prototypeAccessors) 148 | prototypeAccessors = scope.createIdentifier('prototypeAccessors'); 149 | 150 | lhs = `${prototypeAccessors}`; 151 | } 152 | } else { 153 | lhs = method.static ? `${name}` : `${name}.prototype`; 154 | } 155 | 156 | if (!method.computed) lhs += '.'; 157 | 158 | const insertNewlines = 159 | (constructorIndex > 0 && i === constructorIndex + 1) || 160 | (i === 0 && constructorIndex === this.body.length - 1); 161 | 162 | if (insertNewlines) lhs = `\n\n${i0}${lhs}`; 163 | 164 | let c = method.key.end; 165 | if (method.computed) { 166 | if (fake_computed) { 167 | code.prependRight(method.key.start, '['); 168 | code.appendLeft(method.key.end, ']'); 169 | } else { 170 | while (code.original[c] !== ']') c += 1; 171 | c += 1; 172 | } 173 | } 174 | 175 | const funcName = 176 | method.computed || isAccessor || !namedFunctions 177 | ? '' 178 | : `${methodName} `; 179 | const rhs = 180 | (isAccessor ? `.${method.kind}` : '') + 181 | ` = function` + 182 | (method.value.generator ? '* ' : ' ') + 183 | funcName; 184 | code.remove(c, method.value.start); 185 | code.prependRight(method.value.start, rhs); 186 | code.appendLeft(method.end, ';'); 187 | 188 | if (method.value.generator) code.remove(method.start, method.key.start); 189 | 190 | code.prependRight(method.start, lhs); 191 | }); 192 | 193 | if (prototypeGettersAndSetters.length || staticGettersAndSetters.length) { 194 | let intro = []; 195 | let outro = []; 196 | 197 | if (prototypeGettersAndSetters.length) { 198 | intro.push( 199 | `var ${prototypeAccessors} = { ${prototypeGettersAndSetters 200 | .map(name => `${name}: { configurable: true }`) 201 | .join(',')} };` 202 | ); 203 | outro.push( 204 | `Object.defineProperties( ${name}.prototype, ${ 205 | prototypeAccessors 206 | } );` 207 | ); 208 | } 209 | 210 | if (staticGettersAndSetters.length) { 211 | intro.push( 212 | `var ${staticAccessors} = { ${staticGettersAndSetters 213 | .map(name => `${name}: { configurable: true }`) 214 | .join(',')} };` 215 | ); 216 | outro.push(`Object.defineProperties( ${name}, ${staticAccessors} );`); 217 | } 218 | 219 | if (constructor) introBlock += `\n\n${i0}`; 220 | introBlock += intro.join(`\n${i0}`); 221 | if (!constructor) introBlock += `\n\n${i0}`; 222 | 223 | outroBlock += `\n\n${i0}` + outro.join(`\n${i0}`); 224 | } 225 | 226 | if (constructor) { 227 | code.appendLeft(constructor.end, introBlock); 228 | } else { 229 | code.prependRight(this.start, introBlock); 230 | } 231 | 232 | code.appendLeft(this.end, outroBlock); 233 | } 234 | 235 | super.transpile(code, transforms); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/program/types/ClassDeclaration.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import deindent from '../../utils/deindent.js'; 3 | 4 | export default class ClassDeclaration extends Node { 5 | initialise(transforms) { 6 | if (this.id) { 7 | this.name = this.id.name; 8 | this.findScope(true).addDeclaration(this.id, 'class'); 9 | } else { 10 | this.name = this.findScope(true).createIdentifier("defaultExport"); 11 | } 12 | 13 | super.initialise(transforms); 14 | } 15 | 16 | transpile(code, transforms) { 17 | if (transforms.classes) { 18 | if (!this.superClass) deindent(this.body, code); 19 | 20 | const superName = 21 | this.superClass && (this.superClass.name || 'superclass'); 22 | 23 | const i0 = this.getIndentation(); 24 | const i1 = i0 + code.getIndentString(); 25 | 26 | // if this is an export default statement, we have to move the export to 27 | // after the declaration, because `export default var Foo = ...` is illegal 28 | const isExportDefaultDeclaration = this.parent.type === 'ExportDefaultDeclaration'; 29 | 30 | if (isExportDefaultDeclaration) { 31 | code.remove(this.parent.start, this.start); 32 | } 33 | 34 | let c = this.start; 35 | if (this.id) { 36 | code.overwrite(c, this.id.start, 'var '); 37 | c = this.id.end; 38 | } else { 39 | code.prependLeft(c, `var ${this.name}`); 40 | } 41 | 42 | if (this.superClass) { 43 | if (this.superClass.end === this.body.start) { 44 | code.remove(c, this.superClass.start); 45 | code.appendLeft(c, ` = (function (${superName}) {\n${i1}`); 46 | } else { 47 | code.overwrite(c, this.superClass.start, ' = '); 48 | code.overwrite( 49 | this.superClass.end, 50 | this.body.start, 51 | `(function (${superName}) {\n${i1}` 52 | ); 53 | } 54 | } else { 55 | if (c === this.body.start) { 56 | code.appendLeft(c, ' = '); 57 | } else { 58 | code.overwrite(c, this.body.start, ' = '); 59 | } 60 | } 61 | 62 | this.body.transpile(code, transforms, !!this.superClass, superName); 63 | 64 | const syntheticDefaultExport = 65 | isExportDefaultDeclaration 66 | ? `\n\n${i0}export default ${this.name};` 67 | : ''; 68 | if (this.superClass) { 69 | code.appendLeft(this.end, `\n\n${i1}return ${this.name};\n${i0}}(`); 70 | code.move(this.superClass.start, this.superClass.end, this.end); 71 | code.prependRight(this.end, `));${syntheticDefaultExport}`); 72 | } else if (syntheticDefaultExport) { 73 | code.prependRight(this.end, syntheticDefaultExport); 74 | } 75 | } else { 76 | this.body.transpile(code, transforms, false, null); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/program/types/ClassExpression.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | 3 | export default class ClassExpression extends Node { 4 | initialise(transforms) { 5 | this.name = ( this.id 6 | ? this.id.name 7 | : this.parent.type === 'VariableDeclarator' 8 | ? this.parent.id.name 9 | : this.parent.type !== 'AssignmentExpression' 10 | ? null 11 | : this.parent.left.type === 'Identifier' 12 | ? this.parent.left.name 13 | : this.parent.left.type === 'MemberExpression' 14 | ? this.parent.left.property.name 15 | : null ) || this.findScope(true).createIdentifier('anonymous'); 16 | 17 | super.initialise(transforms); 18 | } 19 | 20 | transpile(code, transforms) { 21 | if (transforms.classes) { 22 | const superName = 23 | this.superClass && (this.superClass.name || 'superclass'); 24 | 25 | const i0 = this.getIndentation(); 26 | const i1 = i0 + code.getIndentString(); 27 | 28 | if (this.superClass) { 29 | code.remove(this.start, this.superClass.start); 30 | code.remove(this.superClass.end, this.body.start); 31 | code.appendLeft(this.start, `(function (${superName}) {\n${i1}`); 32 | } else { 33 | code.overwrite(this.start, this.body.start, `(function () {\n${i1}`); 34 | } 35 | 36 | this.body.transpile(code, transforms, true, superName); 37 | 38 | const outro = `\n\n${i1}return ${this.name};\n${i0}}(`; 39 | 40 | if (this.superClass) { 41 | code.appendLeft(this.end, outro); 42 | code.move(this.superClass.start, this.superClass.end, this.end); 43 | code.prependRight(this.end, '))'); 44 | } else { 45 | code.appendLeft(this.end, `\n\n${i1}return ${this.name};\n${i0}}())`); 46 | } 47 | } else { 48 | this.body.transpile(code, transforms, false); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/program/types/ContinueStatement.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import CompileError from '../../utils/CompileError.js'; 3 | import { loopStatement } from '../../utils/patterns.js'; 4 | 5 | export default class ContinueStatement extends Node { 6 | transpile(code) { 7 | const loop = this.findNearest(loopStatement); 8 | if (loop.shouldRewriteAsFunction) { 9 | if (this.label) 10 | throw new CompileError( 11 | 'Labels are not currently supported in a loop with locally-scoped variables', 12 | this 13 | ); 14 | code.overwrite(this.start, this.start + 8, 'return'); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/program/types/ExportDefaultDeclaration.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import CompileError from '../../utils/CompileError.js'; 3 | 4 | export default class ExportDefaultDeclaration extends Node { 5 | initialise(transforms) { 6 | if (transforms.moduleExport) 7 | throw new CompileError('export is not supported', this); 8 | super.initialise(transforms); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/program/types/ExportNamedDeclaration.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import CompileError from '../../utils/CompileError.js'; 3 | 4 | export default class ExportNamedDeclaration extends Node { 5 | initialise(transforms) { 6 | if (transforms.moduleExport) 7 | throw new CompileError('export is not supported', this); 8 | super.initialise(transforms); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/program/types/ForInStatement.js: -------------------------------------------------------------------------------- 1 | import LoopStatement from './shared/LoopStatement.js'; 2 | import destructure from '../../utils/destructure.js'; 3 | import extractNames from '../extractNames.js'; 4 | 5 | export default class ForInStatement extends LoopStatement { 6 | findScope(functionScope) { 7 | return functionScope || !this.createdScope 8 | ? this.parent.findScope(functionScope) 9 | : this.body.scope; 10 | } 11 | 12 | transpile(code, transforms) { 13 | const hasDeclaration = this.left.type === 'VariableDeclaration'; 14 | 15 | if (this.shouldRewriteAsFunction) { 16 | // which variables are declared in the init statement? 17 | const names = 18 | hasDeclaration 19 | ? [].concat.apply( 20 | [], 21 | this.left.declarations.map(declarator => 22 | extractNames(declarator.id) 23 | ) 24 | ) 25 | : []; 26 | 27 | this.args = names.map( 28 | name => (name in this.aliases ? this.aliases[name].outer : name) 29 | ); 30 | this.params = names.map( 31 | name => (name in this.aliases ? this.aliases[name].inner : name) 32 | ); 33 | } 34 | 35 | super.transpile(code, transforms); 36 | 37 | const maybePattern = hasDeclaration ? this.left.declarations[0].id : this.left; 38 | if (maybePattern.type !== 'Identifier') { 39 | this.destructurePattern(code, maybePattern, hasDeclaration); 40 | } 41 | } 42 | 43 | destructurePattern(code, pattern, isDeclaration) { 44 | const scope = this.findScope(true); 45 | const i0 = this.getIndentation(); 46 | const i1 = i0 + code.getIndentString(); 47 | 48 | const ref = scope.createIdentifier('ref'); 49 | 50 | const bodyStart = this.body.body.length ? this.body.body[0].start : this.body.start + 1; 51 | 52 | code.move(pattern.start, pattern.end, bodyStart); 53 | 54 | code.prependRight(pattern.end, isDeclaration ? ref : `var ${ref}`); 55 | 56 | let statementGenerators = []; 57 | destructure( 58 | code, 59 | id => scope.createIdentifier(id), 60 | ({ name }) => scope.resolveName(name), 61 | pattern, 62 | ref, 63 | false, 64 | statementGenerators 65 | ); 66 | 67 | let suffix = `;\n${i1}`; 68 | statementGenerators.forEach((fn, i) => { 69 | if (i === statementGenerators.length - 1) { 70 | suffix = `;\n\n${i1}`; 71 | } 72 | 73 | fn(bodyStart, '', suffix); 74 | }); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/program/types/ForOfStatement.js: -------------------------------------------------------------------------------- 1 | import LoopStatement from './shared/LoopStatement.js'; 2 | import CompileError from '../../utils/CompileError.js'; 3 | import destructure from '../../utils/destructure.js'; 4 | 5 | export default class ForOfStatement extends LoopStatement { 6 | initialise(transforms) { 7 | if (transforms.forOf && !transforms.dangerousForOf) 8 | throw new CompileError( 9 | "for...of statements are not supported. Use `transforms: { forOf: false }` to skip transformation and disable this error, or `transforms: { dangerousForOf: true }` if you know what you're doing", 10 | this 11 | ); 12 | super.initialise(transforms); 13 | } 14 | 15 | transpile(code, transforms) { 16 | super.transpile(code, transforms); 17 | if (!transforms.dangerousForOf) return; 18 | 19 | // edge case (#80) 20 | if (!this.body.body[0]) { 21 | if ( 22 | this.left.type === 'VariableDeclaration' && 23 | this.left.kind === 'var' 24 | ) { 25 | code.remove(this.start, this.left.start); 26 | code.appendLeft(this.left.end, ';'); 27 | code.remove(this.left.end, this.end); 28 | } else { 29 | code.remove(this.start, this.end); 30 | } 31 | 32 | return; 33 | } 34 | 35 | const scope = this.findScope(true); 36 | const i0 = this.getIndentation(); 37 | const i1 = i0 + code.getIndentString(); 38 | 39 | const key = scope.createIdentifier('i'); 40 | const list = scope.createIdentifier('list'); 41 | 42 | if (this.body.synthetic) { 43 | code.prependRight(this.left.start, `{\n${i1}`); 44 | code.appendLeft(this.body.body[0].end, `\n${i0}}`); 45 | } 46 | 47 | const bodyStart = this.body.body[0].start; 48 | 49 | code.remove(this.left.end, this.right.start); 50 | code.move(this.left.start, this.left.end, bodyStart); 51 | 52 | code.prependRight(this.right.start, `var ${key} = 0, ${list} = `); 53 | code.appendLeft(this.right.end, `; ${key} < ${list}.length; ${key} += 1`); 54 | 55 | const isDeclaration = this.left.type === 'VariableDeclaration'; 56 | const maybeDestructuring = isDeclaration ? this.left.declarations[0].id : this.left; 57 | if (maybeDestructuring.type !== 'Identifier') { 58 | let statementGenerators = []; 59 | const ref = scope.createIdentifier('ref'); 60 | destructure( 61 | code, 62 | id => scope.createIdentifier(id), 63 | ({ name }) => scope.resolveName(name), 64 | maybeDestructuring, 65 | ref, 66 | !isDeclaration, 67 | statementGenerators 68 | ); 69 | 70 | let suffix = `;\n${i1}`; 71 | statementGenerators.forEach((fn, i) => { 72 | if (i === statementGenerators.length - 1) { 73 | suffix = `;\n\n${i1}`; 74 | } 75 | 76 | fn(bodyStart, '', suffix); 77 | }); 78 | 79 | if (isDeclaration) { 80 | code.appendLeft(this.left.start + this.left.kind.length + 1, ref); 81 | code.appendLeft(this.left.end, ` = ${list}[${key}];\n${i1}`); 82 | } else { 83 | code.appendLeft(this.left.end, `var ${ref} = ${list}[${key}];\n${i1}`); 84 | } 85 | } else { 86 | code.appendLeft(this.left.end, ` = ${list}[${key}];\n\n${i1}`); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/program/types/ForStatement.js: -------------------------------------------------------------------------------- 1 | import LoopStatement from './shared/LoopStatement.js'; 2 | import extractNames from '../extractNames.js'; 3 | 4 | export default class ForStatement extends LoopStatement { 5 | findScope(functionScope) { 6 | return functionScope || !this.createdScope 7 | ? this.parent.findScope(functionScope) 8 | : this.body.scope; 9 | } 10 | 11 | transpile(code, transforms) { 12 | const i1 = this.getIndentation() + code.getIndentString(); 13 | 14 | if (this.shouldRewriteAsFunction) { 15 | // which variables are declared in the init statement? 16 | const names = 17 | this.init.type === 'VariableDeclaration' 18 | ? [].concat.apply( 19 | [], 20 | this.init.declarations.map(declarator => 21 | extractNames(declarator.id) 22 | ) 23 | ) 24 | : []; 25 | 26 | const aliases = this.aliases; 27 | 28 | this.args = names.map( 29 | name => (name in this.aliases ? this.aliases[name].outer : name) 30 | ); 31 | this.params = names.map( 32 | name => (name in this.aliases ? this.aliases[name].inner : name) 33 | ); 34 | 35 | const updates = Object.keys(this.reassigned).map( 36 | name => `${aliases[name].outer} = ${aliases[name].inner};` 37 | ); 38 | 39 | if (updates.length) { 40 | if (this.body.synthetic) { 41 | code.appendLeft(this.body.body[0].end, `; ${updates.join(` `)}`); 42 | } else { 43 | const lastStatement = this.body.body[this.body.body.length - 1]; 44 | code.appendLeft( 45 | lastStatement.end, 46 | `\n\n${i1}${updates.join(`\n${i1}`)}` 47 | ); 48 | } 49 | } 50 | } 51 | 52 | super.transpile(code, transforms); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/program/types/FunctionDeclaration.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import CompileError from '../../utils/CompileError.js'; 3 | import removeTrailingComma from '../../utils/removeTrailingComma.js'; 4 | 5 | export default class FunctionDeclaration extends Node { 6 | initialise(transforms) { 7 | if (this.generator && transforms.generator) { 8 | throw new CompileError('Generators are not supported', this); 9 | } 10 | 11 | this.body.createScope(); 12 | 13 | if (this.id) { 14 | this.findScope(true).addDeclaration(this.id, 'function'); 15 | } 16 | super.initialise(transforms); 17 | } 18 | 19 | transpile(code, transforms) { 20 | super.transpile(code, transforms); 21 | if (transforms.trailingFunctionCommas && this.params.length) { 22 | removeTrailingComma(code, this.params[this.params.length - 1].end); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/program/types/FunctionExpression.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import CompileError from '../../utils/CompileError.js'; 3 | import removeTrailingComma from '../../utils/removeTrailingComma.js'; 4 | 5 | export default class FunctionExpression extends Node { 6 | initialise(transforms) { 7 | if (this.generator && transforms.generator) { 8 | throw new CompileError('Generators are not supported', this); 9 | } 10 | 11 | this.body.createScope(); 12 | 13 | if (this.id) { 14 | // function expression IDs belong to the child scope... 15 | this.body.scope.addDeclaration(this.id, 'function'); 16 | } 17 | 18 | super.initialise(transforms); 19 | 20 | const parent = this.parent; 21 | let methodName; 22 | 23 | if ( 24 | transforms.conciseMethodProperty && 25 | parent.type === 'Property' && 26 | parent.kind === 'init' && 27 | parent.method && 28 | parent.key.type === 'Identifier' 29 | ) { 30 | // object literal concise method 31 | methodName = parent.key.name; 32 | } else if ( 33 | transforms.classes && 34 | parent.type === 'MethodDefinition' && 35 | parent.kind === 'method' && 36 | parent.key.type === 'Identifier' 37 | ) { 38 | // method definition in a class 39 | methodName = parent.key.name; 40 | } else if (this.id && this.id.type === 'Identifier') { 41 | // naked function expression 42 | methodName = this.id.alias || this.id.name; 43 | } 44 | 45 | if (methodName) { 46 | for (const param of this.params) { 47 | if (param.type === 'Identifier' && methodName === param.name) { 48 | // workaround for Safari 9/WebKit bug: 49 | // https://gitlab.com/Rich-Harris/buble/issues/154 50 | // change parameter name when same as method name 51 | 52 | const scope = this.body.scope; 53 | const declaration = scope.declarations[methodName]; 54 | 55 | const alias = scope.createIdentifier(methodName); 56 | param.alias = alias; 57 | 58 | for (const identifier of declaration.instances) { 59 | identifier.alias = alias; 60 | } 61 | 62 | break; 63 | } 64 | } 65 | } 66 | } 67 | 68 | transpile(code, transforms) { 69 | super.transpile(code, transforms); 70 | if (transforms.trailingFunctionCommas && this.params.length) { 71 | removeTrailingComma(code, this.params[this.params.length - 1].end); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/program/types/Identifier.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import isReference from '../../utils/isReference.js'; 3 | import { loopStatement } from '../../utils/patterns.js'; 4 | import { shouldPrependVm } from '../../utils/prependVm.js'; 5 | 6 | export default class Identifier extends Node { 7 | findScope(functionScope) { 8 | if (this.parent.params && ~this.parent.params.indexOf(this)) { 9 | return this.parent.body.scope; 10 | } 11 | 12 | if (this.parent.type === 'FunctionExpression' && this === this.parent.id) { 13 | return this.parent.body.scope; 14 | } 15 | 16 | return this.parent.findScope(functionScope); 17 | } 18 | 19 | initialise(transforms) { 20 | if (isReference(this, this.parent)) { 21 | if ( 22 | transforms.arrow && 23 | this.name === 'arguments' && 24 | !this.findScope(false).contains(this.name) 25 | ) { 26 | const lexicalBoundary = this.findLexicalBoundary(); 27 | const arrowFunction = this.findNearest('ArrowFunctionExpression'); 28 | const loop = this.findNearest(loopStatement); 29 | 30 | if (arrowFunction && arrowFunction.depth > lexicalBoundary.depth) { 31 | this.alias = lexicalBoundary.getArgumentsAlias(); 32 | } 33 | 34 | if ( 35 | loop && 36 | loop.body.contains(this) && 37 | loop.depth > lexicalBoundary.depth 38 | ) { 39 | this.alias = lexicalBoundary.getArgumentsAlias(); 40 | } 41 | } 42 | 43 | this.findScope(false).addReference(this); 44 | } 45 | } 46 | 47 | transpile(code) { 48 | if (this.alias) { 49 | code.overwrite(this.start, this.end, this.alias, { 50 | storeName: true, 51 | contentOnly: true 52 | }); 53 | } 54 | 55 | if (shouldPrependVm(this)) { 56 | code.overwrite(this.start, this.end, `_vm.${this.name}`, { 57 | storeName: true, 58 | contentOnly: true 59 | }); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/program/types/IfStatement.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | 3 | export default class IfStatement extends Node { 4 | initialise(transforms) { 5 | super.initialise(transforms); 6 | } 7 | 8 | transpile(code, transforms) { 9 | if ( 10 | this.consequent.type !== 'BlockStatement' || 11 | (this.consequent.type === 'BlockStatement' && this.consequent.synthetic) 12 | ) { 13 | code.appendLeft(this.consequent.start, '{ '); 14 | code.prependRight(this.consequent.end, ' }'); 15 | } 16 | 17 | if ( 18 | this.alternate && 19 | this.alternate.type !== 'IfStatement' && 20 | (this.alternate.type !== 'BlockStatement' || 21 | (this.alternate.type === 'BlockStatement' && this.alternate.synthetic)) 22 | ) { 23 | code.appendLeft(this.alternate.start, '{ '); 24 | code.prependRight(this.alternate.end, ' }'); 25 | } 26 | 27 | super.transpile(code, transforms); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/program/types/ImportDeclaration.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import CompileError from '../../utils/CompileError.js'; 3 | 4 | export default class ImportDeclaration extends Node { 5 | initialise(transforms) { 6 | if (transforms.moduleImport) 7 | throw new CompileError('import is not supported', this); 8 | super.initialise(transforms); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/program/types/ImportDefaultSpecifier.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | 3 | export default class ImportDefaultSpecifier extends Node { 4 | initialise(transforms) { 5 | this.findScope(true).addDeclaration(this.local, 'import'); 6 | super.initialise(transforms); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/program/types/ImportSpecifier.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | 3 | export default class ImportSpecifier extends Node { 4 | initialise(transforms) { 5 | this.findScope(true).addDeclaration(this.local, 'import'); 6 | super.initialise(transforms); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/program/types/JSXAttribute.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | 3 | const hasDashes = val => /-/.test(val); 4 | 5 | const formatKey = key => (hasDashes(key) ? `'${key}'` : key); 6 | 7 | const formatVal = val => (val ? '' : 'true'); 8 | 9 | export default class JSXAttribute extends Node { 10 | transpile(code, transforms) { 11 | const { start, name } = this.name; 12 | 13 | // Overwrite equals sign if value is present. 14 | const end = this.value ? this.value.start : this.name.end; 15 | 16 | code.overwrite(start, end, `${formatKey(name)}: ${formatVal(this.value)}`); 17 | 18 | super.transpile(code, transforms); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/program/types/JSXClosingElement.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | 3 | function containsNewLine(node) { 4 | return ( 5 | node.type === 'JSXText' && !/\S/.test(node.value) && /\n/.test(node.value) 6 | ); 7 | } 8 | 9 | export default class JSXClosingElement extends Node { 10 | transpile(code) { 11 | let spaceBeforeParen = true; 12 | 13 | const lastChild = this.parent.children[this.parent.children.length - 1]; 14 | 15 | // omit space before closing paren if 16 | // a) this is on a separate line, or 17 | // b) there are no children but there are attributes 18 | if ( 19 | (lastChild && containsNewLine(lastChild)) || 20 | this.parent.openingElement.attributes.length 21 | ) { 22 | spaceBeforeParen = false; 23 | } 24 | 25 | code.overwrite(this.start, this.end, spaceBeforeParen ? ' )' : ')'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/program/types/JSXClosingFragment.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | 3 | function containsNewLine(node) { 4 | return ( 5 | node.type === 'JSXText' && !/\S/.test(node.value) && /\n/.test(node.value) 6 | ); 7 | } 8 | 9 | export default class JSXClosingFragment extends Node { 10 | transpile(code) { 11 | let spaceBeforeParen = true; 12 | 13 | const lastChild = this.parent.children[this.parent.children.length - 1]; 14 | 15 | // omit space before closing paren if this is on a separate line 16 | if (lastChild && containsNewLine(lastChild)) { 17 | spaceBeforeParen = false; 18 | } 19 | 20 | code.overwrite(this.start, this.end, spaceBeforeParen ? ' )' : ')'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/program/types/JSXElement.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | 3 | function normalise(str, removeTrailingWhitespace) { 4 | 5 | str = str.replace(/\u00a0/g, ' '); 6 | 7 | if (removeTrailingWhitespace && /\n/.test(str)) { 8 | str = str.replace(/\s+$/, ''); 9 | } 10 | 11 | str = str 12 | .replace(/^\n\r?\s+/, '') // remove leading newline + space 13 | .replace(/\s*\n\r?\s*/gm, ' '); // replace newlines with spaces 14 | 15 | // TODO prefer single quotes? 16 | return JSON.stringify(str); 17 | } 18 | 19 | export default class JSXElement extends Node { 20 | transpile(code, transforms) { 21 | super.transpile(code, transforms); 22 | 23 | const children = this.children.filter(child => { 24 | if (child.type !== 'JSXText') return true; 25 | 26 | // remove whitespace-only literals, unless on a single line 27 | return /\S/.test(child.raw) || !/\n/.test(child.raw); 28 | }); 29 | 30 | if (children.length) { 31 | let c = this.openingElement.end; 32 | 33 | let i; 34 | for (i = 0; i < children.length; i += 1) { 35 | const child = children[i]; 36 | 37 | if ( 38 | child.type === 'JSXExpressionContainer' && 39 | child.expression.type === 'JSXEmptyExpression' 40 | ) { 41 | // empty block is a no op 42 | } else { 43 | const tail = 44 | code.original[c] === '\n' && child.type !== 'JSXText' ? '' : ' '; 45 | code.appendLeft(c, `,${tail}`); 46 | } 47 | 48 | if (child.type === 'JSXText') { 49 | const str = normalise(child.value, i === children.length - 1); 50 | code.overwrite(child.start, child.end, str); 51 | } 52 | 53 | c = child.end; 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/program/types/JSXExpressionContainer.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | 3 | export default class JSXExpressionContainer extends Node { 4 | transpile(code, transforms) { 5 | code.remove(this.start, this.expression.start); 6 | code.remove(this.expression.end, this.end); 7 | 8 | super.transpile(code, transforms); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/program/types/JSXFragment.js: -------------------------------------------------------------------------------- 1 | import JSXElement from './JSXElement.js'; 2 | 3 | export default class JSXFragment extends JSXElement { 4 | } 5 | -------------------------------------------------------------------------------- /src/program/types/JSXOpeningElement.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import CompileError from '../../utils/CompileError.js'; 3 | 4 | export default class JSXOpeningElement extends Node { 5 | transpile(code, transforms) { 6 | super.transpile(code, transforms); 7 | 8 | code.overwrite(this.start, this.name.start, `${this.program.jsx}( `); 9 | 10 | const html = 11 | this.name.type === 'JSXIdentifier' && 12 | this.name.name[0] === this.name.name[0].toLowerCase(); 13 | if (html) code.prependRight(this.name.start, `'`); 14 | 15 | const len = this.attributes.length; 16 | let c = this.name.end; 17 | 18 | if (len) { 19 | let i; 20 | 21 | let hasSpread = false; 22 | for (i = 0; i < len; i += 1) { 23 | if (this.attributes[i].type === 'JSXSpreadAttribute') { 24 | hasSpread = true; 25 | break; 26 | } 27 | } 28 | 29 | c = this.attributes[0].end; 30 | 31 | for (i = 0; i < len; i += 1) { 32 | const attr = this.attributes[i]; 33 | 34 | if (i > 0) { 35 | if (attr.start === c) code.prependRight(c, ', '); 36 | else code.overwrite(c, attr.start, ', '); 37 | } 38 | 39 | if (hasSpread && attr.type !== 'JSXSpreadAttribute') { 40 | const lastAttr = this.attributes[i - 1]; 41 | const nextAttr = this.attributes[i + 1]; 42 | 43 | if (!lastAttr || lastAttr.type === 'JSXSpreadAttribute') { 44 | code.prependRight(attr.start, '{ '); 45 | } 46 | 47 | if (!nextAttr || nextAttr.type === 'JSXSpreadAttribute') { 48 | code.appendLeft(attr.end, ' }'); 49 | } 50 | } 51 | 52 | c = attr.end; 53 | } 54 | 55 | let after; 56 | let before; 57 | if (hasSpread) { 58 | if (len === 1) { 59 | before = html ? `',` : ','; 60 | } else { 61 | if (!this.program.options.objectAssign) { 62 | throw new CompileError( 63 | "Mixed JSX attributes ending in spread requires specified objectAssign option with 'Object.assign' or polyfill helper.", 64 | this 65 | ); 66 | } 67 | before = html 68 | ? `', ${this.program.options.objectAssign}({},` 69 | : `, ${this.program.options.objectAssign}({},`; 70 | after = ')'; 71 | } 72 | } else { 73 | before = html ? `', {` : ', {'; 74 | after = ' }'; 75 | } 76 | 77 | code.prependRight(this.name.end, before); 78 | 79 | if (after) { 80 | code.appendLeft(this.attributes[len - 1].end, after); 81 | } 82 | } else { 83 | code.appendLeft(this.name.end, html ? `', null` : `, null`); 84 | c = this.name.end; 85 | } 86 | 87 | if (this.selfClosing) { 88 | code.overwrite(c, this.end, this.attributes.length ? `)` : ` )`); 89 | } else { 90 | code.remove(c, this.end); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/program/types/JSXOpeningFragment.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | 3 | export default class JSXOpeningFragment extends Node { 4 | transpile(code, transforms) { 5 | code.overwrite(this.start, this.end, `${this.program.jsx}( React.Fragment, null`); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/program/types/JSXSpreadAttribute.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | 3 | export default class JSXSpreadAttribute extends Node { 4 | transpile(code, transforms) { 5 | code.remove(this.start, this.argument.start); 6 | code.remove(this.argument.end, this.end); 7 | 8 | super.transpile(code, transforms); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/program/types/Literal.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import CompileError from '../../utils/CompileError.js'; 3 | import rewritePattern from 'regexpu-core'; 4 | 5 | export default class Literal extends Node { 6 | initialise() { 7 | if (typeof this.value === 'string') { 8 | this.program.indentExclusionElements.push(this); 9 | } 10 | } 11 | 12 | transpile(code, transforms) { 13 | if (transforms.numericLiteral) { 14 | const leading = this.raw.slice(0, 2); 15 | if (leading === '0b' || leading === '0o') { 16 | code.overwrite(this.start, this.end, String(this.value), { 17 | storeName: true, 18 | contentOnly: true 19 | }); 20 | } 21 | } 22 | 23 | if (this.regex) { 24 | const { pattern, flags } = this.regex; 25 | 26 | if (transforms.stickyRegExp && /y/.test(flags)) 27 | throw new CompileError( 28 | 'Regular expression sticky flag is not supported', 29 | this 30 | ); 31 | if (transforms.unicodeRegExp && /u/.test(flags)) { 32 | code.overwrite( 33 | this.start, 34 | this.end, 35 | `/${rewritePattern(pattern, flags)}/${flags.replace('u', '')}`, 36 | { 37 | contentOnly: true 38 | } 39 | ); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/program/types/MemberExpression.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import reserved from '../../utils/reserved.js'; 3 | 4 | export default class MemberExpression extends Node { 5 | transpile(code, transforms) { 6 | if (transforms.reservedProperties && reserved[this.property.name]) { 7 | code.overwrite(this.object.end, this.property.start, `['`); 8 | code.appendLeft(this.property.end, `']`); 9 | } 10 | 11 | super.transpile(code, transforms); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/program/types/NewExpression.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import spread, { isArguments } from '../../utils/spread.js'; 3 | import removeTrailingComma from '../../utils/removeTrailingComma.js'; 4 | 5 | export default class NewExpression extends Node { 6 | initialise(transforms) { 7 | if (transforms.spreadRest && this.arguments.length) { 8 | const lexicalBoundary = this.findLexicalBoundary(); 9 | 10 | let i = this.arguments.length; 11 | while (i--) { 12 | const arg = this.arguments[i]; 13 | if (arg.type === 'SpreadElement' && isArguments(arg.argument)) { 14 | this.argumentsArrayAlias = lexicalBoundary.getArgumentsArrayAlias(); 15 | break; 16 | } 17 | } 18 | } 19 | 20 | super.initialise(transforms); 21 | } 22 | 23 | transpile(code, transforms) { 24 | super.transpile(code, transforms); 25 | 26 | if (transforms.spreadRest && this.arguments.length) { 27 | const firstArgument = this.arguments[0]; 28 | const isNew = true; 29 | let hasSpreadElements = spread( 30 | code, 31 | this.arguments, 32 | firstArgument.start, 33 | this.argumentsArrayAlias, 34 | isNew 35 | ); 36 | 37 | if (hasSpreadElements) { 38 | code.prependRight( 39 | this.start + 'new'.length, 40 | ' (Function.prototype.bind.apply(' 41 | ); 42 | code.overwrite( 43 | this.callee.end, 44 | firstArgument.start, 45 | ', [ null ].concat( ' 46 | ); 47 | code.appendLeft(this.end, ' ))'); 48 | } 49 | } 50 | 51 | if (this.arguments.length) { 52 | removeTrailingComma(code, this.arguments[this.arguments.length - 1].end); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/program/types/ObjectExpression.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import CompileError from '../../utils/CompileError.js'; 3 | 4 | export default class ObjectExpression extends Node { 5 | transpile(code, transforms) { 6 | super.transpile(code, transforms); 7 | 8 | let firstPropertyStart = this.start + 1; 9 | let regularPropertyCount = 0; 10 | let spreadPropertyCount = 0; 11 | let computedPropertyCount = 0; 12 | let firstSpreadProperty = null; 13 | let firstComputedProperty = null; 14 | 15 | for (let i = 0; i < this.properties.length; ++i) { 16 | const prop = this.properties[i]; 17 | if (prop.type === 'SpreadElement') { 18 | spreadPropertyCount += 1; 19 | if (firstSpreadProperty === null) firstSpreadProperty = i; 20 | } else if (prop.computed) { 21 | computedPropertyCount += 1; 22 | if (firstComputedProperty === null) firstComputedProperty = i; 23 | } else if (prop.type === 'Property') { 24 | regularPropertyCount += 1; 25 | } 26 | } 27 | 28 | if (spreadPropertyCount) { 29 | if (!this.program.options.objectAssign) { 30 | throw new CompileError( 31 | "Object spread operator requires specified objectAssign option with 'Object.assign' or polyfill helper.", 32 | this 33 | ); 34 | } 35 | // enclose run of non-spread properties in curlies 36 | let i = this.properties.length; 37 | if (regularPropertyCount && !computedPropertyCount) { 38 | while (i--) { 39 | const prop = this.properties[i]; 40 | 41 | if (prop.type === 'Property' && !prop.computed) { 42 | const lastProp = this.properties[i - 1]; 43 | const nextProp = this.properties[i + 1]; 44 | 45 | if ( 46 | !lastProp || 47 | lastProp.type !== 'Property' || 48 | lastProp.computed 49 | ) { 50 | code.prependRight(prop.start, '{'); 51 | } 52 | 53 | if ( 54 | !nextProp || 55 | nextProp.type !== 'Property' || 56 | nextProp.computed 57 | ) { 58 | code.appendLeft(prop.end, '}'); 59 | } 60 | } 61 | } 62 | } 63 | 64 | // wrap the whole thing in Object.assign 65 | firstPropertyStart = this.properties[0].start; 66 | if (!computedPropertyCount) { 67 | code.overwrite( 68 | this.start, 69 | firstPropertyStart, 70 | `${this.program.options.objectAssign}({}, ` 71 | ); 72 | code.overwrite( 73 | this.properties[this.properties.length - 1].end, 74 | this.end, 75 | ')' 76 | ); 77 | } else if (this.properties[0].type === 'SpreadElement') { 78 | code.overwrite( 79 | this.start, 80 | firstPropertyStart, 81 | `${this.program.options.objectAssign}({}, ` 82 | ); 83 | code.remove(this.end - 1, this.end); 84 | code.appendRight(this.end, ')'); 85 | } else { 86 | code.prependLeft(this.start, `${this.program.options.objectAssign}(`); 87 | code.appendRight(this.end, ')'); 88 | } 89 | } 90 | 91 | if (computedPropertyCount && transforms.computedProperty) { 92 | const i0 = this.getIndentation(); 93 | 94 | let isSimpleAssignment; 95 | let name; 96 | 97 | if ( 98 | this.parent.type === 'VariableDeclarator' && 99 | this.parent.parent.declarations.length === 1 && 100 | this.parent.id.type === 'Identifier' 101 | ) { 102 | isSimpleAssignment = true; 103 | name = this.parent.id.alias || this.parent.id.name; // TODO is this right? 104 | } else if ( 105 | this.parent.type === 'AssignmentExpression' && 106 | this.parent.parent.type === 'ExpressionStatement' && 107 | this.parent.left.type === 'Identifier' 108 | ) { 109 | isSimpleAssignment = true; 110 | name = this.parent.left.alias || this.parent.left.name; // TODO is this right? 111 | } else if ( 112 | this.parent.type === 'AssignmentPattern' && 113 | this.parent.left.type === 'Identifier' 114 | ) { 115 | isSimpleAssignment = true; 116 | name = this.parent.left.alias || this.parent.left.name; // TODO is this right? 117 | } 118 | 119 | if (spreadPropertyCount) isSimpleAssignment = false; 120 | 121 | // handle block scoping 122 | name = this.findScope(false).resolveName(name); 123 | 124 | const start = firstPropertyStart; 125 | const end = this.end; 126 | 127 | if (isSimpleAssignment) { 128 | // ??? 129 | } else { 130 | if ( 131 | firstSpreadProperty === null || 132 | firstComputedProperty < firstSpreadProperty 133 | ) { 134 | name = this.findScope(true).createDeclaration('_obj'); 135 | 136 | code.prependRight(this.start, `( ${name} = `); 137 | } else name = null; // We don't actually need this variable 138 | } 139 | 140 | const len = this.properties.length; 141 | let lastComputedProp; 142 | let sawNonComputedProperty = false; 143 | let isFirst = true; 144 | 145 | for (let i = 0; i < len; i += 1) { 146 | const prop = this.properties[i]; 147 | let moveStart = i > 0 ? this.properties[i - 1].end : start; 148 | 149 | if ( 150 | prop.type === 'Property' && 151 | (prop.computed || (lastComputedProp && !spreadPropertyCount)) 152 | ) { 153 | if (i === 0) moveStart = this.start + 1; // Trim leading whitespace 154 | lastComputedProp = prop; 155 | 156 | if (!name) { 157 | name = this.findScope(true).createDeclaration('_obj'); 158 | 159 | const propId = name + (prop.computed ? '' : '.'); 160 | code.appendRight(prop.start, `( ${name} = {}, ${propId}`); 161 | } else { 162 | const propId = 163 | (isSimpleAssignment ? `;\n${i0}${name}` : `, ${name}`) + 164 | (prop.key.type === 'Literal' || prop.computed ? '' : '.'); 165 | 166 | if (moveStart < prop.start) { 167 | code.overwrite(moveStart, prop.start, propId); 168 | } else { 169 | code.prependRight(prop.start, propId); 170 | } 171 | } 172 | 173 | let c = prop.key.end; 174 | if (prop.computed) { 175 | while (code.original[c] !== ']') c += 1; 176 | c += 1; 177 | } 178 | if (prop.key.type === 'Literal' && !prop.computed) { 179 | code.overwrite( 180 | prop.start, 181 | prop.key.end + 1, 182 | '[' + code.slice(prop.start, prop.key.end) + '] = ' 183 | ); 184 | } else if (prop.shorthand || (prop.method && !prop.computed && transforms.conciseMethodProperty)) { 185 | // Replace : with = if Property::transpile inserted the : 186 | code.overwrite( 187 | prop.key.start, 188 | prop.key.end, 189 | code.slice(prop.key.start, prop.key.end).replace(/:/, ' =') 190 | ); 191 | } else { 192 | if (prop.value.start > c) code.remove(c, prop.value.start); 193 | code.prependLeft(c, ' = '); 194 | } 195 | 196 | // This duplicates behavior from Property::transpile which is disabled 197 | // for computed properties or if conciseMethodProperty is false 198 | if (prop.method && (prop.computed || !transforms.conciseMethodProperty)) { 199 | if (prop.value.generator) code.remove(prop.start, prop.key.start); 200 | code.prependRight(prop.value.start, `function${prop.value.generator ? '*' : ''} `); 201 | } 202 | } else if (prop.type === 'SpreadElement') { 203 | if (name && i > 0) { 204 | if (!lastComputedProp) { 205 | lastComputedProp = this.properties[i - 1]; 206 | } 207 | code.appendLeft(lastComputedProp.end, `, ${name} )`); 208 | 209 | lastComputedProp = null; 210 | name = null; 211 | } 212 | } else { 213 | if (!isFirst && spreadPropertyCount) { 214 | // We are in an Object.assign context, so we need to wrap regular properties 215 | code.prependRight(prop.start, '{'); 216 | code.appendLeft(prop.end, '}'); 217 | } 218 | sawNonComputedProperty = true; 219 | } 220 | if (isFirst && (prop.type === 'SpreadElement' || prop.computed)) { 221 | let beginEnd = sawNonComputedProperty 222 | ? this.properties[this.properties.length - 1].end 223 | : this.end - 1; 224 | // Trim trailing comma because it can easily become a leading comma which is illegal 225 | if (code.original[beginEnd] == ',') ++beginEnd; 226 | const closing = code.slice(beginEnd, end); 227 | code.prependLeft(moveStart, closing); 228 | code.remove(beginEnd, end); 229 | isFirst = false; 230 | } 231 | 232 | // Clean up some extranous whitespace 233 | let c = prop.end; 234 | if (i < len - 1 && !sawNonComputedProperty) { 235 | while (code.original[c] !== ',') c += 1; 236 | } else if (i == len - 1) c = this.end; 237 | code.remove(prop.end, c); 238 | } 239 | 240 | // special case 241 | if (computedPropertyCount === len) { 242 | code.remove(this.properties[len - 1].end, this.end - 1); 243 | } 244 | 245 | if (!isSimpleAssignment && name) { 246 | code.appendLeft(lastComputedProp.end, `, ${name} )`); 247 | } 248 | } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/program/types/Property.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import reserved from '../../utils/reserved.js'; 3 | import globals from '../../utils/globals.js' 4 | 5 | export default class Property extends Node { 6 | transpile(code, transforms) { 7 | super.transpile(code, transforms); 8 | 9 | if ( 10 | transforms.conciseMethodProperty && 11 | !this.computed && 12 | this.parent.type !== 'ObjectPattern' 13 | ) { 14 | if (this.shorthand) { 15 | code.prependRight(this.start, `${this.key.name}: ${this.shouldPrefix() ? '_vm.' : ''}`); 16 | } else if (this.method) { 17 | let name = ''; 18 | if (this.program.options.namedFunctionExpressions !== false) { 19 | if ( 20 | this.key.type === 'Literal' && 21 | typeof this.key.value === 'number' 22 | ) { 23 | name = ''; 24 | } else if (this.key.type === 'Identifier') { 25 | if ( 26 | reserved[this.key.name] || 27 | !/^[a-z_$][a-z0-9_$]*$/i.test(this.key.name) || 28 | this.value.body.scope.references[this.key.name] 29 | ) { 30 | name = this.findScope(true).createIdentifier(this.key.name); 31 | } else { 32 | name = this.key.name; 33 | } 34 | } else { 35 | name = this.findScope(true).createIdentifier(this.key.value); 36 | } 37 | name = ' ' + name; 38 | } 39 | 40 | if (this.value.generator) code.remove(this.start, this.key.start); 41 | code.appendLeft( 42 | this.key.end, 43 | `: function${this.value.generator ? '*' : ''}${name}` 44 | ); 45 | } 46 | } 47 | 48 | if (transforms.reservedProperties && reserved[this.key.name]) { 49 | code.prependRight(this.key.start, `'`); 50 | code.appendLeft(this.key.end, `'`); 51 | } 52 | } 53 | 54 | shouldPrefix () { 55 | if ( 56 | this.program.inWith > 0 && 57 | !globals[this.key.name] && 58 | !this.findScope(false).contains(this.key.name) 59 | ) { 60 | return true 61 | } 62 | return false 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/program/types/ReturnStatement.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import { loopStatement } from '../../utils/patterns.js'; 3 | 4 | export default class ReturnStatement extends Node { 5 | initialise(transforms) { 6 | this.loop = this.findNearest(loopStatement); 7 | this.nearestFunction = this.findNearest(/Function/); 8 | 9 | if ( 10 | this.loop && 11 | (!this.nearestFunction || this.loop.depth > this.nearestFunction.depth) 12 | ) { 13 | this.loop.canReturn = true; 14 | this.shouldWrap = true; 15 | } 16 | 17 | if (this.argument) this.argument.initialise(transforms); 18 | } 19 | 20 | transpile(code, transforms) { 21 | const shouldWrap = 22 | this.shouldWrap && this.loop && this.loop.shouldRewriteAsFunction; 23 | 24 | if (this.argument) { 25 | if (shouldWrap) code.prependRight(this.argument.start, `{ v: `); 26 | this.argument.transpile(code, transforms); 27 | if (shouldWrap) code.appendLeft(this.argument.end, ` }`); 28 | } else if (shouldWrap) { 29 | code.appendLeft(this.start + 6, ' {}'); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/program/types/SpreadElement.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | 3 | export default class SpreadElement extends Node { 4 | transpile(code, transforms) { 5 | if (this.parent.type == 'ObjectExpression') { 6 | code.remove(this.start, this.argument.start); 7 | code.remove(this.argument.end, this.end); 8 | } 9 | 10 | super.transpile(code, transforms); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/program/types/Super.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import CompileError from '../../utils/CompileError.js'; 3 | import { loopStatement } from '../../utils/patterns.js'; 4 | 5 | export default class Super extends Node { 6 | initialise(transforms) { 7 | if (transforms.classes) { 8 | this.method = this.findNearest('MethodDefinition'); 9 | if (!this.method) 10 | throw new CompileError('use of super outside class method', this); 11 | 12 | const parentClass = this.findNearest('ClassBody').parent; 13 | this.superClassName = 14 | parentClass.superClass && (parentClass.superClass.name || 'superclass'); 15 | 16 | if (!this.superClassName) 17 | throw new CompileError('super used in base class', this); 18 | 19 | this.isCalled = 20 | this.parent.type === 'CallExpression' && this === this.parent.callee; 21 | 22 | if (this.method.kind !== 'constructor' && this.isCalled) { 23 | throw new CompileError( 24 | 'super() not allowed outside class constructor', 25 | this 26 | ); 27 | } 28 | 29 | this.isMember = this.parent.type === 'MemberExpression'; 30 | 31 | if (!this.isCalled && !this.isMember) { 32 | throw new CompileError( 33 | 'Unexpected use of `super` (expected `super(...)` or `super.*`)', 34 | this 35 | ); 36 | } 37 | } 38 | 39 | if (transforms.arrow) { 40 | const lexicalBoundary = this.findLexicalBoundary(); 41 | const arrowFunction = this.findNearest('ArrowFunctionExpression'); 42 | const loop = this.findNearest(loopStatement); 43 | 44 | if (arrowFunction && arrowFunction.depth > lexicalBoundary.depth) { 45 | this.thisAlias = lexicalBoundary.getThisAlias(); 46 | } 47 | 48 | if ( 49 | loop && 50 | loop.body.contains(this) && 51 | loop.depth > lexicalBoundary.depth 52 | ) { 53 | this.thisAlias = lexicalBoundary.getThisAlias(); 54 | } 55 | } 56 | } 57 | 58 | transpile(code, transforms) { 59 | if (transforms.classes) { 60 | const expression = 61 | this.isCalled || this.method.static 62 | ? this.superClassName 63 | : `${this.superClassName}.prototype`; 64 | 65 | code.overwrite(this.start, this.end, expression, { 66 | storeName: true, 67 | contentOnly: true 68 | }); 69 | 70 | const callExpression = this.isCalled ? this.parent : this.parent.parent; 71 | 72 | if (callExpression && callExpression.type === 'CallExpression') { 73 | if (!this.noCall) { 74 | // special case – `super( ...args )` 75 | code.appendLeft(callExpression.callee.end, '.call'); 76 | } 77 | 78 | const thisAlias = this.thisAlias || 'this'; 79 | 80 | if (callExpression.arguments.length) { 81 | code.appendLeft(callExpression.arguments[0].start, `${thisAlias}, `); 82 | } else { 83 | code.appendLeft(callExpression.end - 1, `${thisAlias}`); 84 | } 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/program/types/TaggedTemplateExpression.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import CompileError from '../../utils/CompileError.js'; 3 | 4 | export default class TaggedTemplateExpression extends Node { 5 | initialise(transforms) { 6 | if ( 7 | transforms.templateString && 8 | !transforms.dangerousTaggedTemplateString 9 | ) { 10 | throw new CompileError( 11 | "Tagged template strings are not supported. Use `transforms: { templateString: false }` to skip transformation and disable this error, or `transforms: { dangerousTaggedTemplateString: true }` if you know what you're doing", 12 | this 13 | ); 14 | } 15 | 16 | super.initialise(transforms); 17 | } 18 | 19 | transpile(code, transforms) { 20 | if (transforms.templateString && transforms.dangerousTaggedTemplateString) { 21 | const ordered = this.quasi.expressions 22 | .concat(this.quasi.quasis) 23 | .sort((a, b) => a.start - b.start); 24 | 25 | const program = this.program; 26 | const rootScope = program.body.scope; 27 | 28 | // insert strings at start 29 | const templateStrings = this.quasi.quasis.map(quasi => 30 | JSON.stringify(quasi.value.cooked) 31 | ).join(', '); 32 | 33 | let templateObject = this.program.templateLiteralQuasis[templateStrings]; 34 | if (!templateObject) { 35 | templateObject = rootScope.createIdentifier('templateObject'); 36 | code.prependRight(this.program.prependAt, `var ${templateObject} = Object.freeze([${templateStrings}]);\n`); 37 | 38 | this.program.templateLiteralQuasis[templateStrings] = templateObject; 39 | } 40 | 41 | code.overwrite( 42 | this.tag.end, 43 | ordered[0].start, 44 | `(${templateObject}` 45 | ); 46 | 47 | let lastIndex = ordered[0].start; 48 | ordered.forEach(node => { 49 | if (node.type === 'TemplateElement') { 50 | code.remove(lastIndex, node.end); 51 | } else { 52 | code.overwrite(lastIndex, node.start, ', '); 53 | } 54 | 55 | lastIndex = node.end; 56 | }); 57 | 58 | code.overwrite(lastIndex, this.end, ')'); 59 | } 60 | 61 | super.transpile(code, transforms); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/program/types/TemplateElement.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | 3 | export default class TemplateElement extends Node { 4 | initialise() { 5 | this.program.indentExclusionElements.push(this); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/program/types/TemplateLiteral.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | 3 | export default class TemplateLiteral extends Node { 4 | transpile(code, transforms) { 5 | super.transpile(code, transforms); 6 | 7 | if ( 8 | transforms.templateString && 9 | this.parent.type !== 'TaggedTemplateExpression' 10 | ) { 11 | let ordered = this.expressions 12 | .concat(this.quasis) 13 | .sort((a, b) => a.start - b.start || a.end - b.end) 14 | .filter((node, i) => { 15 | // include all expressions 16 | if (node.type !== 'TemplateElement') return true; 17 | 18 | // include all non-empty strings 19 | if (node.value.raw) return true; 20 | 21 | // exclude all empty strings not at the head 22 | return !i; 23 | }); 24 | 25 | // special case – we may be able to skip the first element, 26 | // if it's the empty string, but only if the second and 27 | // third elements aren't both expressions (since they maybe 28 | // be numeric, and `1 + 2 + '3' === '33'`) 29 | if (ordered.length >= 3) { 30 | const [first, , third] = ordered; 31 | if ( 32 | first.type === 'TemplateElement' && 33 | first.value.raw === '' && 34 | third.type === 'TemplateElement' 35 | ) { 36 | ordered.shift(); 37 | } 38 | } 39 | 40 | const parenthesise = 41 | (this.quasis.length !== 1 || this.expressions.length !== 0) && 42 | this.parent.type !== 'TemplateLiteral' && 43 | this.parent.type !== 'AssignmentExpression' && 44 | this.parent.type !== 'AssignmentPattern' && 45 | this.parent.type !== 'VariableDeclarator' && 46 | (this.parent.type !== 'BinaryExpression' || 47 | this.parent.operator !== '+'); 48 | 49 | if (parenthesise) code.appendRight(this.start, '('); 50 | 51 | let lastIndex = this.start; 52 | 53 | ordered.forEach((node, i) => { 54 | let prefix = i === 0 ? (parenthesise ? '(' : '') : ' + '; 55 | 56 | if (node.type === 'TemplateElement') { 57 | code.overwrite( 58 | lastIndex, 59 | node.end, 60 | prefix + JSON.stringify(node.value.cooked) 61 | ); 62 | } else { 63 | const parenthesise = node.type !== 'Identifier'; // TODO other cases where it's safe 64 | 65 | if (parenthesise) prefix += '('; 66 | 67 | code.remove(lastIndex, node.start); 68 | 69 | if (prefix) code.prependRight(node.start, prefix); 70 | if (parenthesise) code.appendLeft(node.end, ')'); 71 | } 72 | 73 | lastIndex = node.end; 74 | }); 75 | 76 | if (parenthesise) code.appendLeft(lastIndex, ')'); 77 | code.overwrite(lastIndex, this.end, "", { contentOnly: true } ); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/program/types/ThisExpression.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import { loopStatement } from '../../utils/patterns.js'; 3 | 4 | export default class ThisExpression extends Node { 5 | initialise(transforms) { 6 | if (transforms.arrow) { 7 | const lexicalBoundary = this.findLexicalBoundary(); 8 | const arrowFunction = this.findNearest('ArrowFunctionExpression'); 9 | const loop = this.findNearest(loopStatement); 10 | 11 | if ( 12 | (arrowFunction && arrowFunction.depth > lexicalBoundary.depth) || 13 | (loop && 14 | loop.body.contains(this) && 15 | loop.depth > lexicalBoundary.depth) || 16 | (loop && loop.right && loop.right.contains(this)) 17 | ) { 18 | this.alias = lexicalBoundary.getThisAlias(); 19 | } 20 | } 21 | } 22 | 23 | transpile(code) { 24 | if (this.alias) { 25 | code.overwrite(this.start, this.end, this.alias, { 26 | storeName: true, 27 | contentOnly: true 28 | }); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/program/types/UpdateExpression.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import checkConst from '../../utils/checkConst.js'; 3 | 4 | export default class UpdateExpression extends Node { 5 | initialise(transforms) { 6 | if (this.argument.type === 'Identifier') { 7 | const declaration = this.findScope(false).findDeclaration( 8 | this.argument.name 9 | ); 10 | // special case – https://gitlab.com/Rich-Harris/buble/issues/150 11 | const statement = declaration && declaration.node.ancestor(3); 12 | if ( 13 | statement && 14 | statement.type === 'ForStatement' && 15 | statement.body.contains(this) 16 | ) { 17 | statement.reassigned[this.argument.name] = true; 18 | } 19 | } 20 | 21 | super.initialise(transforms); 22 | } 23 | 24 | transpile(code, transforms) { 25 | if (this.argument.type === 'Identifier') { 26 | // Do this check after everything has been initialized to find 27 | // shadowing declarations after this expression 28 | checkConst(this.argument, this.findScope(false)); 29 | } 30 | super.transpile(code, transforms); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/program/types/VariableDeclaration.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | import destructure from '../../utils/destructure.js'; 3 | import { loopStatement } from '../../utils/patterns.js'; 4 | 5 | export default class VariableDeclaration extends Node { 6 | initialise(transforms) { 7 | this.scope = this.findScope(this.kind === 'var'); 8 | this.declarations.forEach(declarator => declarator.initialise(transforms)); 9 | } 10 | 11 | transpile(code, transforms) { 12 | const i0 = this.getIndentation(); 13 | let kind = this.kind; 14 | 15 | if (transforms.letConst && kind !== 'var') { 16 | kind = 'var'; 17 | code.overwrite(this.start, this.start + this.kind.length, kind, { 18 | storeName: true 19 | }); 20 | } 21 | 22 | if (transforms.destructuring && this.parent.type !== 'ForOfStatement' && this.parent.type !== 'ForInStatement') { 23 | let c = this.start; 24 | let lastDeclaratorIsPattern; 25 | 26 | this.declarations.forEach((declarator, i) => { 27 | declarator.transpile(code, transforms); 28 | 29 | if (declarator.id.type === 'Identifier') { 30 | if (i > 0 && this.declarations[i - 1].id.type !== 'Identifier') { 31 | code.overwrite(c, declarator.id.start, `var `); 32 | } 33 | } else { 34 | const inline = loopStatement.test(this.parent.type); 35 | 36 | if (i === 0) { 37 | code.remove(c, declarator.id.start); 38 | } else { 39 | code.overwrite(c, declarator.id.start, `;\n${i0}`); 40 | } 41 | 42 | const simple = 43 | declarator.init.type === 'Identifier' && !declarator.init.rewritten; 44 | 45 | const name = simple 46 | ? (declarator.init.alias || declarator.init.name) 47 | : declarator.findScope(true).createIdentifier('ref'); 48 | 49 | c = declarator.start; 50 | 51 | let statementGenerators = []; 52 | 53 | if (simple) { 54 | code.remove(declarator.id.end, declarator.end); 55 | } else { 56 | statementGenerators.push((start, prefix, suffix) => { 57 | code.prependRight(declarator.id.end, `var ${name}`); 58 | code.appendLeft(declarator.init.end, `${suffix}`); 59 | code.move(declarator.id.end, declarator.end, start); 60 | }); 61 | } 62 | 63 | const scope = declarator.findScope(false); 64 | destructure( 65 | code, 66 | id => scope.createIdentifier(id), 67 | ({ name }) => scope.resolveName(name), 68 | declarator.id, 69 | name, 70 | inline, 71 | statementGenerators 72 | ); 73 | 74 | let prefix = inline ? 'var ' : ''; 75 | let suffix = inline ? `, ` : `;\n${i0}`; 76 | statementGenerators.forEach((fn, j) => { 77 | if ( 78 | i === this.declarations.length - 1 && 79 | j === statementGenerators.length - 1 80 | ) { 81 | suffix = inline ? '' : ';'; 82 | } 83 | 84 | fn(declarator.start, j === 0 ? prefix : '', suffix); 85 | }); 86 | } 87 | 88 | c = declarator.end; 89 | lastDeclaratorIsPattern = declarator.id.type !== 'Identifier'; 90 | }); 91 | 92 | if (lastDeclaratorIsPattern && this.end > c) { 93 | code.overwrite(c, this.end, '', { contentOnly: true }); 94 | } 95 | } else { 96 | this.declarations.forEach(declarator => { 97 | declarator.transpile(code, transforms); 98 | }); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/program/types/VariableDeclarator.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js'; 2 | 3 | export default class VariableDeclarator extends Node { 4 | initialise(transforms) { 5 | let kind = this.parent.kind; 6 | if (kind === 'let' && this.parent.parent.type === 'ForStatement') { 7 | kind = 'for.let'; // special case... 8 | } 9 | 10 | this.parent.scope.addDeclaration(this.id, kind); 11 | super.initialise(transforms); 12 | } 13 | 14 | transpile(code, transforms) { 15 | if (!this.init && transforms.letConst && this.parent.kind !== 'var') { 16 | let inLoop = this.findNearest( 17 | /Function|^For(In|Of)?Statement|^(?:Do)?WhileStatement/ 18 | ); 19 | if ( 20 | inLoop && 21 | !/Function/.test(inLoop.type) && 22 | !this.isLeftDeclaratorOfLoop() 23 | ) { 24 | code.appendLeft(this.id.end, ' = (void 0)'); 25 | } 26 | } 27 | 28 | if (this.id) this.id.transpile(code, transforms); 29 | if (this.init) this.init.transpile(code, transforms); 30 | } 31 | 32 | isLeftDeclaratorOfLoop() { 33 | return ( 34 | this.parent && 35 | this.parent.type === 'VariableDeclaration' && 36 | this.parent.parent && 37 | (this.parent.parent.type === 'ForInStatement' || 38 | this.parent.parent.type === 'ForOfStatement') && 39 | this.parent.parent.left && 40 | this.parent.parent.left.declarations[0] === this 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/program/types/WithStatement.js: -------------------------------------------------------------------------------- 1 | import Node from '../Node.js' 2 | 3 | export default class WithStatement extends Node { 4 | transpile (code, transforms) { 5 | if (transforms.stripWith) { 6 | this.program.inWith = (this.program.inWith || 0) + 1 7 | // remove surrounding with block 8 | code.remove(this.start, this.body.start + 1) 9 | code.remove(this.end - 1, this.end) 10 | if (transforms.stripWithFunctional) { 11 | code.prependRight(this.start, "var _c=_vm._c;") 12 | } else { 13 | code.prependRight(this.start, `var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;`) 14 | } 15 | super.transpile(code, transforms) 16 | this.program.inWith-- 17 | } else { 18 | super.transpile(code, transforms) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/program/types/index.js: -------------------------------------------------------------------------------- 1 | import ArrayExpression from './ArrayExpression.js'; 2 | import ArrowFunctionExpression from './ArrowFunctionExpression.js'; 3 | import AssignmentExpression from './AssignmentExpression.js'; 4 | import BinaryExpression from './BinaryExpression.js'; 5 | import BreakStatement from './BreakStatement.js'; 6 | import CallExpression from './CallExpression.js'; 7 | import ClassBody from './ClassBody.js'; 8 | import ClassDeclaration from './ClassDeclaration.js'; 9 | import ClassExpression from './ClassExpression.js'; 10 | import ContinueStatement from './ContinueStatement.js'; 11 | import ExportDefaultDeclaration from './ExportDefaultDeclaration.js'; 12 | import ExportNamedDeclaration from './ExportNamedDeclaration.js'; 13 | import ForStatement from './ForStatement.js'; 14 | import ForInStatement from './ForInStatement.js'; 15 | import ForOfStatement from './ForOfStatement.js'; 16 | import FunctionDeclaration from './FunctionDeclaration.js'; 17 | import FunctionExpression from './FunctionExpression.js'; 18 | import Identifier from './Identifier.js'; 19 | import IfStatement from './IfStatement.js'; 20 | import ImportDeclaration from './ImportDeclaration.js'; 21 | import ImportDefaultSpecifier from './ImportDefaultSpecifier.js'; 22 | import ImportSpecifier from './ImportSpecifier.js'; 23 | import JSXAttribute from './JSXAttribute.js'; 24 | import JSXClosingElement from './JSXClosingElement.js'; 25 | import JSXClosingFragment from './JSXClosingFragment.js'; 26 | import JSXElement from './JSXElement.js'; 27 | import JSXExpressionContainer from './JSXExpressionContainer.js'; 28 | import JSXFragment from './JSXFragment.js'; 29 | import JSXOpeningElement from './JSXOpeningElement.js'; 30 | import JSXOpeningFragment from './JSXOpeningFragment.js'; 31 | import JSXSpreadAttribute from './JSXSpreadAttribute.js'; 32 | import Literal from './Literal.js'; 33 | import LoopStatement from './shared/LoopStatement.js'; 34 | import MemberExpression from './MemberExpression.js'; 35 | import NewExpression from './NewExpression.js'; 36 | import ObjectExpression from './ObjectExpression.js'; 37 | import Property from './Property.js'; 38 | import ReturnStatement from './ReturnStatement.js'; 39 | import SpreadElement from './SpreadElement.js'; 40 | import Super from './Super.js'; 41 | import TaggedTemplateExpression from './TaggedTemplateExpression.js'; 42 | import TemplateElement from './TemplateElement.js'; 43 | import TemplateLiteral from './TemplateLiteral.js'; 44 | import ThisExpression from './ThisExpression.js'; 45 | import UpdateExpression from './UpdateExpression.js'; 46 | import VariableDeclaration from './VariableDeclaration.js'; 47 | import VariableDeclarator from './VariableDeclarator.js'; 48 | import WithStatement from './WithStatement.js'; 49 | 50 | export default { 51 | ArrayExpression, 52 | ArrowFunctionExpression, 53 | AssignmentExpression, 54 | BinaryExpression, 55 | BreakStatement, 56 | CallExpression, 57 | ClassBody, 58 | ClassDeclaration, 59 | ClassExpression, 60 | ContinueStatement, 61 | DoWhileStatement: LoopStatement, 62 | ExportNamedDeclaration, 63 | ExportDefaultDeclaration, 64 | ForStatement, 65 | ForInStatement, 66 | ForOfStatement, 67 | FunctionDeclaration, 68 | FunctionExpression, 69 | Identifier, 70 | IfStatement, 71 | ImportDeclaration, 72 | ImportDefaultSpecifier, 73 | ImportSpecifier, 74 | JSXAttribute, 75 | JSXClosingElement, 76 | JSXClosingFragment, 77 | JSXElement, 78 | JSXExpressionContainer, 79 | JSXFragment, 80 | JSXOpeningElement, 81 | JSXOpeningFragment, 82 | JSXSpreadAttribute, 83 | Literal, 84 | MemberExpression, 85 | NewExpression, 86 | ObjectExpression, 87 | Property, 88 | ReturnStatement, 89 | SpreadElement, 90 | Super, 91 | TaggedTemplateExpression, 92 | TemplateElement, 93 | TemplateLiteral, 94 | ThisExpression, 95 | UpdateExpression, 96 | VariableDeclaration, 97 | VariableDeclarator, 98 | WhileStatement: LoopStatement, 99 | WithStatement 100 | }; 101 | -------------------------------------------------------------------------------- /src/program/types/shared/LoopStatement.js: -------------------------------------------------------------------------------- 1 | import Node from '../../Node.js'; 2 | 3 | export default class LoopStatement extends Node { 4 | findScope(functionScope) { 5 | return functionScope || !this.createdScope 6 | ? this.parent.findScope(functionScope) 7 | : this.body.scope; 8 | } 9 | 10 | initialise(transforms) { 11 | this.body.createScope(); 12 | this.createdScope = true; 13 | 14 | // this is populated as and when reassignments occur 15 | this.reassigned = Object.create(null); 16 | this.aliases = Object.create(null); 17 | 18 | super.initialise(transforms); 19 | 20 | if (transforms.letConst) { 21 | // see if any block-scoped declarations are referenced 22 | // inside function expressions 23 | const names = Object.keys(this.body.scope.declarations); 24 | 25 | let i = names.length; 26 | while (i--) { 27 | const name = names[i]; 28 | const declaration = this.body.scope.declarations[name]; 29 | 30 | let j = declaration.instances.length; 31 | while (j--) { 32 | const instance = declaration.instances[j]; 33 | const nearestFunctionExpression = instance.findNearest(/Function/); 34 | 35 | if ( 36 | nearestFunctionExpression && 37 | nearestFunctionExpression.depth > this.depth 38 | ) { 39 | this.shouldRewriteAsFunction = true; 40 | break; 41 | } 42 | } 43 | 44 | if (this.shouldRewriteAsFunction) break; 45 | } 46 | } 47 | } 48 | 49 | transpile(code, transforms) { 50 | const needsBlock = 51 | this.type != 'ForOfStatement' && 52 | (this.body.type !== 'BlockStatement' || 53 | (this.body.type === 'BlockStatement' && this.body.synthetic)); 54 | 55 | if (this.shouldRewriteAsFunction) { 56 | const i0 = this.getIndentation(); 57 | const i1 = i0 + code.getIndentString(); 58 | 59 | const argString = this.args ? ` ${this.args.join(', ')} ` : ''; 60 | const paramString = this.params ? ` ${this.params.join(', ')} ` : ''; 61 | 62 | const functionScope = this.findScope(true); 63 | const loop = functionScope.createIdentifier('loop'); 64 | 65 | const before = 66 | `var ${loop} = function (${paramString}) ` + 67 | (this.body.synthetic ? `{\n${i0}${code.getIndentString()}` : ''); 68 | const after = (this.body.synthetic ? `\n${i0}}` : '') + `;\n\n${i0}`; 69 | 70 | code.prependRight(this.body.start, before); 71 | code.appendLeft(this.body.end, after); 72 | code.move(this.start, this.body.start, this.body.end); 73 | 74 | if (this.canBreak || this.canReturn) { 75 | const returned = functionScope.createIdentifier('returned'); 76 | 77 | let insert = `{\n${i1}var ${returned} = ${loop}(${argString});\n`; 78 | if (this.canBreak) 79 | insert += `\n${i1}if ( ${returned} === 'break' ) break;`; 80 | if (this.canReturn) 81 | insert += `\n${i1}if ( ${returned} ) return ${returned}.v;`; 82 | insert += `\n${i0}}`; 83 | 84 | code.prependRight(this.body.end, insert); 85 | } else { 86 | const callExpression = `${loop}(${argString});`; 87 | 88 | if (this.type === 'DoWhileStatement') { 89 | code.overwrite( 90 | this.start, 91 | this.body.start, 92 | `do {\n${i1}${callExpression}\n${i0}}` 93 | ); 94 | } else { 95 | code.prependRight(this.body.end, callExpression); 96 | } 97 | } 98 | } else if (needsBlock) { 99 | code.appendLeft(this.body.start, '{ '); 100 | code.prependRight(this.body.end, ' }'); 101 | } 102 | 103 | super.transpile(code, transforms); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/program/types/shared/ModuleDeclaration.js: -------------------------------------------------------------------------------- 1 | import Node from '../../Node.js'; 2 | import CompileError from '../../../utils/CompileError.js'; 3 | 4 | export default class ModuleDeclaration extends Node { 5 | initialise(transforms) { 6 | if (transforms.moduleImport) 7 | throw new CompileError('Modules are not supported', this); 8 | super.initialise(transforms); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/program/wrap.js: -------------------------------------------------------------------------------- 1 | import types from './types/index.js'; 2 | import BlockStatement from './BlockStatement.js'; 3 | import Node from './Node.js'; 4 | import keys from './keys.js'; 5 | 6 | const statementsWithBlocks = { 7 | IfStatement: 'consequent', 8 | ForStatement: 'body', 9 | ForInStatement: 'body', 10 | ForOfStatement: 'body', 11 | WhileStatement: 'body', 12 | DoWhileStatement: 'body', 13 | ArrowFunctionExpression: 'body' 14 | }; 15 | 16 | export default function wrap(raw, parent) { 17 | if (!raw) return; 18 | 19 | if ('length' in raw) { 20 | let i = raw.length; 21 | while (i--) wrap(raw[i], parent); 22 | return; 23 | } 24 | 25 | // with e.g. shorthand properties, key and value are 26 | // the same node. We don't want to wrap an object twice 27 | if (raw.__wrapped) return; 28 | raw.__wrapped = true; 29 | 30 | if (!keys[raw.type]) { 31 | keys[raw.type] = Object.keys(raw).filter( 32 | key => typeof raw[key] === 'object' 33 | ); 34 | } 35 | 36 | // special case – body-less if/for/while statements. TODO others? 37 | const bodyType = statementsWithBlocks[raw.type]; 38 | if (bodyType && raw[bodyType].type !== 'BlockStatement') { 39 | const expression = raw[bodyType]; 40 | 41 | // create a synthetic block statement, otherwise all hell 42 | // breaks loose when it comes to block scoping 43 | raw[bodyType] = { 44 | start: expression.start, 45 | end: expression.end, 46 | type: 'BlockStatement', 47 | body: [expression], 48 | synthetic: true 49 | }; 50 | } 51 | 52 | raw.parent = parent; 53 | raw.program = parent.program || parent; 54 | raw.depth = parent.depth + 1; 55 | raw.keys = keys[raw.type]; 56 | raw.indentation = undefined; 57 | 58 | for (const key of keys[raw.type]) { 59 | wrap(raw[key], raw); 60 | } 61 | 62 | raw.program.magicString.addSourcemapLocation(raw.start); 63 | raw.program.magicString.addSourcemapLocation(raw.end); 64 | 65 | const type = 66 | (raw.type === 'BlockStatement' ? BlockStatement : types[raw.type]) || Node; 67 | raw.__proto__ = type.prototype; 68 | } 69 | -------------------------------------------------------------------------------- /src/support.js: -------------------------------------------------------------------------------- 1 | export const matrix = { 2 | chrome: { 3 | 48: 0b01001010100011001101, 4 | 49: 0b01001111100111111111, 5 | 50: 0b01011111100111111111, 6 | 51: 0b01011111100111111111, 7 | 52: 0b01111111100111111111, 8 | 53: 0b01111111100111111111, 9 | 54: 0b01111111100111111111, 10 | 55: 0b01111111100111111111, 11 | 56: 0b01111111100111111111, 12 | 57: 0b01111111100111111111, 13 | 58: 0b11111111100111111111, 14 | 59: 0b11111111100111111111, 15 | 60: 0b11111111100111111111, 16 | 61: 0b11111111100111111111, 17 | 62: 0b11111111100111111111, 18 | 63: 0b11111111100111111111 19 | }, 20 | firefox: { 21 | 43: 0b01001111100011011101, 22 | 44: 0b01001111100111011101, 23 | 45: 0b01001111100111011111, 24 | 46: 0b01011111100111011111, 25 | 47: 0b01011111100111111111, 26 | 48: 0b01011111100111111111, 27 | 49: 0b01011110100111111111, 28 | 50: 0b01011110100111111111, 29 | 51: 0b01011110100111111111, 30 | 52: 0b11111111100111111111, 31 | 53: 0b11111111100111111111, 32 | 54: 0b11111111100111111111, 33 | 55: 0b11111111100111111111, 34 | 56: 0b11111111100111111111, 35 | 57: 0b11111111100111111111, 36 | 58: 0b11111111100111111111 37 | }, 38 | safari: { 39 | 8: 0b01000000000000000100, 40 | 9: 0b01001001100001101110, 41 | 10: 0b11011111100111111111, 42 | '10.1': 0b11111111100111111111, 43 | 11: 0b11111111100111111111 44 | }, 45 | ie: { 46 | 8: 0b00000000000000000000, 47 | 9: 0b01000000000000000000, 48 | 10: 0b01000000000000000000, 49 | 11: 0b01000000000100000000 50 | }, 51 | edge: { 52 | 12: 0b01001010100101001101, 53 | 13: 0b01011110100111001111, 54 | 14: 0b11111110100111111111, 55 | 15: 0b11111110100111111111, 56 | 16: 0b11111110100111111111 57 | }, 58 | node: { 59 | '0.10': 0b01000000000000000000, 60 | '0.12': 0b01000000000001000000, 61 | 4: 0b01001000100011001111, 62 | 5: 0b01001000100011001111, 63 | 6: 0b01011111100111111111, 64 | 8: 0b11111111100111111111, 65 | '8.3': 0b11111111100111111111, 66 | '8.7': 0b11111111100111111111 67 | } 68 | }; 69 | 70 | export const features = [ 71 | 'arrow', 72 | 'classes', 73 | 'computedProperty', 74 | 'conciseMethodProperty', 75 | 'defaultParameter', 76 | 'destructuring', 77 | 'forOf', 78 | 'generator', 79 | 'letConst', 80 | 'moduleExport', 81 | 'moduleImport', 82 | 'numericLiteral', 83 | 'parameterDestructuring', 84 | 'spreadRest', 85 | 'stickyRegExp', 86 | 'templateString', 87 | 'unicodeRegExp', 88 | 89 | // ES2016 90 | 'exponentiation', 91 | 92 | // additional transforms, not from 93 | // https://featuretests.io 94 | 'reservedProperties', 95 | 96 | 'trailingFunctionCommas' 97 | ]; 98 | -------------------------------------------------------------------------------- /src/utils/CompileError.js: -------------------------------------------------------------------------------- 1 | import locate from './locate.js'; 2 | import getSnippet from './getSnippet.js'; 3 | 4 | export default class CompileError extends Error { 5 | constructor(message, node) { 6 | super(message); 7 | 8 | this.name = 'CompileError'; 9 | if (!node) { 10 | return; 11 | } 12 | 13 | const source = node.program.magicString.original; 14 | const loc = locate(source, node.start); 15 | 16 | this.message = message + ` (${loc.line}:${loc.column})`; 17 | 18 | this.stack = new Error().stack.replace( 19 | new RegExp(`.+new ${this.name}.+\\n`, 'm'), 20 | '' 21 | ); 22 | 23 | this.loc = loc; 24 | this.snippet = getSnippet(source, loc, node.end - node.start); 25 | } 26 | 27 | toString() { 28 | return `${this.name}: ${this.message}\n${this.snippet}`; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/utils/array.js: -------------------------------------------------------------------------------- 1 | export function findIndex(array, fn) { 2 | for (let i = 0; i < array.length; i += 1) { 3 | if (fn(array[i], i)) return i; 4 | } 5 | 6 | return -1; 7 | } 8 | 9 | export function find(array, fn) { 10 | return array[findIndex(array, fn)]; 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/checkConst.js: -------------------------------------------------------------------------------- 1 | import CompileError from './CompileError.js'; 2 | 3 | export default function checkConst(identifier, scope) { 4 | const declaration = scope.findDeclaration(identifier.name); 5 | if (declaration && declaration.kind === 'const') { 6 | throw new CompileError(`${identifier.name} is read-only`, identifier); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/deindent.js: -------------------------------------------------------------------------------- 1 | // TODO this function is slightly flawed – it works on the original string, 2 | // not its current edited state. 3 | // That's not a problem for the way that it's currently used, but it could 4 | // be in future... 5 | export default function deindent(node, code) { 6 | const start = node.start; 7 | const end = node.end; 8 | 9 | const indentStr = code.getIndentString(); 10 | const indentStrLen = indentStr.length; 11 | const indentStart = start - indentStrLen; 12 | 13 | if ( 14 | !node.program.indentExclusions[indentStart] && 15 | code.original.slice(indentStart, start) === indentStr 16 | ) { 17 | code.remove(indentStart, start); 18 | } 19 | 20 | const pattern = new RegExp(indentStr + '\\S', 'g'); 21 | const slice = code.original.slice(start, end); 22 | let match; 23 | 24 | while ((match = pattern.exec(slice))) { 25 | const removeStart = start + match.index; 26 | if (!node.program.indentExclusions[removeStart]) { 27 | code.remove(removeStart, removeStart + indentStrLen); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/utils/getSnippet.js: -------------------------------------------------------------------------------- 1 | function pad(num, len) { 2 | let result = String(num); 3 | return result + repeat(' ', len - result.length); 4 | } 5 | 6 | function repeat(str, times) { 7 | let result = ''; 8 | while (times--) result += str; 9 | return result; 10 | } 11 | 12 | export default function getSnippet(source, loc, length = 1) { 13 | const first = Math.max(loc.line - 5, 0); 14 | const last = loc.line; 15 | 16 | const numDigits = String(last).length; 17 | 18 | const lines = source.split('\n').slice(first, last); 19 | 20 | const lastLine = lines[lines.length - 1]; 21 | const offset = lastLine.slice(0, loc.column).replace(/\t/g, ' ').length; 22 | 23 | let snippet = lines 24 | .map((line, i) => `${pad(i + first + 1, numDigits)} : ${line.replace(/\t/g, ' ')}`) 25 | .join('\n'); 26 | 27 | snippet += '\n' + repeat(' ', numDigits + 3 + offset) + repeat('^', length); 28 | 29 | return snippet; 30 | } 31 | -------------------------------------------------------------------------------- /src/utils/globals.js: -------------------------------------------------------------------------------- 1 | // allowed globals in Vue render functions. 2 | // same as in src/core/instance/proxy.js 3 | const names = 'Infinity,undefined,NaN,isFinite,isNaN,' + 4 | 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' + 5 | 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' + 6 | 'require,' + // for webpack 7 | 'arguments,' + // parsed as identifier but is a special keyword... 8 | '_h,_c' // cached to save property access (_c for ^2.1.5) 9 | 10 | const hash = Object.create(null) 11 | names.split(',').forEach(name => { 12 | hash[name] = true 13 | }) 14 | 15 | export default hash 16 | -------------------------------------------------------------------------------- /src/utils/isReference.js: -------------------------------------------------------------------------------- 1 | export default function isReference(node, parent) { 2 | if (node.type === 'MemberExpression') { 3 | return !node.computed && isReference(node.object, node); 4 | } 5 | 6 | if (node.type === 'Identifier') { 7 | // the only time we could have an identifier node without a parent is 8 | // if it's the entire body of a function without a block statement – 9 | // i.e. an arrow function expression like `a => a` 10 | if (!parent) return true; 11 | 12 | if (/(Function|Class)Expression/.test(parent.type)) return false; 13 | 14 | if (parent.type === 'VariableDeclarator') return node === parent.init; 15 | 16 | // TODO is this right? 17 | if ( 18 | parent.type === 'MemberExpression' || 19 | parent.type === 'MethodDefinition' 20 | ) { 21 | return parent.computed || node === parent.object; 22 | } 23 | 24 | if (parent.type === 'ArrayPattern') return false; 25 | 26 | // disregard the `bar` in `{ bar: foo }`, but keep it in `{ [bar]: foo }` 27 | if (parent.type === 'Property') { 28 | if (parent.parent.type === 'ObjectPattern') return false; 29 | return parent.computed || node === parent.value; 30 | } 31 | 32 | // disregard the `bar` in `class Foo { bar () {...} }` 33 | if (parent.type === 'MethodDefinition') return false; 34 | 35 | // disregard the `bar` in `export { foo as bar }` 36 | if (parent.type === 'ExportSpecifier' && node !== parent.local) 37 | return false; 38 | 39 | return true; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/utils/locate.js: -------------------------------------------------------------------------------- 1 | export default function locate(source, index) { 2 | var lines = source.split('\n'); 3 | var len = lines.length; 4 | 5 | var lineStart = 0; 6 | var i; 7 | 8 | for (i = 0; i < len; i += 1) { 9 | var line = lines[i]; 10 | var lineEnd = lineStart + line.length + 1; // +1 for newline 11 | 12 | if (lineEnd > index) { 13 | return { line: i + 1, column: index - lineStart, char: i }; 14 | } 15 | 16 | lineStart = lineEnd; 17 | } 18 | 19 | throw new Error('Could not determine location of character'); 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/patterns.js: -------------------------------------------------------------------------------- 1 | export const loopStatement = /(?:For(?:In|Of)?|While)Statement/; 2 | -------------------------------------------------------------------------------- /src/utils/prependVm.js: -------------------------------------------------------------------------------- 1 | import globals from './globals.js'; 2 | 3 | const isDeclaration = type => /Declaration$/.test(type); 4 | const isFunction = type => /Function(Expression|Declaration)$/.test(type); 5 | 6 | export function shouldPrependVm (identifier) { 7 | if ( 8 | identifier.program.inWith > 0 && 9 | // not id of a Declaration 10 | !(isDeclaration(identifier.parent.type) && identifier.parent.id === identifier) && 11 | // not a params of a function 12 | !(isFunction(identifier.parent.type) && identifier.parent.params.indexOf(identifier) > -1) && 13 | // not a key of Property 14 | !(identifier.parent.type === 'Property' && identifier.parent.key === identifier && !identifier.parent.computed) && 15 | // not a property of a MemberExpression 16 | !(identifier.parent.type === 'MemberExpression' && identifier.parent.property === identifier && !identifier.parent.computed) && 17 | // not in an Array destructure pattern 18 | !(identifier.parent.type === 'ArrayPattern') && 19 | // not in an Object destructure pattern 20 | !(identifier.parent.parent.type === 'ObjectPattern') && 21 | // skip globals + commonly used shorthands 22 | !globals[identifier.name] && 23 | // not already in scope 24 | !identifier.findScope(false).contains(identifier.name) 25 | ) { 26 | return true; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/removeTrailingComma.js: -------------------------------------------------------------------------------- 1 | export default function removeTrailingComma(code, c) { 2 | while (code.original[c] !== ')') { 3 | if (code.original[c] === ',') { 4 | code.remove(c, c + 1); 5 | return; 6 | } 7 | 8 | if (code.original[c] === '/') { 9 | c = code.original.indexOf(code.original[c + 1] === '/' ? '\n' : '*/', c) + 1; 10 | } 11 | c += 1; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/reserved.js: -------------------------------------------------------------------------------- 1 | let reserved = Object.create(null); 2 | 'do if in for let new try var case else enum eval null this true void with await break catch class const false super throw while yield delete export import public return static switch typeof default extends finally package private continue debugger function arguments interface protected implements instanceof' 3 | .split(' ') 4 | .forEach(word => (reserved[word] = true)); 5 | 6 | export default reserved; 7 | -------------------------------------------------------------------------------- /src/utils/spread.js: -------------------------------------------------------------------------------- 1 | export function isArguments(node) { 2 | return node.type === 'Identifier' && node.name === 'arguments'; 3 | } 4 | 5 | export default function spread( 6 | code, 7 | elements, 8 | start, 9 | argumentsArrayAlias, 10 | isNew 11 | ) { 12 | let i = elements.length; 13 | let firstSpreadIndex = -1; 14 | 15 | while (i--) { 16 | const element = elements[i]; 17 | if (element && element.type === 'SpreadElement') { 18 | if (isArguments(element.argument)) { 19 | code.overwrite( 20 | element.argument.start, 21 | element.argument.end, 22 | argumentsArrayAlias 23 | ); 24 | } 25 | 26 | firstSpreadIndex = i; 27 | } 28 | } 29 | 30 | if (firstSpreadIndex === -1) return false; // false indicates no spread elements 31 | 32 | if (isNew) { 33 | for (i = 0; i < elements.length; i += 1) { 34 | let element = elements[i]; 35 | if (element.type === 'SpreadElement') { 36 | code.remove(element.start, element.argument.start); 37 | } else { 38 | code.prependRight(element.start, '['); 39 | code.prependRight(element.end, ']'); 40 | } 41 | } 42 | 43 | return true; // true indicates some spread elements 44 | } 45 | 46 | let element = elements[firstSpreadIndex]; 47 | const previousElement = elements[firstSpreadIndex - 1]; 48 | 49 | if (!previousElement) { 50 | code.remove(start, element.start); 51 | code.overwrite(element.end, elements[1].start, '.concat( '); 52 | } else { 53 | code.overwrite(previousElement.end, element.start, ' ].concat( '); 54 | } 55 | 56 | for (i = firstSpreadIndex; i < elements.length; i += 1) { 57 | element = elements[i]; 58 | 59 | if (element) { 60 | if (element.type === 'SpreadElement') { 61 | code.remove(element.start, element.argument.start); 62 | } else { 63 | code.appendLeft(element.start, '['); 64 | code.appendLeft(element.end, ']'); 65 | } 66 | } 67 | } 68 | 69 | return true; // true indicates some spread elements 70 | } 71 | -------------------------------------------------------------------------------- /test/cli/basic/command.sh: -------------------------------------------------------------------------------- 1 | buble input.js -o actual/output.js 2 | -------------------------------------------------------------------------------- /test/cli/basic/expected/output.js: -------------------------------------------------------------------------------- 1 | var answer = function () { return 42; }; 2 | -------------------------------------------------------------------------------- /test/cli/basic/input.js: -------------------------------------------------------------------------------- 1 | const answer = () => 42; 2 | -------------------------------------------------------------------------------- /test/cli/compiles-directory/command.sh: -------------------------------------------------------------------------------- 1 | buble src -o actual -m 2 | -------------------------------------------------------------------------------- /test/cli/compiles-directory/expected/bar.js: -------------------------------------------------------------------------------- 1 | console.log( 'bar' ); 2 | //# sourceMappingURL=bar.js.map -------------------------------------------------------------------------------- /test/cli/compiles-directory/expected/bar.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"bar.js","sources":["../src/bar.jsm"],"sourcesContent":["console.log( 'bar' );"],"names":[],"mappings":"AAAA,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE"} 2 | -------------------------------------------------------------------------------- /test/cli/compiles-directory/expected/baz.js: -------------------------------------------------------------------------------- 1 | console.log( 'baz' ); 2 | //# sourceMappingURL=baz.js.map -------------------------------------------------------------------------------- /test/cli/compiles-directory/expected/baz.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"baz.js","sources":["../src/baz.es6"],"sourcesContent":["console.log( 'baz' );"],"names":[],"mappings":"AAAA,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE"} -------------------------------------------------------------------------------- /test/cli/compiles-directory/expected/foo.js: -------------------------------------------------------------------------------- 1 | console.log('foo'); 2 | 3 | //# sourceMappingURL=foo.js.map -------------------------------------------------------------------------------- /test/cli/compiles-directory/expected/foo.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"foo.js","sources":["../src/foo.js"],"sourcesContent":["console.log('foo');\n"],"names":[],"mappings":"AAAA,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;"} -------------------------------------------------------------------------------- /test/cli/compiles-directory/src/bar.jsm: -------------------------------------------------------------------------------- 1 | console.log( 'bar' ); -------------------------------------------------------------------------------- /test/cli/compiles-directory/src/baz.es6: -------------------------------------------------------------------------------- 1 | console.log( 'baz' ); -------------------------------------------------------------------------------- /test/cli/compiles-directory/src/foo.js: -------------------------------------------------------------------------------- 1 | console.log('foo'); 2 | -------------------------------------------------------------------------------- /test/cli/compiles-directory/src/nope.txt: -------------------------------------------------------------------------------- 1 | nope 2 | -------------------------------------------------------------------------------- /test/cli/creates-inline-sourcemap/command.sh: -------------------------------------------------------------------------------- 1 | buble input.js -o actual/output.js -m inline 2 | -------------------------------------------------------------------------------- /test/cli/creates-inline-sourcemap/expected/output.js: -------------------------------------------------------------------------------- 1 | var answer = function () { return 42; }; 2 | 3 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3V0cHV0LmpzIiwic291cmNlcyI6WyIuLi9pbnB1dC5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyJjb25zdCBhbnN3ZXIgPSAoKSA9PiA0MjtcbiJdLCJuYW1lcyI6WyJjb25zdCJdLCJtYXBwaW5ncyI6IkFBQUFBLEdBQUssQ0FBQyxNQUFNLFlBQUcsR0FBRyxTQUFHLEtBQUUsQ0FBQzsifQ== -------------------------------------------------------------------------------- /test/cli/creates-inline-sourcemap/input.js: -------------------------------------------------------------------------------- 1 | const answer = () => 42; 2 | -------------------------------------------------------------------------------- /test/cli/creates-sourcemap/command.sh: -------------------------------------------------------------------------------- 1 | buble input.js -o actual/output.js -m 2 | -------------------------------------------------------------------------------- /test/cli/creates-sourcemap/expected/output.js: -------------------------------------------------------------------------------- 1 | var answer = function () { return 42; }; 2 | 3 | //# sourceMappingURL=output.js.map -------------------------------------------------------------------------------- /test/cli/creates-sourcemap/expected/output.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"output.js","sources":["../input.js"],"sourcesContent":["const answer = () => 42;\n"],"names":["const"],"mappings":"AAAAA,GAAK,CAAC,MAAM,YAAG,GAAG,SAAG,KAAE,CAAC;"} -------------------------------------------------------------------------------- /test/cli/creates-sourcemap/input.js: -------------------------------------------------------------------------------- 1 | const answer = () => 42; 2 | -------------------------------------------------------------------------------- /test/cli/supports-jsx-pragma-comment/command.sh: -------------------------------------------------------------------------------- 1 | buble input.js -o actual/output.js --jsx NotReact.createElement 2 | -------------------------------------------------------------------------------- /test/cli/supports-jsx-pragma-comment/expected/output.js: -------------------------------------------------------------------------------- 1 | /* @jsx customPragma */ 2 | var div = customPragma( 'div', null, "Hello" ); 3 | -------------------------------------------------------------------------------- /test/cli/supports-jsx-pragma-comment/input.js: -------------------------------------------------------------------------------- 1 | /* @jsx customPragma */ 2 | var div =
Hello
; 3 | -------------------------------------------------------------------------------- /test/cli/supports-jsx-pragma/command.sh: -------------------------------------------------------------------------------- 1 | buble input.js -o actual/output.js --jsx NotReact.createElement 2 | -------------------------------------------------------------------------------- /test/cli/supports-jsx-pragma/expected/output.js: -------------------------------------------------------------------------------- 1 | var img = NotReact.createElement( 'img', { src: "foo.gif" }); 2 | -------------------------------------------------------------------------------- /test/cli/supports-jsx-pragma/input.js: -------------------------------------------------------------------------------- 1 | var img = ; 2 | -------------------------------------------------------------------------------- /test/cli/supports-jsx/command.sh: -------------------------------------------------------------------------------- 1 | buble input.jsx -o actual/output.js 2 | -------------------------------------------------------------------------------- /test/cli/supports-jsx/expected/output.js: -------------------------------------------------------------------------------- 1 | var img = React.createElement( 'img', { src: 'foo.gif' }); 2 | -------------------------------------------------------------------------------- /test/cli/supports-jsx/input.jsx: -------------------------------------------------------------------------------- 1 | var img = ; 2 | -------------------------------------------------------------------------------- /test/cli/uses-overrides/command.sh: -------------------------------------------------------------------------------- 1 | buble input.js -o actual/output.js -n letConst 2 | -------------------------------------------------------------------------------- /test/cli/uses-overrides/expected/output.js: -------------------------------------------------------------------------------- 1 | const answer = function () { return 42; }; 2 | -------------------------------------------------------------------------------- /test/cli/uses-overrides/input.js: -------------------------------------------------------------------------------- 1 | const answer = () => 42; 2 | -------------------------------------------------------------------------------- /test/cli/uses-targets/command.sh: -------------------------------------------------------------------------------- 1 | buble input.js -o actual/output.js -t firefox:43 2 | -------------------------------------------------------------------------------- /test/cli/uses-targets/expected/output.js: -------------------------------------------------------------------------------- 1 | var answer = () => 42; 2 | -------------------------------------------------------------------------------- /test/cli/uses-targets/input.js: -------------------------------------------------------------------------------- 1 | const answer = () => 42; 2 | -------------------------------------------------------------------------------- /test/cli/writes-to-stdout/command.sh: -------------------------------------------------------------------------------- 1 | buble input.js > actual/output.js 2 | -------------------------------------------------------------------------------- /test/cli/writes-to-stdout/expected/output.js: -------------------------------------------------------------------------------- 1 | var answer = function () { return 42; }; 2 | 3 | -------------------------------------------------------------------------------- /test/cli/writes-to-stdout/input.js: -------------------------------------------------------------------------------- 1 | const answer = () => 42; 2 | -------------------------------------------------------------------------------- /test/samples/arrow-functions.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | description: 'transpiles an arrow function', 4 | input: `var answer = () => 42`, 5 | output: `var answer = function () { return 42; }` 6 | }, 7 | 8 | { 9 | description: 'transpiles an arrow function with a naked parameter', 10 | input: `var double = x => x * 2`, 11 | output: `var double = function (x) { return x * 2; }` 12 | }, 13 | 14 | { 15 | description: 'transpiles an arrow function with single wrapped parameter', 16 | input: `var double = (x) => x * 2`, 17 | output: `var double = function (x) { return x * 2; }` 18 | }, 19 | 20 | { 21 | description: 'transpiles an arrow function with parenthesised parameters', 22 | input: `var add = ( a, b ) => a + b`, 23 | output: `var add = function ( a, b ) { return a + b; }` 24 | }, 25 | 26 | { 27 | description: 'transpiles an arrow function with a body', 28 | 29 | input: ` 30 | var add = ( a, b ) => { 31 | return a + b; 32 | };`, 33 | 34 | output: ` 35 | var add = function ( a, b ) { 36 | return a + b; 37 | };` 38 | }, 39 | 40 | { 41 | description: 'replaces `this` inside an arrow function', 42 | 43 | input: ` 44 | this.foo = 'bar'; 45 | var lexicallyScoped = () => this.foo;`, 46 | 47 | output: ` 48 | var this$1 = this; 49 | 50 | this.foo = 'bar'; 51 | var lexicallyScoped = function () { return this$1.foo; };` 52 | }, 53 | 54 | { 55 | description: 'replaces `arguments` inside an arrow function', 56 | 57 | input: ` 58 | function firstArgument () { 59 | return () => arguments[0]; 60 | } 61 | equal( firstArgument( 1, 2, 3 )(), 1 )`, 62 | 63 | output: ` 64 | function firstArgument () { 65 | var arguments$1 = arguments; 66 | 67 | return function () { return arguments$1[0]; }; 68 | } 69 | equal( firstArgument( 1, 2, 3 )(), 1 )` 70 | }, 71 | 72 | { 73 | description: 'only adds one `this` or `arguments` per context', 74 | 75 | input: ` 76 | function multiply () { 77 | return () => { 78 | return () => this * arguments[0]; 79 | }; 80 | } 81 | equal( multiply.call( 2, 3 )()(), 6 )`, 82 | 83 | output: ` 84 | function multiply () { 85 | var arguments$1 = arguments; 86 | var this$1 = this; 87 | 88 | return function () { 89 | return function () { return this$1 * arguments$1[0]; }; 90 | }; 91 | } 92 | equal( multiply.call( 2, 3 )()(), 6 )` 93 | }, 94 | 95 | { 96 | description: 'transpiles a body-less arrow function with rest params', 97 | 98 | input: ` 99 | const sum = ( ...nums ) => nums.reduce( ( t, n ) => t + n, 0 );`, 100 | 101 | output: ` 102 | var sum = function () { 103 | var nums = [], len = arguments.length; 104 | while ( len-- ) nums[ len ] = arguments[ len ]; 105 | 106 | return nums.reduce( function ( t, n ) { return t + n; }, 0 ); 107 | };` 108 | }, 109 | 110 | { 111 | description: 'handles combination of destructuring and template strings', 112 | 113 | input: ` 114 | var shoutHello = ({ name }) => \`\${name}! Hello \${name}!\`.toUpperCase();`, 115 | 116 | output: ` 117 | var shoutHello = function (ref) { 118 | var name = ref.name; 119 | 120 | return (name + "! Hello " + name + "!").toUpperCase(); 121 | };` 122 | }, 123 | 124 | { 125 | description: 'can be disabled with `transforms.arrow: false`', 126 | options: { transforms: { arrow: false } }, 127 | 128 | input: ` 129 | var add = ( a, b ) => { 130 | console.log( 'this, arguments', this, arguments ) 131 | a = b; 132 | }`, 133 | 134 | output: ` 135 | var add = ( a, b ) => { 136 | console.log( 'this, arguments', this, arguments ) 137 | a = b; 138 | }` 139 | }, 140 | 141 | { 142 | description: 'inserts statements after use strict pragma (#72)', 143 | 144 | input: ` 145 | 'use strict'; 146 | setTimeout( () => console.log( this ) ); 147 | 148 | function foo () { 149 | 'use strict'; 150 | setTimeout( () => console.log( this ) ); 151 | }`, 152 | 153 | output: ` 154 | 'use strict'; 155 | var this$1 = this; 156 | 157 | setTimeout( function () { return console.log( this$1 ); } ); 158 | 159 | function foo () { 160 | 'use strict'; 161 | var this$1 = this; 162 | 163 | setTimeout( function () { return console.log( this$1 ); } ); 164 | }` 165 | }, 166 | 167 | { 168 | description: 'handles standalone arrow function expression statement', 169 | 170 | input: ` 171 | () => console.log( 'not printed' );`, 172 | 173 | output: ` 174 | !function() { return console.log( 'not printed' ); };` 175 | }, 176 | 177 | { 178 | description: 179 | 'handles standalone arrow function expression statement within a function', 180 | 181 | input: ` 182 | function no_op () { 183 | () => console.log( 'not printed' ); 184 | }`, 185 | 186 | output: ` 187 | function no_op () { 188 | !function() { return console.log( 'not printed' ); }; 189 | }` 190 | }, 191 | 192 | { 193 | description: 194 | 'are transformed even if disabled if they have a transpiled spread parameter', 195 | 196 | options: { transforms: { arrow: false, spreadRest: true } }, 197 | 198 | input: ` 199 | (...args) => console.log( args );`, 200 | 201 | output: ` 202 | !function() { 203 | var args = [], len = arguments.length; 204 | while ( len-- ) args[ len ] = arguments[ len ]; 205 | 206 | return console.log( args ); 207 | };` 208 | } 209 | ]; 210 | -------------------------------------------------------------------------------- /test/samples/async.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | description: 'supports async as property name', 4 | 5 | input: ` 6 | ({async, foo})`, 7 | 8 | output: ` 9 | ({async: async, foo: foo})` 10 | } 11 | ]; 12 | -------------------------------------------------------------------------------- /test/samples/binary-and-octal.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | description: 'transpiles binary numbers', 4 | 5 | input: ` 6 | var num = 0b111110111; 7 | var str = '0b111110111';`, 8 | 9 | output: ` 10 | var num = 503; 11 | var str = '0b111110111';` 12 | }, 13 | 14 | { 15 | description: 'transpiles octal numbers', 16 | 17 | input: ` 18 | var num = 0o767; 19 | var str = '0o767';`, 20 | 21 | output: ` 22 | var num = 503; 23 | var str = '0o767';` 24 | }, 25 | 26 | { 27 | description: 'can be disabled with `transforms.numericLiteral: false`', 28 | options: { transforms: { numericLiteral: false } }, 29 | input: '0b111110111', 30 | output: '0b111110111' 31 | } 32 | ]; 33 | -------------------------------------------------------------------------------- /test/samples/computed-properties.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | description: 'creates a computed property', 4 | 5 | input: ` 6 | var obj = { 7 | [a]: 1 8 | };`, 9 | 10 | output: ` 11 | var obj = {}; 12 | obj[a] = 1;` 13 | }, 14 | 15 | { 16 | description: 'creates a computed property with a non-identifier expression', 17 | 18 | input: ` 19 | var obj = { 20 | [a()]: 1 21 | };`, 22 | 23 | output: ` 24 | var obj = {}; 25 | obj[a()] = 1;` 26 | }, 27 | 28 | { 29 | description: 'creates a computed property at start of literal', 30 | 31 | input: ` 32 | var obj = { 33 | [a]: 1, 34 | b: 2 35 | };`, 36 | 37 | output: ` 38 | var obj = {}; 39 | obj[a] = 1; 40 | obj.b = 2;` 41 | }, 42 | 43 | { 44 | description: 'creates a computed property at start of literal with method afterwards', 45 | 46 | input: ` 47 | var obj = { 48 | [a]: 1, 49 | b() {} 50 | };`, 51 | 52 | output: ` 53 | var obj = {}; 54 | obj[a] = 1; 55 | obj.b = function b() {};` 56 | }, 57 | 58 | { 59 | description: 'creates a computed property at start of literal with generator method afterwards when transpiling methods is disabled', 60 | 61 | options: { transforms: { conciseMethodProperty: false, generator: false } }, 62 | 63 | input: ` 64 | var obj = { 65 | [a]: 1, 66 | *b() {} 67 | };`, 68 | 69 | output: ` 70 | var obj = {}; 71 | obj[a] = 1; 72 | obj.b = function* () {};` 73 | }, 74 | 75 | { 76 | description: 'creates a computed property at end of literal', 77 | 78 | input: ` 79 | var obj = { 80 | a: 1, 81 | [b]: 2 82 | };`, 83 | 84 | output: ` 85 | var obj = { 86 | a: 1 87 | }; 88 | obj[b] = 2;` 89 | }, 90 | 91 | { 92 | description: 'creates a computed property in middle of literal', 93 | 94 | input: ` 95 | var obj = { 96 | a: 1, 97 | [b]: 2, 98 | c: 3 99 | };`, 100 | 101 | output: ` 102 | var obj = { 103 | a: 1 104 | }; 105 | obj[b] = 2; 106 | obj.c = 3;` 107 | }, 108 | 109 | { 110 | description: 'creates multiple computed properties', 111 | 112 | input: ` 113 | var obj = { 114 | [a]: 1, 115 | b: 2, 116 | [c]: 3, 117 | [d]: 4, 118 | e: 5, 119 | [f]: 6 120 | };`, 121 | 122 | output: ` 123 | var obj = {}; 124 | obj[a] = 1; 125 | obj.b = 2; 126 | obj[c] = 3; 127 | obj[d] = 4; 128 | obj.e = 5; 129 | obj[f] = 6;` 130 | }, 131 | 132 | { 133 | description: 'creates computed property in complex expression', 134 | 135 | input: ` 136 | var a = 'foo', obj = { [a]: 'bar', x: 42 }, bar = obj.foo;`, 137 | 138 | output: ` 139 | var _obj; 140 | 141 | var a = 'foo', obj = ( _obj = {}, _obj[a] = 'bar', _obj.x = 42, _obj ), bar = obj.foo;` 142 | }, 143 | 144 | { 145 | description: 'creates computed property in block with conflicts', 146 | 147 | input: ` 148 | var x; 149 | 150 | if ( true ) { 151 | let x = { 152 | [a]: 1 153 | }; 154 | }`, 155 | 156 | output: ` 157 | var x; 158 | 159 | if ( true ) { 160 | var x$1 = {}; 161 | x$1[a] = 1; 162 | }` 163 | }, 164 | 165 | { 166 | description: 'closing parenthesis put in correct place (#73)', 167 | 168 | input: ` 169 | call({ [a]: 5 });`, 170 | 171 | output: ` 172 | var _obj; 173 | 174 | call(( _obj = {}, _obj[a] = 5, _obj ));` 175 | }, 176 | 177 | { 178 | description: 'creates a computed method (#78)', 179 | 180 | input: ` 181 | var obj = { 182 | [a] () { 183 | // code goes here 184 | } 185 | };`, 186 | 187 | output: ` 188 | var obj = {}; 189 | obj[a] = function () { 190 | // code goes here 191 | };` 192 | }, 193 | 194 | { 195 | description: 196 | 'creates a computed method with a non-identifier expression (#78)', 197 | 198 | input: ` 199 | var obj = { 200 | [a()] () { 201 | // code goes here 202 | } 203 | };`, 204 | 205 | output: ` 206 | var obj = {}; 207 | obj[a()] = function () { 208 | // code goes here 209 | };` 210 | }, 211 | 212 | { 213 | description: 214 | 'does not require space before parens of computed method (#82)', 215 | 216 | input: ` 217 | var obj = { 218 | [a]() { 219 | // code goes here 220 | } 221 | };`, 222 | 223 | output: ` 224 | var obj = {}; 225 | obj[a] = function () { 226 | // code goes here 227 | };` 228 | }, 229 | 230 | { 231 | description: 232 | 'supports computed shorthand function with object spread in body (#135)', 233 | 234 | options: { 235 | objectAssign: 'Object.assign' 236 | }, 237 | input: ` 238 | let a = { 239 | [foo] (x, y) { 240 | return { 241 | ...{abc: '123'} 242 | }; 243 | }, 244 | }; 245 | `, 246 | output: ` 247 | var a = {}; 248 | a[foo] = function (x, y) { 249 | return Object.assign({}, {abc: '123'}); 250 | }; 251 | ` 252 | }, 253 | 254 | { 255 | description: 256 | 'object literal with computed property within arrow expression (#126)', 257 | 258 | input: ` 259 | foo => bar({[x - y]: obj}); 260 | `, 261 | output: ` 262 | !function(foo) { 263 | var _obj; 264 | 265 | return bar(( _obj = {}, _obj[x - y] = obj, _obj )); 266 | }; 267 | ` 268 | }, 269 | 270 | { 271 | description: 'Supports nested computed properties (#51)', 272 | 273 | input: ` 274 | (function () { return { [key]: { [key]: val } } }) 275 | `, 276 | output: ` 277 | (function () { 278 | var _obj, _obj$1; 279 | return ( _obj$1 = {}, _obj$1[key] = ( _obj = {}, _obj[key] = val, _obj ), _obj$1 ) }) 280 | ` 281 | }, 282 | 283 | { 284 | description: 'Puts helper variables in correct scope', 285 | 286 | input: ` 287 | ((x) => {var obj = 2; console.log([{[x]: 1}, obj]);})(3); 288 | `, 289 | output: ` 290 | (function (x) { 291 | var _obj; 292 | var obj = 2; console.log([( _obj = {}, _obj[x] = 1, _obj ), obj]);})(3); 293 | ` 294 | } 295 | ]; 296 | -------------------------------------------------------------------------------- /test/samples/default-parameters.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | description: 'transpiles default parameters', 4 | 5 | input: ` 6 | function foo ( a = 1, b = 2 ) { 7 | console.log( a, b ); 8 | } 9 | 10 | var bar = function ( a = 1, b = 2 ) { 11 | console.log( a, b ); 12 | };`, 13 | 14 | output: ` 15 | function foo ( a, b ) { 16 | if ( a === void 0 ) a = 1; 17 | if ( b === void 0 ) b = 2; 18 | 19 | console.log( a, b ); 20 | } 21 | 22 | var bar = function ( a, b ) { 23 | if ( a === void 0 ) a = 1; 24 | if ( b === void 0 ) b = 2; 25 | 26 | console.log( a, b ); 27 | };` 28 | }, 29 | 30 | { 31 | description: 'transpiles default parameters in object pattern (#23)', 32 | 33 | input: ` 34 | function foo ({ a = 1 }) { 35 | console.log( a ); 36 | }`, 37 | 38 | output: ` 39 | function foo (ref) { 40 | var a = ref.a; if ( a === void 0 ) a = 1; 41 | 42 | console.log( a ); 43 | }` 44 | }, 45 | 46 | { 47 | description: 'transpiles multiple default parameters in object pattern', 48 | 49 | input: ` 50 | function foo ({ a = 1 }, { b = 2 }) { 51 | console.log( a, b ); 52 | } 53 | 54 | var bar = function ({ a = 1 }, { b = 2 }) { 55 | console.log( a, b ); 56 | };`, 57 | 58 | output: ` 59 | function foo (ref, ref$1) { 60 | var a = ref.a; if ( a === void 0 ) a = 1; 61 | var b = ref$1.b; if ( b === void 0 ) b = 2; 62 | 63 | console.log( a, b ); 64 | } 65 | 66 | var bar = function (ref, ref$1) { 67 | var a = ref.a; if ( a === void 0 ) a = 1; 68 | var b = ref$1.b; if ( b === void 0 ) b = 2; 69 | 70 | console.log( a, b ); 71 | };` 72 | }, 73 | 74 | { 75 | description: 'can be disabled with `transforms.defaultParameter: false`', 76 | options: { transforms: { defaultParameter: false } }, 77 | 78 | input: ` 79 | function foo ( a = 1, b = 2 ) { 80 | console.log( a, b ); 81 | } 82 | 83 | var bar = function ( a = 1, b = 2 ) { 84 | console.log( a, b ); 85 | };`, 86 | 87 | output: ` 88 | function foo ( a = 1, b = 2 ) { 89 | console.log( a, b ); 90 | } 91 | 92 | var bar = function ( a = 1, b = 2 ) { 93 | console.log( a, b ); 94 | };` 95 | }, 96 | 97 | { 98 | description: 'transpiles default arrow function parameters', 99 | 100 | input: ` 101 | function a(x, f = () => x) { 102 | console.log( f() ); 103 | }`, 104 | 105 | output: ` 106 | function a(x, f) { 107 | if ( f === void 0 ) f = function () { return x; }; 108 | 109 | console.log( f() ); 110 | }` 111 | }, 112 | 113 | { 114 | description: 'transpiles destructured default parameters (#43)', 115 | 116 | input: ` 117 | function a({ x = 1 } = {}) { 118 | console.log( x ); 119 | }`, 120 | 121 | output: ` 122 | function a(ref) { 123 | if ( ref === void 0 ) ref = {}; 124 | var x = ref.x; if ( x === void 0 ) x = 1; 125 | 126 | console.log( x ); 127 | }` 128 | } 129 | ]; 130 | -------------------------------------------------------------------------------- /test/samples/dynamic-import.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | description: 'support dynamic import', 4 | input: `import('./module.js')`, 5 | output: `import('./module.js')` 6 | } 7 | 8 | ]; 9 | -------------------------------------------------------------------------------- /test/samples/exponentiation-operator.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | description: 'transpiles an exponentiation operator', 4 | input: `x ** y`, 5 | output: `Math.pow( x, y )` 6 | }, 7 | 8 | { 9 | description: 10 | 'transpiles an exponentiation assignment to a simple reference', 11 | input: `x **= y`, 12 | output: `x = Math.pow( x, y )` 13 | }, 14 | 15 | { 16 | description: 17 | 'transpiles an exponentiation assignment to a simple parenthesized reference', 18 | input: `( x ) **= y`, 19 | output: `( x ) = Math.pow( x, y )` 20 | }, 21 | 22 | { 23 | description: 24 | 'transpiles an exponentiation assignment to a rewritten simple reference', 25 | 26 | input: ` 27 | let x = 1; 28 | 29 | if ( maybe ) { 30 | let x = 2; 31 | x **= y; 32 | }`, 33 | 34 | output: ` 35 | var x = 1; 36 | 37 | if ( maybe ) { 38 | var x$1 = 2; 39 | x$1 = Math.pow( x$1, y ); 40 | }` 41 | }, 42 | 43 | { 44 | description: 45 | 'transpiles an exponentiation assignment to a simple member expression', 46 | 47 | input: ` 48 | foo.bar **= y;`, 49 | 50 | output: ` 51 | foo.bar = Math.pow( foo.bar, y );` 52 | }, 53 | 54 | { 55 | description: 56 | 'transpiles an exponentiation assignment to a simple deep member expression', 57 | 58 | input: ` 59 | foo.bar.baz **= y;`, 60 | 61 | output: ` 62 | var object; 63 | 64 | object = foo.bar; 65 | object.baz = Math.pow( object.baz, y );` 66 | }, 67 | 68 | { 69 | description: 70 | 'transpiles an exponentiation assignment to a simple computed member expression', 71 | 72 | input: ` 73 | foo[ bar ] **= y;`, 74 | 75 | output: ` 76 | foo[ bar ] = Math.pow( foo[bar], y );` 77 | }, 78 | 79 | { 80 | description: 81 | 'transpiles an exponentiation assignment to a complex reference', 82 | 83 | input: ` 84 | foo[ bar() ] **= y;`, 85 | 86 | output: ` 87 | var property; 88 | 89 | property = bar(); 90 | foo[property] = Math.pow( foo[property], y );` 91 | }, 92 | 93 | { 94 | description: 95 | 'transpiles an exponentiation assignment to a contrivedly complex reference', 96 | 97 | input: ` 98 | foo[ bar() ][ baz() ] **= y;`, 99 | 100 | output: ` 101 | var property, object; 102 | 103 | object = foo[ bar() ]; 104 | property = baz(); 105 | object[property] = Math.pow( object[property], y );` 106 | }, 107 | 108 | { 109 | description: 110 | 'transpiles an exponentiation assignment to a contrivedly complex reference (that is not a top-level statement)', 111 | 112 | input: ` 113 | var baz = 1, lolwut = foo[ bar() ][ baz * 2 ] **= y;`, 114 | 115 | output: ` 116 | var property, object; 117 | 118 | var baz = 1, lolwut = ( object = foo[ bar() ], property = baz * 2, object[property] = Math.pow( object[property], y ) );` 119 | }, 120 | 121 | { 122 | description: 123 | 'transpiles an exponentiation assignment to a contrivedly complex reference with simple object (that is not a top-level statement)', 124 | 125 | input: ` 126 | var baz = 1, lolwut = foo[ bar() ] **= y;`, 127 | 128 | output: ` 129 | var property; 130 | 131 | var baz = 1, lolwut = ( property = bar(), foo[property] = Math.pow( foo[property], y ) );` 132 | }, 133 | 134 | { 135 | description: 'handles pathological bastard case', 136 | 137 | input: ` 138 | let i; 139 | 140 | if ( maybe ) { 141 | for ( let i = 1.1; i < 1e6; i **= i ) { 142 | setTimeout( function () { 143 | console.log( i ); 144 | }, i ); 145 | } 146 | }`, 147 | 148 | output: ` 149 | var i; 150 | 151 | if ( maybe ) { 152 | var loop = function ( i ) { 153 | setTimeout( function () { 154 | console.log( i ); 155 | }, i ); 156 | }; 157 | 158 | for ( var i$1 = 1.1; i$1 < 1e6; i$1 = Math.pow( i$1, i$1 ) ) loop( i$1 ); 159 | }` 160 | }, 161 | 162 | { 163 | description: 'handles assignment of exponentiation assignment to property', 164 | 165 | input: ` 166 | x=a.b**=2; 167 | `, 168 | output: ` 169 | x=a.b=Math.pow( a.b, 2 ); 170 | ` 171 | }, 172 | 173 | { 174 | description: 175 | 'handles assignment of exponentiation assignment to property with side effect', 176 | 177 | input: ` 178 | x=a[bar()]**=2; 179 | `, 180 | output: ` 181 | var property; 182 | 183 | x=( property = bar(), a[property]=Math.pow( a[property], 2 ) ); 184 | ` 185 | } 186 | 187 | /* TODO: Test currently errors out with: TypeError: Cannot read property 'property' of null 188 | { 189 | description: 'handles assignment of exponentiation assignment to property with side effect within a block-less if', 190 | 191 | input: ` 192 | if(y)x=a[foo()]**=2; 193 | `, 194 | output: ` 195 | ` 196 | }, 197 | */ 198 | ]; 199 | -------------------------------------------------------------------------------- /test/samples/for-of.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | description: 'disallows for-of statements', 4 | input: `for ( x of y ) {}`, 5 | error: /for\.\.\.of statements are not supported/ 6 | }, 7 | 8 | { 9 | description: 'ignores for-of with `transforms.forOf === false`', 10 | options: { transforms: { forOf: false } }, 11 | input: `for ( x of y ) {}`, 12 | output: `for ( x of y ) {}` 13 | }, 14 | 15 | { 16 | description: 17 | 'transpiles for-of with array assumption with `transforms.dangerousForOf`', 18 | options: { transforms: { dangerousForOf: true } }, 19 | 20 | input: ` 21 | for ( let member of array ) { 22 | doSomething( member ); 23 | }`, 24 | 25 | output: ` 26 | for ( var i = 0, list = array; i < list.length; i += 1 ) { 27 | var member = list[i]; 28 | 29 | doSomething( member ); 30 | }` 31 | }, 32 | 33 | { 34 | description: 'transpiles for-of with expression', 35 | options: { transforms: { dangerousForOf: true } }, 36 | 37 | input: ` 38 | for ( let member of [ 'a', 'b', 'c' ] ) { 39 | doSomething( member ); 40 | }`, 41 | 42 | output: ` 43 | for ( var i = 0, list = [ 'a', 'b', 'c' ]; i < list.length; i += 1 ) { 44 | var member = list[i]; 45 | 46 | doSomething( member ); 47 | }` 48 | }, 49 | 50 | { 51 | description: 'transpiles for-of that needs to be rewritten as function', 52 | options: { transforms: { dangerousForOf: true } }, 53 | 54 | input: ` 55 | for ( let member of [ 'a', 'b', 'c' ] ) { 56 | setTimeout( function () { 57 | doSomething( member ); 58 | }); 59 | }`, 60 | 61 | output: ` 62 | var loop = function () { 63 | var member = list[i]; 64 | 65 | setTimeout( function () { 66 | doSomething( member ); 67 | }); 68 | }; 69 | 70 | for ( var i = 0, list = [ 'a', 'b', 'c' ]; i < list.length; i += 1 ) loop();` 71 | }, 72 | 73 | { 74 | description: 'transpiles body-less for-of', 75 | options: { transforms: { dangerousForOf: true } }, 76 | 77 | input: ` 78 | for ( let member of array ) console.log( member );`, 79 | 80 | output: ` 81 | for ( var i = 0, list = array; i < list.length; i += 1 ) { 82 | var member = list[i]; 83 | 84 | console.log( member ); 85 | }` 86 | }, 87 | 88 | { 89 | description: 'transpiles space-less for-of', 90 | options: { transforms: { dangerousForOf: true } }, 91 | 92 | input: ` 93 | for (const key of this.keys) { 94 | console.log(key); 95 | }`, 96 | 97 | output: ` 98 | var this$1 = this; 99 | 100 | for (var i = 0, list = this$1.keys; i < list.length; i += 1) { 101 | var key = list[i]; 102 | 103 | console.log(key); 104 | }` 105 | }, 106 | 107 | { 108 | description: 'handles continue in for-of', 109 | options: { transforms: { dangerousForOf: true } }, 110 | 111 | input: ` 112 | for ( let item of items ) { 113 | if ( item.foo ) continue; 114 | }`, 115 | 116 | output: ` 117 | for ( var i = 0, list = items; i < list.length; i += 1 ) { 118 | var item = list[i]; 119 | 120 | if ( item.foo ) { continue; } 121 | }` 122 | }, 123 | 124 | { 125 | description: 'handles this and arguments in for-of', 126 | options: { transforms: { dangerousForOf: true } }, 127 | 128 | input: ` 129 | for ( let item of items ) { 130 | console.log( this, arguments, item ); 131 | setTimeout( () => { 132 | console.log( item ); 133 | }); 134 | }`, 135 | 136 | output: ` 137 | var arguments$1 = arguments; 138 | var this$1 = this; 139 | 140 | var loop = function () { 141 | var item = list[i]; 142 | 143 | console.log( this$1, arguments$1, item ); 144 | setTimeout( function () { 145 | console.log( item ); 146 | }); 147 | }; 148 | 149 | for ( var i = 0, list = items; i < list.length; i += 1 ) loop();` 150 | }, 151 | 152 | { 153 | description: 'for-of with empty block (#80)', 154 | options: { transforms: { dangerousForOf: true } }, 155 | 156 | input: ` 157 | for ( let x of y ) {}`, 158 | 159 | output: ` 160 | ` 161 | }, 162 | 163 | { 164 | description: 'for-of with empty block and var (#80)', 165 | options: { transforms: { dangerousForOf: true } }, 166 | 167 | input: ` 168 | for ( var x of y ) {}`, 169 | 170 | output: ` 171 | var x;` 172 | }, 173 | 174 | { 175 | description: 'return from for-of loop rewritten as function', 176 | options: { transforms: { dangerousForOf: true } }, 177 | 178 | input: ` 179 | function foo () { 180 | for ( let x of y ) { 181 | setTimeout( function () { 182 | console.log( x ); 183 | }); 184 | 185 | if ( x > 10 ) return; 186 | } 187 | }`, 188 | 189 | output: ` 190 | function foo () { 191 | var loop = function () { 192 | var x = list[i]; 193 | 194 | setTimeout( function () { 195 | console.log( x ); 196 | }); 197 | 198 | if ( x > 10 ) { return {}; } 199 | }; 200 | 201 | for ( var i = 0, list = y; i < list.length; i += 1 ) { 202 | var returned = loop(); 203 | 204 | if ( returned ) return returned.v; 205 | } 206 | }` 207 | }, 208 | 209 | { 210 | description: 'allows destructured variable declaration (#95)', 211 | options: { transforms: { dangerousForOf: true } }, 212 | 213 | input: ` 214 | for (var {x, y} of [{x: 1, y: 2}]) { 215 | console.log(x, y); 216 | }`, 217 | 218 | output: ` 219 | for (var i = 0, list = [{x: 1, y: 2}]; i < list.length; i += 1) { 220 | var ref = list[i]; 221 | var x = ref.x; 222 | var y = ref.y; 223 | 224 | console.log(x, y); 225 | }` 226 | }, 227 | 228 | { 229 | description: 'destructures assignment in forOf loop', 230 | options: { transforms: { dangerousForOf: true } }, 231 | 232 | input: ` 233 | var x, y; 234 | for ({x, y} of [{x: 1, y: 2}]) { 235 | console.log(x, y); 236 | } 237 | `, 238 | 239 | output: ` 240 | var x, y; 241 | for (var i = 0, list = [{x: 1, y: 2}]; i < list.length; i += 1) { 242 | var ref = list[i]; 243 | x = ref.x; 244 | y = ref.y; 245 | 246 | console.log(x, y); 247 | } 248 | ` 249 | }, 250 | 251 | { 252 | description: 'destructures assignment in forOf loop with array pattern', 253 | options: { transforms: { dangerousForOf: true } }, 254 | 255 | input: ` 256 | var x, y; 257 | for ([x, y] of [[1, 2]]) { 258 | console.log(x, y); 259 | } 260 | `, 261 | 262 | output: ` 263 | var x, y; 264 | for (var i = 0, list = [[1, 2]]; i < list.length; i += 1) { 265 | var ref = list[i]; 266 | x = ref[0]; 267 | y = ref[1]; 268 | 269 | console.log(x, y); 270 | } 271 | ` 272 | } 273 | ]; 274 | -------------------------------------------------------------------------------- /test/samples/generators.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | description: 'disallows generator function declarations', 4 | 5 | input: ` 6 | function* foo () { 7 | 8 | }`, 9 | 10 | error: /Generators are not supported/ 11 | }, 12 | 13 | { 14 | description: 'disallows generator function expressions', 15 | 16 | input: ` 17 | var fn = function* foo () { 18 | 19 | }`, 20 | 21 | error: /Generators are not supported/ 22 | }, 23 | 24 | { 25 | description: 'disallows generator functions as object literal methods', 26 | 27 | input: ` 28 | var obj = { 29 | *foo () { 30 | 31 | } 32 | };`, 33 | 34 | error: /Generators are not supported/ 35 | }, 36 | 37 | { 38 | description: 'disallows generator functions as class methods', 39 | 40 | input: ` 41 | class Foo { 42 | *foo () { 43 | 44 | } 45 | }`, 46 | 47 | error: /Generators are not supported/ 48 | }, 49 | 50 | { 51 | description: 52 | 'ignores generator function declarations with `transforms.generator: false`', 53 | options: { transforms: { generator: false } }, 54 | input: `function* foo () {}`, 55 | output: `function* foo () {}` 56 | }, 57 | 58 | { 59 | description: 60 | 'ignores generator function expressions with `transforms.generator: false`', 61 | options: { transforms: { generator: false } }, 62 | input: `var foo = function* foo () {}`, 63 | output: `var foo = function* foo () {}` 64 | }, 65 | 66 | { 67 | description: 68 | 'ignores generator function methods with `transforms.generator: false`', 69 | options: { transforms: { generator: false } }, 70 | input: `var obj = { *foo () {} }`, 71 | output: `var obj = { foo: function* foo () {} }` 72 | }, 73 | 74 | { 75 | description: 76 | 'ignores generator function class methods with `transforms.generator: false`', 77 | options: { transforms: { generator: false } }, 78 | input: ` 79 | class Foo { 80 | *foo () { 81 | // code goes here 82 | } 83 | }`, 84 | 85 | output: ` 86 | var Foo = function Foo () {}; 87 | 88 | Foo.prototype.foo = function* foo () { 89 | // code goes here 90 | };` 91 | } 92 | ]; 93 | -------------------------------------------------------------------------------- /test/samples/jsx.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | description: 'transpiles self-closing JSX tag', 4 | input: `var img = ;`, 5 | output: `var img = React.createElement( 'img', { src: 'foo.gif' });` 6 | }, 7 | 8 | { 9 | description: 'transpiles non-self-closing JSX tag', 10 | input: `var div =
;`, 11 | output: `var div = React.createElement( 'div', { className: 'foo' });` 12 | }, 13 | 14 | { 15 | description: 'transpiles non-self-closing JSX tag without attributes', 16 | input: `var div =
;`, 17 | output: `var div = React.createElement( 'div', null );` 18 | }, 19 | 20 | { 21 | description: 'transpiles nested JSX tags', 22 | 23 | input: ` 24 | var div = ( 25 |
26 | 27 | 28 |
29 | );`, 30 | 31 | output: ` 32 | var div = ( 33 | React.createElement( 'div', { className: 'foo' }, 34 | React.createElement( 'img', { src: 'foo.gif' }), 35 | React.createElement( 'img', { src: 'bar.gif' }) 36 | ) 37 | );` 38 | }, 39 | 40 | { 41 | description: 'transpiles JSX tag with expression attributes', 42 | input: `var img = ;`, 43 | output: `var img = React.createElement( 'img', { src: src });` 44 | }, 45 | 46 | { 47 | description: 'transpiles JSX tag with expression children', 48 | 49 | input: ` 50 | var div = ( 51 |
52 | { images.map( src => ) } 53 |
54 | );`, 55 | 56 | output: ` 57 | var div = ( 58 | React.createElement( 'div', null, 59 | images.map( function (src) { return React.createElement( 'img', { src: src }); } ) 60 | ) 61 | );` 62 | }, 63 | 64 | { 65 | description: 'transpiles JSX component', 66 | input: `var element = ;`, 67 | output: `var element = React.createElement( Hello, { name: name });` 68 | }, 69 | 70 | { 71 | description: 'transpiles empty JSX expression block', 72 | input: `var element = {};`, 73 | output: `var element = React.createElement( Foo, null );` 74 | }, 75 | 76 | { 77 | description: 'transpiles empty JSX expression block with comment', 78 | input: `var element = {/* comment */};`, 79 | output: `var element = React.createElement( Foo, null/* comment */ );` 80 | }, 81 | 82 | { 83 | description: 'transpiles JSX component without attributes', 84 | input: `var element = ;`, 85 | output: `var element = React.createElement( Hello, null );` 86 | }, 87 | 88 | { 89 | description: 'transpiles JSX component without attributes with children', 90 | input: `var element = hello;`, 91 | output: `var element = React.createElement( Hello, null, "hello" );` 92 | }, 93 | 94 | { 95 | description: 'transpiles namespaced JSX component', 96 | input: `var element = ;`, 97 | output: `var element = React.createElement( Foo.Bar, { name: name });` 98 | }, 99 | 100 | { 101 | description: 'supports pragmas', 102 | options: { jsx: 'NotReact.createElement' }, 103 | input: `var img = ;`, 104 | output: `var img = NotReact.createElement( 'img', { src: 'foo.gif' });` 105 | }, 106 | 107 | { 108 | description: 'stringifies text children', 109 | input: `

Hello {name}!

`, 110 | output: `React.createElement( 'h1', null, "Hello ", name, "!" )` 111 | }, 112 | 113 | { 114 | description: 'handles whitespace and quotes appropriately', 115 | input: ` 116 |

117 | Hello {name} 118 | ! 119 |

`, 120 | output: ` 121 | React.createElement( 'h1', null, "Hello ", name, "!" )` 122 | }, 123 | 124 | { 125 | description: 'handles single-line whitespace and quotes appropriately', 126 | input: ` 127 |

128 | Hello {name} – and goodbye! 129 |

`, 130 | output: ` 131 | React.createElement( 'h1', null, "Hello ", name, " – and goodbye!" )` 132 | }, 133 | 134 | { 135 | description: 'handles single quotes in text children', 136 | input: ` 137 |

138 | Hello {name} 139 | !${' '} 140 | It's nice to meet you 141 |

`, 142 | output: ` 143 | React.createElement( 'h1', null, "Hello ", name, "! It's nice to meet you" )` 144 | }, 145 | 146 | { 147 | description: 'transpiles tag with data attribute', 148 | input: `var element =
;`, 149 | output: `var element = React.createElement( 'div', { 'data-name': name });` 150 | }, 151 | 152 | { 153 | description: 'transpiles JSX tag without value', 154 | input: `var div =
;`, 155 | output: `var div = React.createElement( 'div', { contentEditable: true });` 156 | }, 157 | 158 | { 159 | description: 'transpiles JSX fragments', 160 | input: `var divs = <>
;`, 161 | output: `var divs = React.createElement( React.Fragment, null, React.createElement( 'div', { contentEditable: true }), React.createElement( 'div', null ) );` 162 | }, 163 | 164 | { 165 | description: 'transpiles one JSX spread attributes', 166 | input: `var element =
;`, 167 | output: `var element = React.createElement( 'div', props);` 168 | }, 169 | 170 | { 171 | description: 'disallow mixed JSX spread attributes ending in spread', 172 | input: `var element =
;`, 173 | error: /Mixed JSX attributes ending in spread requires specified objectAssign option with 'Object\.assign' or polyfill helper\./ 174 | }, 175 | 176 | { 177 | description: 'transpiles mixed JSX spread attributes ending in spread', 178 | options: { 179 | objectAssign: 'Object.assign' 180 | }, 181 | input: `var element =
;`, 182 | output: `var element = React.createElement( 'div', Object.assign({}, { a: 1 }, props, stuff));` 183 | }, 184 | 185 | { 186 | description: 187 | 'transpiles mixed JSX spread attributes ending in spread with custom Object.assign', 188 | options: { 189 | objectAssign: 'angular.extend' 190 | }, 191 | input: `var element =
;`, 192 | output: `var element = React.createElement( 'div', angular.extend({}, { a: 1 }, props, stuff));` 193 | }, 194 | 195 | { 196 | description: 197 | 'transpiles mixed JSX spread attributes ending in other values', 198 | options: { 199 | objectAssign: 'Object.assign' 200 | }, 201 | input: `var element =
;`, 202 | output: `var element = React.createElement( 'div', Object.assign({}, { a: 1 }, props, { b: 2, c: 3 }, stuff, { more: things }));` 203 | }, 204 | 205 | { 206 | description: 'transpiles spread expressions (#64)', 207 | input: `
`, 208 | output: `React.createElement( 'div', this.props)` 209 | }, 210 | 211 | { 212 | description: 'handles whitespace between elements on same line (#65)', 213 | 214 | input: ` 215 |

Hello {name}!

`, 216 | 217 | output: ` 218 | React.createElement( Foo, null, " ", React.createElement( 'h1', null, "Hello ", name, "!" ), " " )` 219 | }, 220 | 221 | { 222 | description: 'fix Object.assign regression in JSXOpeningElement (#163)', 223 | 224 | options: { 225 | objectAssign: 'Object.assign' 226 | }, 227 | input: ` 228 | 229 | `, 230 | output: ` 231 | React.createElement( Thing, Object.assign({}, { two: "This no longer fails" }, props)) 232 | ` 233 | }, 234 | 235 | { 236 | description: 'fix no space between JSXOpeningElement attributes (#178)', 237 | 238 | input: ` 239 |
240 | `, 241 | output: ` 242 | React.createElement( 'div', { style: {color:'#000000'}, className: 'content' }) 243 | ` 244 | }, 245 | 246 | { 247 | description: 'supports /* @jsx customPragma */ directives (#195)', 248 | input: ` 249 | /* @jsx customPragma */ 250 | var div =
Hello
251 | `, 252 | output: ` 253 | /* @jsx customPragma */ 254 | var div = customPragma( 'div', null, "Hello" ) 255 | ` 256 | }, 257 | 258 | { 259 | description: 'ignores subsequent /* @jsx customPragma */ directives (#195)', 260 | input: ` 261 | /* @jsx customPragma */ 262 | /* @jsx customPragmaWannabe */ 263 | var div =
Hello
264 | `, 265 | output: ` 266 | /* @jsx customPragma */ 267 | /* @jsx customPragmaWannabe */ 268 | var div = customPragma( 'div', null, "Hello" ) 269 | ` 270 | }, 271 | 272 | { 273 | description: 'handles dash-cased value-less props', 274 | 275 | input: ` 276 | 277 | `, 278 | output: ` 279 | React.createElement( Thing, { 'data-foo': true }) 280 | ` 281 | }, 282 | 283 | { 284 | description: 'handles non-breaking white-space entities', 285 | 286 | input: ` 287 |
288 | 1  289 |   290 |
291 | `, 292 | output: ` 293 | React.createElement( 'div', null, 294 | React.createElement( 'a', null, "1" ), "   ") 295 | ` 296 | }, 297 | 298 | { 299 | description: 'transpiles entities', 300 | 301 | input: ` 302 |
303 | 1<  304 |
305 | `, 306 | output: ` 307 | React.createElement( 'div', null, 308 | React.createElement( 'a', null, "1<" ), " ") 309 | ` 310 | } 311 | ]; 312 | -------------------------------------------------------------------------------- /test/samples/misc.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | description: 'handles empty return', 4 | input: ` 5 | function foo () { 6 | return; 7 | }`, 8 | output: ` 9 | function foo () { 10 | return; 11 | }` 12 | }, 13 | 14 | { 15 | description: 'allows break statement inside switch', 16 | 17 | input: ` 18 | switch ( foo ) { 19 | case bar: 20 | console.log( 'bar' ); 21 | break; 22 | 23 | default: 24 | console.log( 'default' ); 25 | }`, 26 | 27 | output: ` 28 | switch ( foo ) { 29 | case bar: 30 | console.log( 'bar' ); 31 | break; 32 | 33 | default: 34 | console.log( 'default' ); 35 | }` 36 | }, 37 | 38 | { 39 | description: 'double var is okay', 40 | 41 | input: ` 42 | function foo () { 43 | var x = 1; 44 | var x = 2; 45 | }`, 46 | 47 | output: ` 48 | function foo () { 49 | var x = 1; 50 | var x = 2; 51 | }` 52 | }, 53 | 54 | { 55 | description: 'var followed by let is not okay', 56 | 57 | input: ` 58 | function foo () { 59 | var x = 1; 60 | let x = 2; 61 | }`, 62 | 63 | error: /Identifier 'x' has already been declared/ 64 | }, 65 | 66 | { 67 | description: 'let followed by var is not okay', 68 | 69 | input: ` 70 | function foo () { 71 | let x = 1; 72 | var x = 2; 73 | }`, 74 | 75 | error: /Identifier 'x' has already been declared/ 76 | }, 77 | 78 | { 79 | description: 'does not get confused about keys of Literal node', 80 | 81 | input: ` 82 | console.log( null ); 83 | console.log( 'some string' ); 84 | console.log( null );`, 85 | 86 | output: ` 87 | console.log( null ); 88 | console.log( 'some string' ); 89 | console.log( null );` 90 | }, 91 | 92 | { 93 | description: 'handles sparse arrays (#62)', 94 | input: `var a = [ , 1 ], b = [ 1, ], c = [ 1, , 2 ], d = [ 1, , , ];`, 95 | output: `var a = [ , 1 ], b = [ 1 ], c = [ 1, , 2 ], d = [ 1, , , ];` 96 | }, 97 | 98 | { 99 | description: 100 | 'Safari/WebKit bug workaround: parameter shadowing function expression name (#154)', 101 | 102 | input: ` 103 | "use strict"; // necessary to trigger WebKit bug 104 | 105 | class Foo { 106 | bar (bar) { 107 | return bar; 108 | } 109 | static baz (foo, bar, baz) { 110 | return foo * baz - baz * bar; 111 | } 112 | } 113 | 114 | var a = class Bar { 115 | b (a, b, c) { 116 | return a * b - c * b + b$1 - b$2; 117 | } 118 | }; 119 | 120 | var b = class { 121 | b (a, b, c) { 122 | return a * b - c * b; 123 | } 124 | }; 125 | 126 | var c = { 127 | b (a, b, c) { 128 | return a * b - c * b; 129 | } 130 | }; 131 | 132 | var d = function foo(foo) { 133 | return foo; 134 | }; 135 | 136 | // FunctionDeclaration is not subject to the WebKit bug 137 | function bar(bar) { 138 | return bar; 139 | } 140 | `, 141 | output: ` 142 | "use strict"; // necessary to trigger WebKit bug 143 | 144 | var Foo = function Foo () {}; 145 | 146 | Foo.prototype.bar = function bar (bar$1) { 147 | return bar$1; 148 | }; 149 | Foo.baz = function baz (foo, bar, baz$1) { 150 | return foo * baz$1 - baz$1 * bar; 151 | }; 152 | 153 | var a = (function () { 154 | function Bar () {} 155 | 156 | Bar.prototype.b = function b (a, b$3, c) { 157 | return a * b$3 - c * b$3 + b$1 - b$2; 158 | }; 159 | 160 | return Bar; 161 | }()); 162 | 163 | var b = (function () { 164 | function b () {} 165 | 166 | b.prototype.b = function b (a, b$1, c) { 167 | return a * b$1 - c * b$1; 168 | }; 169 | 170 | return b; 171 | }()); 172 | 173 | var c = { 174 | b: function b (a, b$1, c) { 175 | return a * b$1 - c * b$1; 176 | } 177 | }; 178 | 179 | var d = function foo(foo$1) { 180 | return foo$1; 181 | }; 182 | 183 | // FunctionDeclaration is not subject to the WebKit bug 184 | function bar(bar) { 185 | return bar; 186 | } 187 | ` 188 | } 189 | ]; 190 | -------------------------------------------------------------------------------- /test/samples/modules.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | // { 3 | // description: 'disallows import statement', 4 | // input: `import 'foo';`, 5 | // error: /import is not supported/ 6 | // }, 7 | 8 | // { 9 | // description: 'disallows export statement', 10 | // input: `export { foo };`, 11 | // error: /export is not supported/ 12 | // }, 13 | 14 | // { 15 | // description: 'imports are ignored with `transforms.moduleImport === false`', 16 | // options: { transforms: { moduleImport: false } }, 17 | // input: `import 'foo';`, 18 | // output: `import 'foo';` 19 | // }, 20 | 21 | // { 22 | // description: 'exports are ignored with `transforms.moduleExport === false`', 23 | // options: { transforms: { moduleExport: false } }, 24 | // input: `export { foo };`, 25 | // output: `export { foo };` 26 | // }, 27 | 28 | // { 29 | // description: 'imports and exports are ignored with `transforms.modules === false`', 30 | // options: { transforms: { modules: false } }, 31 | // input: `import 'foo'; export { foo };`, 32 | // output: `import 'foo'; export { foo };` 33 | // } 34 | ]; 35 | -------------------------------------------------------------------------------- /test/samples/object-properties-no-named-function-expressions.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | description: 'transpiles shorthand properties', 4 | options: { namedFunctionExpressions: false }, 5 | input: `obj = { x, y }`, 6 | output: `obj = { x: x, y: y }` 7 | }, 8 | 9 | { 10 | description: 'transpiles shorthand methods', 11 | options: { namedFunctionExpressions: false }, 12 | 13 | input: ` 14 | obj = { 15 | foo () { return 42; } 16 | }`, 17 | 18 | output: ` 19 | obj = { 20 | foo: function () { return 42; } 21 | }` 22 | }, 23 | 24 | { 25 | description: 'transpiles shorthand methods with quoted names (#82)', 26 | options: { namedFunctionExpressions: false }, 27 | 28 | input: ` 29 | obj = { 30 | 'foo-bar' () { return 42; } 31 | }`, 32 | 33 | output: ` 34 | obj = { 35 | 'foo-bar': function () { return 42; } 36 | }` 37 | }, 38 | 39 | { 40 | description: 'transpiles shorthand methods with reserved names (!68)', 41 | options: { namedFunctionExpressions: false }, 42 | 43 | input: ` 44 | obj = { 45 | catch () { return 42; } 46 | }`, 47 | 48 | output: ` 49 | obj = { 50 | catch: function () { return 42; } 51 | }` 52 | }, 53 | 54 | { 55 | description: 56 | 'transpiles shorthand methods with numeric or string names (#139)', 57 | options: { namedFunctionExpressions: false }, 58 | 59 | input: ` 60 | obj = { 61 | 0() {}, 62 | 0b101() {}, 63 | 80() {}, 64 | .12e3() {}, 65 | 0o753() {}, 66 | 12e34() {}, 67 | 0xFFFF() {}, 68 | "a string"() {}, 69 | "var"() {}, 70 | }`, 71 | 72 | output: ` 73 | obj = { 74 | 0: function() {}, 75 | 5: function() {}, 76 | 80: function() {}, 77 | .12e3: function() {}, 78 | 491: function() {}, 79 | 12e34: function() {}, 80 | 0xFFFF: function() {}, 81 | "a string": function() {}, 82 | "var": function() {}, 83 | }` 84 | }, 85 | 86 | { 87 | description: 88 | 'shorthand properties can be disabled with `transforms.conciseMethodProperty === false`', 89 | options: { 90 | namedFunctionExpressions: false, 91 | transforms: { conciseMethodProperty: false } 92 | }, 93 | input: `var obj = { x, y, z () {} }`, 94 | output: `var obj = { x, y, z () {} }` 95 | }, 96 | 97 | { 98 | description: 99 | 'computed properties can be disabled with `transforms.computedProperty === false`', 100 | options: { 101 | namedFunctionExpressions: false, 102 | transforms: { computedProperty: false } 103 | }, 104 | input: `var obj = { [x]: 'x' }`, 105 | output: `var obj = { [x]: 'x' }` 106 | }, 107 | 108 | { 109 | description: 'transpiles computed properties without spacing (#117)', 110 | options: { namedFunctionExpressions: false }, 111 | 112 | input: ` 113 | if (1) 114 | console.log(JSON.stringify({['com'+'puted']:1,['foo']:2})); 115 | else 116 | console.log(JSON.stringify({['bar']:3})); 117 | `, 118 | output: ` 119 | var _obj, _obj$1; 120 | 121 | if (1) 122 | { console.log(JSON.stringify(( _obj = {}, _obj['com'+'puted'] = 1, _obj['foo'] = 2, _obj ))); } 123 | else 124 | { console.log(JSON.stringify(( _obj$1 = {}, _obj$1['bar'] = 3, _obj$1 ))); } 125 | ` 126 | } 127 | ]; 128 | -------------------------------------------------------------------------------- /test/samples/object-properties.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | description: 'transpiles shorthand properties', 4 | input: `obj = { x, y }`, 5 | output: `obj = { x: x, y: y }` 6 | }, 7 | 8 | { 9 | description: 'transpiles shorthand methods', 10 | 11 | input: ` 12 | obj = { 13 | foo () { return 42; } 14 | }`, 15 | 16 | output: ` 17 | obj = { 18 | foo: function foo () { return 42; } 19 | }` 20 | }, 21 | 22 | { 23 | description: 'transpiles shorthand methods with quoted names (#82)', 24 | 25 | input: ` 26 | obj = { 27 | 'foo-bar' () { return 42; } 28 | }`, 29 | 30 | output: ` 31 | obj = { 32 | 'foo-bar': function foo_bar () { return 42; } 33 | }` 34 | }, 35 | 36 | { 37 | description: 'transpiles shorthand methods with reserved names (!68)', 38 | 39 | input: ` 40 | obj = { 41 | catch () { return 42; } 42 | }`, 43 | 44 | output: ` 45 | obj = { 46 | catch: function catch$1 () { return 42; } 47 | }` 48 | }, 49 | 50 | { 51 | description: 52 | 'transpiles shorthand methods with numeric or string names (#139)', 53 | 54 | input: ` 55 | obj = { 56 | 0() {}, 57 | 0b101() {}, 58 | 80() {}, 59 | .12e3() {}, 60 | 0o753() {}, 61 | 12e34() {}, 62 | 0xFFFF() {}, 63 | "a string"() {}, 64 | "var"() {}, 65 | }`, 66 | 67 | output: ` 68 | obj = { 69 | 0: function () {}, 70 | 5: function () {}, 71 | 80: function () {}, 72 | .12e3: function () {}, 73 | 491: function () {}, 74 | 12e34: function () {}, 75 | 0xFFFF: function () {}, 76 | "a string": function astring() {}, 77 | "var": function var$1() {}, 78 | }` 79 | }, 80 | 81 | { 82 | description: 83 | 'shorthand properties can be disabled with `transforms.conciseMethodProperty === false`', 84 | options: { transforms: { conciseMethodProperty: false } }, 85 | input: `var obj = { x, y, z () {} }`, 86 | output: `var obj = { x, y, z () {} }` 87 | }, 88 | 89 | { 90 | description: 91 | 'computed properties can be disabled with `transforms.computedProperty === false`', 92 | options: { transforms: { computedProperty: false } }, 93 | input: `var obj = { [x]: 'x' }`, 94 | output: `var obj = { [x]: 'x' }` 95 | }, 96 | 97 | { 98 | description: 'transpiles computed properties without spacing (#117)', 99 | 100 | input: ` 101 | if (1) 102 | console.log(JSON.stringify({['com'+'puted']:1,['foo']:2})); 103 | else 104 | console.log(JSON.stringify({['bar']:3})); 105 | `, 106 | output: ` 107 | var _obj, _obj$1; 108 | 109 | if (1) 110 | { console.log(JSON.stringify(( _obj = {}, _obj['com'+'puted'] = 1, _obj['foo'] = 2, _obj ))); } 111 | else 112 | { console.log(JSON.stringify(( _obj$1 = {}, _obj$1['bar'] = 3, _obj$1 ))); } 113 | ` 114 | }, 115 | 116 | { 117 | description: 'transpiles string-keyed properties after computed properties', 118 | 119 | input: ` 120 | fn({['computed']:1, 'some-var':2, a: 3}); 121 | `, 122 | output: ` 123 | var _obj; 124 | 125 | fn(( _obj = {}, _obj['computed'] = 1, _obj['some-var'] = 2, _obj.a = 3, _obj )); 126 | ` 127 | }, 128 | 129 | { 130 | description: 'avoids shadowing free variables with method names (#166)', 131 | input: ` 132 | let x = { 133 | foo() { return foo }, 134 | bar() {} 135 | } 136 | `, 137 | output: ` 138 | var x = { 139 | foo: function foo$1() { return foo }, 140 | bar: function bar() {} 141 | } 142 | ` 143 | } 144 | ]; 145 | -------------------------------------------------------------------------------- /test/samples/object-rest-spread.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | description: 'disallows object spread operator', 4 | input: 'var obj = {...a};', 5 | error: /Object spread operator requires specified objectAssign option with 'Object\.assign' or polyfill helper\./ 6 | }, 7 | 8 | { 9 | description: 'transpiles object spread with one object', 10 | options: { 11 | objectAssign: 'Object.assign' 12 | }, 13 | input: `var obj = {...a};`, 14 | output: `var obj = Object.assign({}, a);` 15 | }, 16 | 17 | { 18 | description: 'transpiles object spread with two objects', 19 | options: { 20 | objectAssign: 'Object.assign' 21 | }, 22 | input: `var obj = {...a, ...b};`, 23 | output: `var obj = Object.assign({}, a, b);` 24 | }, 25 | 26 | { 27 | description: 'transpiles object spread with regular keys in between', 28 | options: { 29 | objectAssign: 'Object.assign' 30 | }, 31 | input: `var obj = { ...a, b: 1, c: 2 };`, 32 | output: `var obj = Object.assign({}, a, {b: 1, c: 2});` 33 | }, 34 | 35 | { 36 | description: 'transpiles object spread mixed', 37 | options: { 38 | objectAssign: 'Object.assign' 39 | }, 40 | input: `var obj = { ...a, b: 1, ...d, e};`, 41 | output: `var obj = Object.assign({}, a, {b: 1}, d, {e: e});` 42 | }, 43 | 44 | { 45 | description: 'transpiles objects with spread with computed property (#144)', 46 | options: { 47 | objectAssign: 'Object.assign' 48 | }, 49 | input: ` 50 | var a0 = { [ x ] : true , ... y }; 51 | var a1 = { [ w ] : 0 , [ x ] : true , ... y }; 52 | var a2 = { v, [ w ] : 0, [ x ] : true, ... y }; 53 | var a3 = { [ w ] : 0, [ x ] : true }; 54 | var a4 = { [ w ] : 0 , [ x ] : true , y }; 55 | var a5 = { k : 9 , [ x ] : true, ... y }; 56 | var a6 = { ... y, [ x ] : true }; 57 | var a7 = { ... y, [ w ] : 0, [ x ] : true }; 58 | var a8 = { k : 9, ... y, [ x ] : true }; 59 | var a9 = { [ x ] : true , [ y ] : false , [ z ] : 9 }; 60 | var a10 = { [ x ] : true, ...y, p, ...q }; 61 | var a11 = { x, [c] : 9 , y }; 62 | var a12 = { ...b, [c]:3, d:4 }; 63 | `, 64 | output: ` 65 | var _obj, _obj$1, _obj$2, _obj$3, _obj$4, _obj$5, _obj$6, _obj$7, _obj$8; 66 | 67 | var a0 = Object.assign(( _obj = {}, _obj[ x ] = true, _obj ), y); 68 | var a1 = Object.assign(( _obj$1 = {}, _obj$1[ w ] = 0, _obj$1[ x ] = true, _obj$1 ), y); 69 | var a2 = Object.assign(( _obj$2 = { v: v }, _obj$2[ w ] = 0, _obj$2[ x ] = true, _obj$2 ), y); 70 | var a3 = {}; 71 | a3[ w ] = 0; 72 | a3[ x ] = true; 73 | var a4 = {}; 74 | a4[ w ] = 0; 75 | a4[ x ] = true; 76 | a4.y = y; 77 | var a5 = Object.assign(( _obj$3 = { k : 9 }, _obj$3[ x ] = true, _obj$3 ), y); 78 | var a6 = Object.assign({}, y, ( _obj$4 = {}, _obj$4[ x ] = true, _obj$4 )); 79 | var a7 = Object.assign({}, y, ( _obj$5 = {}, _obj$5[ w ] = 0, _obj$5[ x ] = true, _obj$5 )); 80 | var a8 = Object.assign({ k : 9 }, y, ( _obj$6 = {}, _obj$6[ x ] = true, _obj$6 )); 81 | var a9 = {}; 82 | a9[ x ] = true; 83 | a9[ y ] = false; 84 | a9[ z ] = 9; 85 | var a10 = Object.assign(( _obj$7 = {}, _obj$7[ x ] = true, _obj$7 ), y, {p: p}, q); 86 | var a11 = { x: x }; 87 | a11[c] = 9; 88 | a11.y = y; 89 | var a12 = Object.assign({}, b, ( _obj$8 = {}, _obj$8[c] = 3, _obj$8 ), {d:4}); 90 | ` 91 | }, 92 | 93 | { 94 | description: 95 | 'transpiles inline objects with spread with computed property (#144)', 96 | options: { 97 | objectAssign: 'Object.assign' 98 | }, 99 | input: ` 100 | f0( { [ x ] : true , ... y } ); 101 | f1( { [ w ] : 0 , [ x ] : true , ... y } ); 102 | f2( { v, [ w ] : 0, [ x ] : true, ... y } ); 103 | f3( { [ w ] : 0, [ x ] : true } ); 104 | f4( { [ w ] : 0 , [ x ] : true , y } ); 105 | f5( { k : 9 , [ x ] : true, ... y } ); 106 | f6( { ... y, [ x ] : true } ); 107 | f7( { ... y, [ w ] : 0, [ x ] : true } ); 108 | f8( { k : 9, ... y, [ x ] : true } ); 109 | f9( { [ x ] : true , [ y ] : false , [ z ] : 9 } ); 110 | f10( { [ x ] : true, ...y, p, ...q } ); 111 | f11( { x, [c] : 9 , y } ); 112 | f12({ ...b, [c]:3, d:4 }); 113 | `, 114 | output: ` 115 | var _obj, _obj$1, _obj$2, _obj$3, _obj$4, _obj$5, _obj$6, _obj$7, _obj$8, _obj$9, _obj$10, _obj$11, _obj$12; 116 | 117 | f0( Object.assign(( _obj = {}, _obj[ x ] = true, _obj ), y) ); 118 | f1( Object.assign(( _obj$1 = {}, _obj$1[ w ] = 0, _obj$1[ x ] = true, _obj$1 ), y) ); 119 | f2( Object.assign(( _obj$2 = { v: v }, _obj$2[ w ] = 0, _obj$2[ x ] = true, _obj$2 ), y) ); 120 | f3( ( _obj$3 = {}, _obj$3[ w ] = 0, _obj$3[ x ] = true, _obj$3 ) ); 121 | f4( ( _obj$4 = {}, _obj$4[ w ] = 0, _obj$4[ x ] = true, _obj$4.y = y, _obj$4 ) ); 122 | f5( Object.assign(( _obj$5 = { k : 9 }, _obj$5[ x ] = true, _obj$5 ), y) ); 123 | f6( Object.assign({}, y, ( _obj$6 = {}, _obj$6[ x ] = true, _obj$6 )) ); 124 | f7( Object.assign({}, y, ( _obj$7 = {}, _obj$7[ w ] = 0, _obj$7[ x ] = true, _obj$7 )) ); 125 | f8( Object.assign({ k : 9 }, y, ( _obj$8 = {}, _obj$8[ x ] = true, _obj$8 )) ); 126 | f9( ( _obj$9 = {}, _obj$9[ x ] = true, _obj$9[ y ] = false, _obj$9[ z ] = 9, _obj$9 ) ); 127 | f10( Object.assign(( _obj$10 = {}, _obj$10[ x ] = true, _obj$10 ), y, {p: p}, q) ); 128 | f11( ( _obj$11 = { x: x }, _obj$11[c] = 9, _obj$11.y = y, _obj$11 ) ); 129 | f12(Object.assign({}, b, ( _obj$12 = {}, _obj$12[c] = 3, _obj$12 ), {d:4})); 130 | ` 131 | }, 132 | 133 | { 134 | description: 'transpiles object spread nested', 135 | options: { 136 | objectAssign: 'Object.assign' 137 | }, 138 | input: `var obj = { ...a, b: 1, dd: {...d, f: 1}, e};`, 139 | output: `var obj = Object.assign({}, a, {b: 1, dd: Object.assign({}, d, {f: 1}), e: e});` 140 | }, 141 | 142 | { 143 | description: 'transpiles object spread deeply nested', 144 | options: { 145 | objectAssign: 'Object.assign' 146 | }, 147 | input: `const c = { ...a, b: 1, dd: {...d, f: 1, gg: {h, ...g, ii: {...i}}}, e};`, 148 | output: `var c = Object.assign({}, a, {b: 1, dd: Object.assign({}, d, {f: 1, gg: Object.assign({}, {h: h}, g, {ii: Object.assign({}, i)})}), e: e});` 149 | }, 150 | 151 | { 152 | description: 'transpiles object spread with custom Object.assign', 153 | options: { 154 | objectAssign: 'angular.extend' 155 | }, 156 | input: `var obj = { ...a, b: 1, dd: {...d, f: 1}, e};`, 157 | output: `var obj = angular.extend({}, a, {b: 1, dd: angular.extend({}, d, {f: 1}), e: e});` 158 | }, 159 | 160 | { 161 | description: 'transpiles rest properties', 162 | input: `var {a, ...b} = c`, 163 | output: `function objectWithoutProperties (obj, exclude) { var target = {}; for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) target[k] = obj[k]; return target; } 164 | var a = c.a; 165 | var rest = objectWithoutProperties( c, ["a"] ); 166 | var b = rest;` 167 | }, 168 | 169 | { 170 | description: 'transpiles rest properties in assignments', 171 | input: `({a, ...b} = c);`, 172 | output: `function objectWithoutProperties (obj, exclude) { var target = {}; for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) target[k] = obj[k]; return target; } 173 | 174 | var assign, rest; 175 | ((assign = c, a = assign.a, rest = objectWithoutProperties( assign, ["a"] ), b = rest));` 176 | }, 177 | 178 | { 179 | description: 'transpiles rest properties in arguments', 180 | input: ` 181 | "use strict"; 182 | function objectWithoutProperties({x, ...y}) {}`, 183 | output: ` 184 | "use strict"; 185 | function objectWithoutProperties$1 (obj, exclude) { var target = {}; for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) target[k] = obj[k]; return target; } 186 | function objectWithoutProperties(ref) { 187 | var x = ref.x; 188 | var rest = objectWithoutProperties$1( ref, ["x"] ); 189 | var y = rest; 190 | }` 191 | }, 192 | 193 | { 194 | description: 'transpiles rest properties in arrow function arguments', 195 | input: `(({x, ...y}) => {})`, 196 | output: `function objectWithoutProperties (obj, exclude) { var target = {}; for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) target[k] = obj[k]; return target; } 197 | (function (ref) { 198 | var x = ref.x; 199 | var rest = objectWithoutProperties( ref, ["x"] ); 200 | var y = rest; 201 | })` 202 | }, 203 | 204 | { 205 | description: 'transpiles rest properties in for loop heads', 206 | input: `for( var {a, ...b} = c;; ) {}`, 207 | output: `function objectWithoutProperties (obj, exclude) { var target = {}; for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) target[k] = obj[k]; return target; } 208 | for( var a = c.a, rest = objectWithoutProperties( c, ["a"] ), b = rest;; ) {}` 209 | }, 210 | 211 | { 212 | description: 'transpiles trivial rest properties in for loop heads', 213 | input: `for( var {...b} = c;; ) {}`, 214 | output: `function objectWithoutProperties (obj, exclude) { var target = {}; for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) target[k] = obj[k]; return target; } 215 | for( var rest = objectWithoutProperties( c, [] ), b = rest;; ) {}` 216 | } 217 | 218 | ]; 219 | -------------------------------------------------------------------------------- /test/samples/regex.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | description: 'transpiles regex with unicode flag', 4 | input: `var regex = /foo.bar/u;`, 5 | output: `var regex = /foo(?:[\\0-\\t\\x0B\\f\\x0E-\\u2027\\u202A-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF])bar/;` 6 | }, 7 | 8 | { 9 | description: 'disallows sticky flag in regex literals', 10 | input: `var regex = /x/y;`, 11 | error: /Regular expression sticky flag is not supported/ 12 | }, 13 | 14 | { 15 | description: 'u flag is ignored with `transforms.unicodeRegExp === false`', 16 | options: { transforms: { unicodeRegExp: false } }, 17 | input: `var regex = /x/u;`, 18 | output: `var regex = /x/u;` 19 | }, 20 | 21 | { 22 | description: 'y flag is ignored with `transforms.stickyRegExp === false`', 23 | options: { transforms: { stickyRegExp: false } }, 24 | input: `var regex = /x/y;`, 25 | output: `var regex = /x/y;` 26 | } 27 | ]; 28 | -------------------------------------------------------------------------------- /test/samples/reserved-properties.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | description: 'rewrites member expressions that are reserved words', 4 | options: { transforms: { reservedProperties: true } }, 5 | input: `foo.then + foo.catch`, 6 | output: `foo.then + foo['catch']` 7 | }, 8 | 9 | { 10 | description: 'rewrites object literal properties that are reserved words', 11 | options: { transforms: { reservedProperties: true } }, 12 | input: `obj = { then: 1, catch: 2 }`, 13 | output: `obj = { then: 1, 'catch': 2 }` 14 | }, 15 | 16 | { 17 | description: 'does not rewrite member expressions by default', 18 | input: `foo.then + foo.catch`, 19 | output: `foo.then + foo.catch` 20 | }, 21 | 22 | { 23 | description: 'does not rewrite object literal properties by default', 24 | input: `obj = { then: 1, catch: 2 }`, 25 | output: `obj = { then: 1, catch: 2 }` 26 | } 27 | ]; 28 | -------------------------------------------------------------------------------- /test/samples/rest-parameters.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | description: 'transpiles solo rest parameters', 4 | 5 | input: ` 6 | function foo ( ...theRest ) { 7 | console.log( theRest ); 8 | }`, 9 | 10 | output: ` 11 | function foo () { 12 | var theRest = [], len = arguments.length; 13 | while ( len-- ) theRest[ len ] = arguments[ len ]; 14 | 15 | console.log( theRest ); 16 | }` 17 | }, 18 | 19 | { 20 | description: 'transpiles rest parameters following other parameters', 21 | 22 | input: ` 23 | function foo ( a, b, c, ...theRest ) { 24 | console.log( theRest ); 25 | }`, 26 | 27 | output: ` 28 | function foo ( a, b, c ) { 29 | var theRest = [], len = arguments.length - 3; 30 | while ( len-- > 0 ) theRest[ len ] = arguments[ len + 3 ]; 31 | 32 | console.log( theRest ); 33 | }` 34 | }, 35 | 36 | { 37 | description: 'can be disabled with `transforms.spreadRest === false`', 38 | options: { transforms: { spreadRest: false } }, 39 | 40 | input: ` 41 | function foo ( ...list ) { 42 | // code goes here 43 | }`, 44 | 45 | output: ` 46 | function foo ( ...list ) { 47 | // code goes here 48 | }` 49 | } 50 | ]; 51 | -------------------------------------------------------------------------------- /test/samples/strip-with.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | description: 'strip with from Vue render functions', 4 | options: { 5 | transforms: { stripWith: true }, 6 | objectAssign: 'Object.assign' 7 | }, 8 | input: ` 9 | function render () { 10 | with (this) { 11 | return _h('div', items.map(function (item) { 12 | return _h('p', { 13 | class: [a, b + 'c', c ? d : item.e], 14 | style: { color, item, [prop]: true }, 15 | inlineTemplate: { 16 | render: function () { 17 | with (this) { 18 | return _h('span', ['hi', arguments[1]]) 19 | } 20 | } 21 | } 22 | }, item.tags.map(function (tag) { 23 | return _c('span', [item.id, tag.text, foo, a[b]]) 24 | }), item.stuff.map(([a, b], { c }) => { 25 | return _h('p', [a, b, c]) 26 | })) 27 | })) 28 | } 29 | } 30 | `, 31 | output: ` 32 | function render () { 33 | var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h; 34 | return _h('div', _vm.items.map(function (item) { 35 | var _obj; 36 | 37 | return _h('p', { 38 | class: [_vm.a, _vm.b + 'c', _vm.c ? _vm.d : item.e], 39 | style: ( _obj = { color: _vm.color, item: item }, _obj[_vm.prop] = true, _obj ), 40 | inlineTemplate: { 41 | render: function () { 42 | var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h; 43 | return _h('span', ['hi', arguments[1]]) 44 | 45 | } 46 | } 47 | }, item.tags.map(function (tag) { 48 | return _c('span', [item.id, tag.text, _vm.foo, _vm.a[_vm.b]]) 49 | }), item.stuff.map(function (ref, ref$1) { 50 | var a = ref[0]; 51 | var b = ref[1]; 52 | var c = ref$1.c; 53 | 54 | return _h('p', [a, b, c]) 55 | })) 56 | })) 57 | 58 | } 59 | ` 60 | }, 61 | { 62 | description: 'strip with w/ single line if', 63 | options: { 64 | transforms: { stripWith: true }, 65 | objectAssign: 'Object.assign' 66 | }, 67 | input: ` 68 | function render() { 69 | with(this){ 70 | if (true) return;text;} 71 | } 72 | `, 73 | output: ` 74 | function render() { 75 | var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h; 76 | if (true) { return; }_vm.text; 77 | } 78 | ` 79 | } 80 | ] 81 | -------------------------------------------------------------------------------- /test/samples/template-strings.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | description: 'transpiles an untagged template literal', 4 | input: 'var str = `foo${bar}baz`;', 5 | output: `var str = "foo" + bar + "baz";` 6 | }, 7 | 8 | { 9 | description: 'handles arbitrary whitespace inside template elements', 10 | input: 'var str = `foo${ bar }baz`;', 11 | output: `var str = "foo" + bar + "baz";` 12 | }, 13 | 14 | { 15 | description: 16 | 'transpiles an untagged template literal containing complex expressions', 17 | input: 'var str = `foo${bar + baz}qux`;', 18 | output: `var str = "foo" + (bar + baz) + "qux";` 19 | }, 20 | 21 | { 22 | description: 'transpiles a template literal containing single quotes', 23 | input: "var singleQuote = `'`;", 24 | output: `var singleQuote = "'";` 25 | }, 26 | 27 | { 28 | description: 'transpiles a template literal containing double quotes', 29 | input: 'var doubleQuote = `"`;', 30 | output: `var doubleQuote = "\\"";` 31 | }, 32 | 33 | { 34 | description: 'does not transpile tagged template literals', 35 | input: 'var str = x`y`', 36 | error: /Tagged template strings are not supported/ 37 | }, 38 | 39 | { 40 | description: 41 | 'transpiles tagged template literals with `transforms.dangerousTaggedTemplateString = true`', 42 | options: { transforms: { dangerousTaggedTemplateString: true } }, 43 | input: 'var str = x`y${(() => 42)()}`;', 44 | output: `var templateObject = Object.freeze(["y", ""]);\nvar str = x(templateObject, (function () { return 42; })());` 45 | }, 46 | 47 | { 48 | description: 49 | 'transpiles tagged template literals with `transforms.dangerousTaggedTemplateString = true`', 50 | options: { transforms: { dangerousTaggedTemplateString: true } }, 51 | input: 'var str = x`${(() => 42)()}y`;', 52 | output: `var templateObject = Object.freeze(["", "y"]);\nvar str = x(templateObject, (function () { return 42; })());` 53 | }, 54 | 55 | { 56 | description: 'reuses quasi array for identical tagged template strings', 57 | options: { transforms: { dangerousTaggedTemplateString: true } }, 58 | input: 'x`a${a}b`, x`a${b}b`, x`b${c}a`', 59 | output: `var templateObject$1 = Object.freeze(["b", "a"]);\nvar templateObject = Object.freeze(["a", "b"]);\nx(templateObject, a), x(templateObject, b), x(templateObject$1, c)` 60 | }, 61 | 62 | { 63 | description: 'reuses quasi array for identical tagged template strings in strict mode', 64 | options: { transforms: { dangerousTaggedTemplateString: true } }, 65 | input: '"use strict";\nx`a${a}b`, x`a${b}b`, x`b${c}a`', 66 | output: `"use strict";\nvar templateObject$1 = Object.freeze(["b", "a"]);\nvar templateObject = Object.freeze(["a", "b"]);\nx(templateObject, a), x(templateObject, b), x(templateObject$1, c)` 67 | }, 68 | 69 | { 70 | description: 'parenthesises template strings as necessary', 71 | input: 'var str = `x${y}`.toUpperCase();', 72 | output: 'var str = ("x" + y).toUpperCase();' 73 | }, 74 | 75 | { 76 | description: 'does not parenthesise plain template strings', 77 | input: 'var str = `x`.toUpperCase();', 78 | output: 'var str = "x".toUpperCase();' 79 | }, 80 | 81 | { 82 | description: 83 | 'does not parenthesise template strings in arithmetic expressions', 84 | input: 'var str = `x${y}` + z; var str2 = `x${y}` * z;', 85 | output: 'var str = "x" + y + z; var str2 = ("x" + y) * z;' 86 | }, 87 | 88 | { 89 | description: 'can be disabled with `transforms.templateString === false`', 90 | options: { transforms: { templateString: false } }, 91 | input: 'var a = `a`, b = c`b`;', 92 | output: 'var a = `a`, b = c`b`;' 93 | }, 94 | 95 | { 96 | description: 'skips leading empty string if possible', 97 | input: 'var str = `${a} ${b}`', 98 | output: 'var str = a + " " + b' 99 | }, 100 | 101 | { 102 | description: 'includes leading empty string if necessary', 103 | input: 'var str = `${a}${b}`', 104 | output: 'var str = "" + a + b' 105 | }, 106 | 107 | { 108 | description: 'closes parens if final empty string is omitted', 109 | input: 'var str = `1 + 1 = ${1 + 1}`;', 110 | output: 'var str = "1 + 1 = " + (1 + 1);' 111 | }, 112 | 113 | { 114 | description: 'allows empty template string', 115 | input: 'var str = ``;', 116 | output: 'var str = "";' 117 | }, 118 | 119 | { 120 | description: 'concats expression with variable', 121 | input: 'var str = `${a + b}${c}`;', 122 | output: 'var str = "" + (a + b) + c;' 123 | }, 124 | 125 | { 126 | description: 'interpolations inside interpolations', 127 | input: 'var string = `foo${`${bar}`}`', 128 | output: `var string = "foo" + ("" + bar)` 129 | } 130 | ]; 131 | -------------------------------------------------------------------------------- /test/samples/trailing-function-commas.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | description: 'strips trailing commas in call arguments', 4 | 5 | input: ` 6 | f(a,)`, 7 | 8 | output: ` 9 | f(a)` 10 | }, 11 | 12 | { 13 | description: 'strips trailing commas in function expression arguments', 14 | 15 | input: ` 16 | let f = function (a,) {}`, 17 | 18 | output: ` 19 | var f = function (a) {}` 20 | }, 21 | 22 | { 23 | description: 'strips trailing commas in normal function declaration arguments', 24 | 25 | input: ` 26 | function f(a,) {}`, 27 | 28 | output: ` 29 | function f(a) {}` 30 | }, 31 | 32 | { 33 | description: 'strips trailing commas in method arguments', 34 | 35 | input: ` 36 | class A { 37 | f(a,) {} 38 | }`, 39 | 40 | output: ` 41 | var A = function A () {}; 42 | 43 | A.prototype.f = function f (a) {};` 44 | }, 45 | 46 | { 47 | description: 'strips trailing commas in arrow function declaration arguments', 48 | 49 | input: ` 50 | ((a,) => {})`, 51 | 52 | output: ` 53 | (function (a) {})` 54 | }, 55 | 56 | { 57 | description: 'strips trailing commas after destructured argument in arrow function declaration arguments', 58 | 59 | input: ` 60 | ((a,[b],{c},) => {})`, 61 | 62 | output: ` 63 | (function (a,ref,ref$1) { 64 | var b = ref[0]; 65 | var c = ref$1.c; 66 | })` 67 | }, 68 | 69 | { 70 | description: 'strips trailing commas in new expression arguments', 71 | 72 | input: ` 73 | new f(a,)`, 74 | 75 | output: ` 76 | new f(a)` 77 | }, 78 | 79 | { 80 | description: 'keeps commas in trailing comments in normal function declaration arguments', 81 | 82 | input: ` 83 | function f(a/*,*/) {}`, 84 | 85 | output: ` 86 | function f(a/*,*/) {}` 87 | }, 88 | 89 | { 90 | description: 'strips trailing commas after comments in normal function declaration arguments', 91 | 92 | input: ` 93 | function f(a/*a*/,) {}`, 94 | 95 | output: ` 96 | function f(a/*a*/) {}` 97 | }, 98 | ]; 99 | -------------------------------------------------------------------------------- /test/utils/getLocation.js: -------------------------------------------------------------------------------- 1 | module.exports = function getLocation(source, search, start) { 2 | if (typeof search === 'string') { 3 | search = source.indexOf(search, start); 4 | } 5 | 6 | var lines = source.split('\n'); 7 | var len = lines.length; 8 | 9 | var lineStart = 0; 10 | var i; 11 | 12 | for (i = 0; i < len; i += 1) { 13 | var line = lines[i]; 14 | var lineEnd = lineStart + line.length + 1; // +1 for newline 15 | 16 | if (lineEnd > search) { 17 | return { line: i + 1, column: search - lineStart, char: i }; 18 | } 19 | 20 | lineStart = lineEnd; 21 | } 22 | 23 | throw new Error('Could not determine location of character'); 24 | }; 25 | --------------------------------------------------------------------------------