mirror of https://github.com/lukechilds/rollup.git
Rich-Harris
8 years ago
974 changed files with 8765 additions and 2347 deletions
@ -0,0 +1,2 @@ |
|||
test/* |
|||
!test/test.js |
@ -0,0 +1,18 @@ |
|||
<!-- |
|||
Thanks for raising an issue! To help us help you, if you've found a bug please consider the following: |
|||
|
|||
* If you can demonstrate the bug using the interactive http://rollupjs.org demo, please do. |
|||
* If that's not possible, perhaps because your bug involves plugins, we recommend creating a small repo that illustrates the problem. |
|||
|
|||
Reproductions should be small, self-contained, correct examples – http://sscce.org. For example, if your bug isn't related to Gulp, **do not include Gulp in the repro**. (Side-note: if it *is* related to Gulp, this is the wrong issue tracker!) If possible, use a config file as it's the standard, favoured approach to using Rollup: https://github.com/rollup/rollup/wiki/Command-Line-Interface#using-a-config-file |
|||
|
|||
Occasionally, this won't be possible, and that's fine – we still appreciate you raising the issue. But please understand that Rollup is run by unpaid volunteers in their free time, and we will prioritise bugs that follow these instructions. |
|||
|
|||
If you have a stack trace to include, we recommend putting inside a `<details>` block for the sake of the thread's readability: |
|||
|
|||
<details> |
|||
<summary>Stack trace</summary> |
|||
|
|||
Stack trace goes here... |
|||
</details> |
|||
--> |
@ -0,0 +1,7 @@ |
|||
<!-- |
|||
Thank you for creating a pull request. Before submitting, please note the following: |
|||
|
|||
* If your pull request implements a new feature, please raise an issue to discuss it before sending code. In many cases features are absent for a reason. |
|||
* This message body should clearly illustrate what problems it solves. If there are related issues, remember to reference them. |
|||
* Ideally, include a test that fails without this PR but passes with it. PRs will only be merged once they pass CI. (Remember to `npm run lint`!) |
|||
--> |
@ -1,9 +1,9 @@ |
|||
.DS_Store |
|||
node_modules |
|||
!test/node_modules |
|||
/node_modules |
|||
.gobble* |
|||
dist |
|||
_actual |
|||
coverage |
|||
.commithash |
|||
.idea |
|||
bin/rollup |
|||
|
@ -1,49 +0,0 @@ |
|||
var chalk = require( 'chalk' ); |
|||
|
|||
var handlers = { |
|||
MISSING_CONFIG: function () { |
|||
console.error( chalk.red( 'Config file must export an options object. See https://github.com/rollup/rollup/wiki/Command-Line-Interface#using-a-config-file' ) ); |
|||
}, |
|||
|
|||
MISSING_INPUT_OPTION: function () { |
|||
console.error( chalk.red( 'You must specify an --input (-i) option' ) ); |
|||
}, |
|||
|
|||
MISSING_OUTPUT_OPTION: function () { |
|||
console.error( chalk.red( 'You must specify an --output (-o) option when creating a file with a sourcemap' ) ); |
|||
}, |
|||
|
|||
MISSING_NAME: function ( err ) { |
|||
console.error( chalk.red( 'You must supply a name for UMD exports (e.g. `--name myModule`)' ) ); |
|||
}, |
|||
|
|||
PARSE_ERROR: function ( err ) { |
|||
console.error( chalk.red( 'Error parsing ' + err.file + ': ' + err.message ) ); |
|||
}, |
|||
|
|||
ONE_AT_A_TIME: function ( err ) { |
|||
console.error( chalk.red( 'rollup can only bundle one file at a time' ) ); |
|||
}, |
|||
|
|||
DUPLICATE_IMPORT_OPTIONS: function ( err ) { |
|||
console.error( chalk.red( 'use --input, or pass input path as argument' ) ); |
|||
} |
|||
}; |
|||
|
|||
module.exports = function handleError ( err ) { |
|||
var handler; |
|||
|
|||
if ( handler = handlers[ err && err.code ] ) { |
|||
handler( err ); |
|||
} else { |
|||
console.error( chalk.red( err.message || err ) ); |
|||
|
|||
if ( err.stack ) { |
|||
console.error( chalk.grey( err.stack ) ); |
|||
} |
|||
} |
|||
|
|||
console.error( 'Type ' + chalk.cyan( 'rollup --help' ) + ' for help, or visit https://github.com/rollup/rollup/wiki' ); |
|||
|
|||
process.exit( 1 ); |
|||
}; |
@ -1,37 +0,0 @@ |
|||
#!/usr/bin/env node |
|||
|
|||
var minimist = require( 'minimist' ), |
|||
command; |
|||
|
|||
command = minimist( process.argv.slice( 2 ), { |
|||
alias: { |
|||
// Aliases |
|||
strict: 'useStrict', |
|||
|
|||
// Short options |
|||
c: 'config', |
|||
d: 'indent', |
|||
e: 'external', |
|||
f: 'format', |
|||
g: 'globals', |
|||
h: 'help', |
|||
i: 'input', |
|||
m: 'sourcemap', |
|||
n: 'name', |
|||
o: 'output', |
|||
u: 'id', |
|||
v: 'version' |
|||
} |
|||
}); |
|||
|
|||
if ( command.help || ( process.argv.length <= 2 && process.stdin.isTTY ) ) { |
|||
require( './showHelp' )(); |
|||
} |
|||
|
|||
else if ( command.version ) { |
|||
console.log( 'rollup version ' + require( '../package.json' ).version ); |
|||
} |
|||
|
|||
else { |
|||
require( './runRollup' )( command ); |
|||
} |
@ -1,161 +0,0 @@ |
|||
require( 'source-map-support' ).install(); |
|||
|
|||
var path = require( 'path' ); |
|||
var handleError = require( './handleError' ); |
|||
var rollup = require( '../' ); |
|||
|
|||
// log to stderr to keep `rollup main.js > bundle.js` from breaking
|
|||
var log = console.error.bind(console); |
|||
|
|||
module.exports = function ( command ) { |
|||
if ( command._.length > 1 ) { |
|||
handleError({ code: 'ONE_AT_A_TIME' }); |
|||
} |
|||
|
|||
if ( command._.length === 1 ) { |
|||
if ( command.input ) { |
|||
handleError({ code: 'DUPLICATE_IMPORT_OPTIONS' }); |
|||
} |
|||
|
|||
command.input = command._[0]; |
|||
} |
|||
|
|||
if ( command.environment ) { |
|||
command.environment.split( ',' ).forEach( function ( pair ) { |
|||
var index = pair.indexOf( ':' ); |
|||
if ( ~index ) { |
|||
process.env[ pair.slice( 0, index ) ] = pair.slice( index + 1 ); |
|||
} else { |
|||
process.env[ pair ] = true; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
var config = command.config === true ? 'rollup.config.js' : command.config; |
|||
|
|||
if ( config ) { |
|||
config = path.resolve( config ); |
|||
|
|||
rollup.rollup({ |
|||
entry: config, |
|||
onwarn: function ( message ) { |
|||
if ( /Treating .+ as external dependency/.test( message ) ) return; |
|||
log( message ); |
|||
} |
|||
}).then( function ( bundle ) { |
|||
var code = bundle.generate({ |
|||
format: 'cjs' |
|||
}).code; |
|||
|
|||
// temporarily override require
|
|||
var defaultLoader = require.extensions[ '.js' ]; |
|||
require.extensions[ '.js' ] = function ( m, filename ) { |
|||
if ( filename === config ) { |
|||
m._compile( code, filename ); |
|||
} else { |
|||
defaultLoader( m, filename ); |
|||
} |
|||
}; |
|||
|
|||
try { |
|||
var options = require( path.resolve( config ) ); |
|||
if ( Object.keys( options ).length === 0 ) { |
|||
handleError({ code: 'MISSING_CONFIG' }); |
|||
} |
|||
} catch ( err ) { |
|||
handleError( err ); |
|||
} |
|||
|
|||
execute( options, command ); |
|||
|
|||
require.extensions[ '.js' ] = defaultLoader; |
|||
}) |
|||
.catch(log); |
|||
} else { |
|||
execute( {}, command ); |
|||
} |
|||
}; |
|||
|
|||
var equivalents = { |
|||
banner: 'banner', |
|||
footer: 'footer', |
|||
format: 'format', |
|||
globals: 'globals', |
|||
id: 'moduleId', |
|||
indent: 'indent', |
|||
input: 'entry', |
|||
intro: 'intro', |
|||
name: 'moduleName', |
|||
output: 'dest', |
|||
outro: 'outro', |
|||
sourcemap: 'sourceMap', |
|||
treeshake: 'treeshake' |
|||
}; |
|||
|
|||
function execute ( options, command ) { |
|||
var external = ( options.external || [] ) |
|||
.concat( command.external ? command.external.split( ',' ) : [] ); |
|||
|
|||
if ( command.globals ) { |
|||
var globals = Object.create( null ); |
|||
|
|||
command.globals.split( ',' ).forEach(function ( str ) { |
|||
var names = str.split( ':' ); |
|||
globals[ names[0] ] = names[1]; |
|||
|
|||
// Add missing Module IDs to external.
|
|||
if ( external.indexOf( names[0] ) === -1 ) { |
|||
external.push( names[0] ); |
|||
} |
|||
}); |
|||
|
|||
command.globals = globals; |
|||
} |
|||
|
|||
options.onwarn = options.onwarn || log; |
|||
|
|||
options.external = external; |
|||
|
|||
options.noConflict = command.conflict === false; |
|||
delete command.conflict; |
|||
|
|||
// Use any options passed through the CLI as overrides.
|
|||
Object.keys( equivalents ).forEach( function ( cliOption ) { |
|||
if ( command.hasOwnProperty( cliOption ) ) { |
|||
options[ equivalents[ cliOption ] ] = command[ cliOption ]; |
|||
} |
|||
}); |
|||
|
|||
try { |
|||
bundle( options ).catch( handleError ); |
|||
} catch ( err ) { |
|||
handleError( err ); |
|||
} |
|||
} |
|||
|
|||
function bundle ( options ) { |
|||
if ( !options.entry ) { |
|||
handleError({ code: 'MISSING_INPUT_OPTION' }); |
|||
} |
|||
|
|||
return rollup.rollup( options ).then( function ( bundle ) { |
|||
if ( options.dest ) { |
|||
return bundle.write( options ); |
|||
} |
|||
|
|||
if ( options.sourceMap && options.sourceMap !== 'inline' ) { |
|||
handleError({ code: 'MISSING_OUTPUT_OPTION' }); |
|||
} |
|||
|
|||
var result = bundle.generate( options ); |
|||
|
|||
var code = result.code, |
|||
map = result.map; |
|||
|
|||
if ( options.sourceMap === 'inline' ) { |
|||
code += '\n//# sourceMappingURL=' + map.toUrl(); |
|||
} |
|||
|
|||
process.stdout.write( code ); |
|||
}); |
|||
} |
@ -1,13 +0,0 @@ |
|||
var fs = require( 'fs' ); |
|||
var path = require( 'path' ); |
|||
|
|||
module.exports = function () { |
|||
fs.readFile( path.join( __dirname, 'help.md' ), function ( err, result ) { |
|||
var help; |
|||
|
|||
if ( err ) throw err; |
|||
|
|||
help = result.toString().replace( '<%= version %>', require( '../package.json' ).version ); |
|||
console.log( '\n' + help + '\n' ); |
|||
}); |
|||
}; |
@ -0,0 +1,65 @@ |
|||
import * as chalk from 'chalk'; |
|||
|
|||
function stderr ( msg ) { |
|||
console.error( msg ); // eslint-disable-line no-console
|
|||
} |
|||
|
|||
const handlers = { |
|||
MISSING_CONFIG: () => { |
|||
stderr( chalk.red( 'Config file must export an options object. See https://github.com/rollup/rollup/wiki/Command-Line-Interface#using-a-config-file' ) ); |
|||
}, |
|||
|
|||
MISSING_EXTERNAL_CONFIG: err => { |
|||
stderr( chalk.red( `Could not resolve config file ${err.config}` ) ); |
|||
}, |
|||
|
|||
MISSING_INPUT_OPTION: () => { |
|||
stderr( chalk.red( 'You must specify an --input (-i) option' ) ); |
|||
}, |
|||
|
|||
MISSING_OUTPUT_OPTION: () => { |
|||
stderr( chalk.red( 'You must specify an --output (-o) option when creating a file with a sourcemap' ) ); |
|||
}, |
|||
|
|||
MISSING_NAME: () => { |
|||
stderr( chalk.red( 'You must supply a name for UMD exports (e.g. `--name myModule`)' ) ); |
|||
}, |
|||
|
|||
PARSE_ERROR: err => { |
|||
stderr( chalk.red( `Error parsing ${err.file}: ${err.message}` ) ); |
|||
}, |
|||
|
|||
ONE_AT_A_TIME: () => { |
|||
stderr( chalk.red( 'rollup can only bundle one file at a time' ) ); |
|||
}, |
|||
|
|||
DUPLICATE_IMPORT_OPTIONS: () => { |
|||
stderr( chalk.red( 'use --input, or pass input path as argument' ) ); |
|||
}, |
|||
|
|||
ROLLUP_WATCH_NOT_INSTALLED: () => { |
|||
stderr( chalk.red( 'rollup --watch depends on the rollup-watch package, which could not be found. You can install it globally (recommended) with ' ) + chalk.cyan( 'npm install -g rollup-watch' ) ); |
|||
}, |
|||
|
|||
WATCHER_MISSING_INPUT_OR_OUTPUT: () => { |
|||
stderr( chalk.red( 'must specify --input and --output when using rollup --watch' ) ); |
|||
} |
|||
}; |
|||
|
|||
export default function handleError ( err, recover ) { |
|||
const handler = handlers[ err && err.code ]; |
|||
|
|||
if ( handler ) { |
|||
handler( err ); |
|||
} else { |
|||
stderr( chalk.red( err.message || err ) ); |
|||
|
|||
if ( err.stack ) { |
|||
stderr( chalk.grey( err.stack ) ); |
|||
} |
|||
} |
|||
|
|||
stderr( `Type ${chalk.cyan( 'rollup --help' )} for help, or visit https://github.com/rollup/rollup/wiki` ); |
|||
|
|||
if ( !recover ) process.exit( 1 ); |
|||
} |
@ -0,0 +1,39 @@ |
|||
import minimist from 'minimist'; |
|||
import help from './help.md'; |
|||
import { version } from '../../package.json'; |
|||
import runRollup from './runRollup'; |
|||
|
|||
const command = minimist( process.argv.slice( 2 ), { |
|||
alias: { |
|||
// Aliases
|
|||
strict: 'useStrict', |
|||
|
|||
// Short options
|
|||
c: 'config', |
|||
d: 'indent', |
|||
e: 'external', |
|||
f: 'format', |
|||
g: 'globals', |
|||
h: 'help', |
|||
i: 'input', |
|||
l: 'legacy', |
|||
m: 'sourcemap', |
|||
n: 'name', |
|||
o: 'output', |
|||
u: 'id', |
|||
v: 'version', |
|||
w: 'watch' |
|||
} |
|||
}); |
|||
|
|||
if ( command.help || ( process.argv.length <= 2 && process.stdin.isTTY ) ) { |
|||
console.log( `\n${help.replace('__VERSION__', version)}\n` ); // eslint-disable-line no-console
|
|||
} |
|||
|
|||
else if ( command.version ) { |
|||
console.log( `rollup version ${version}` ); // eslint-disable-line no-console
|
|||
} |
|||
|
|||
else { |
|||
runRollup( command ); |
|||
} |
@ -0,0 +1,247 @@ |
|||
import { realpathSync } from 'fs'; |
|||
import * as rollup from 'rollup'; |
|||
import relative from 'require-relative'; |
|||
import handleError from './handleError'; |
|||
import SOURCEMAPPING_URL from './sourceMappingUrl.js'; |
|||
|
|||
import { install as installSourcemapSupport } from 'source-map-support'; |
|||
installSourcemapSupport(); |
|||
|
|||
// stderr to stderr to keep `rollup main.js > bundle.js` from breaking
|
|||
const stderr = console.error.bind( console ); // eslint-disable-line no-console
|
|||
|
|||
export default function runRollup ( command ) { |
|||
if ( command._.length > 1 ) { |
|||
handleError({ code: 'ONE_AT_A_TIME' }); |
|||
} |
|||
|
|||
if ( command._.length === 1 ) { |
|||
if ( command.input ) { |
|||
handleError({ code: 'DUPLICATE_IMPORT_OPTIONS' }); |
|||
} |
|||
|
|||
command.input = command._[0]; |
|||
} |
|||
|
|||
if ( command.environment ) { |
|||
command.environment.split( ',' ).forEach( pair => { |
|||
const index = pair.indexOf( ':' ); |
|||
if ( ~index ) { |
|||
process.env[ pair.slice( 0, index ) ] = pair.slice( index + 1 ); |
|||
} else { |
|||
process.env[ pair ] = true; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
let config = command.config === true ? 'rollup.config.js' : command.config; |
|||
|
|||
if ( config ) { |
|||
if ( config.slice( 0, 5 ) === 'node:' ) { |
|||
const pkgName = config.slice( 5 ); |
|||
try { |
|||
config = relative.resolve( `rollup-config-${pkgName}`, process.cwd() ); |
|||
} catch ( err ) { |
|||
try { |
|||
config = relative.resolve( pkgName, process.cwd() ); |
|||
} catch ( err ) { |
|||
if ( err.code === 'MODULE_NOT_FOUND' ) { |
|||
handleError({ code: 'MISSING_EXTERNAL_CONFIG', config }); |
|||
} |
|||
|
|||
throw err; |
|||
} |
|||
} |
|||
} else { |
|||
// find real path of config so it matches what Node provides to callbacks in require.extensions
|
|||
config = realpathSync( config ); |
|||
} |
|||
|
|||
rollup.rollup({ |
|||
entry: config, |
|||
onwarn: message => { |
|||
if ( /Treating .+ as external dependency/.test( message ) ) return; |
|||
stderr( message ); |
|||
} |
|||
}).then( bundle => { |
|||
const { code } = bundle.generate({ |
|||
format: 'cjs' |
|||
}); |
|||
|
|||
// temporarily override require
|
|||
const defaultLoader = require.extensions[ '.js' ]; |
|||
require.extensions[ '.js' ] = ( m, filename ) => { |
|||
if ( filename === config ) { |
|||
m._compile( code, filename ); |
|||
} else { |
|||
defaultLoader( m, filename ); |
|||
} |
|||
}; |
|||
|
|||
try { |
|||
const options = require( config ); |
|||
if ( Object.keys( options ).length === 0 ) { |
|||
handleError({ code: 'MISSING_CONFIG' }); |
|||
} |
|||
execute( options, command ); |
|||
require.extensions[ '.js' ] = defaultLoader; |
|||
} catch ( err ) { |
|||
handleError( err ); |
|||
} |
|||
}) |
|||
.catch( stderr ); |
|||
} else { |
|||
execute( {}, command ); |
|||
} |
|||
} |
|||
|
|||
const equivalents = { |
|||
useStrict: 'useStrict', |
|||
banner: 'banner', |
|||
footer: 'footer', |
|||
format: 'format', |
|||
globals: 'globals', |
|||
id: 'moduleId', |
|||
indent: 'indent', |
|||
input: 'entry', |
|||
intro: 'intro', |
|||
legacy: 'legacy', |
|||
name: 'moduleName', |
|||
output: 'dest', |
|||
outro: 'outro', |
|||
sourcemap: 'sourceMap', |
|||
treeshake: 'treeshake' |
|||
}; |
|||
|
|||
function execute ( options, command ) { |
|||
let external; |
|||
|
|||
const commandExternal = ( command.external || '' ).split( ',' ); |
|||
const optionsExternal = options.external; |
|||
|
|||
if ( command.globals ) { |
|||
let globals = Object.create( null ); |
|||
|
|||
command.globals.split( ',' ).forEach( str => { |
|||
const names = str.split( ':' ); |
|||
globals[ names[0] ] = names[1]; |
|||
|
|||
// Add missing Module IDs to external.
|
|||
if ( commandExternal.indexOf( names[0] ) === -1 ) { |
|||
commandExternal.push( names[0] ); |
|||
} |
|||
}); |
|||
|
|||
command.globals = globals; |
|||
} |
|||
|
|||
if ( typeof optionsExternal === 'function' ) { |
|||
external = id => { |
|||
return optionsExternal( id ) || ~commandExternal.indexOf( id ); |
|||
}; |
|||
} else { |
|||
external = ( optionsExternal || [] ).concat( commandExternal ); |
|||
} |
|||
|
|||
options.onwarn = options.onwarn || stderr; |
|||
|
|||
options.external = external; |
|||
|
|||
// Use any options passed through the CLI as overrides.
|
|||
Object.keys( equivalents ).forEach( cliOption => { |
|||
if ( command.hasOwnProperty( cliOption ) ) { |
|||
options[ equivalents[ cliOption ] ] = command[ cliOption ]; |
|||
} |
|||
}); |
|||
|
|||
try { |
|||
if ( command.watch ) { |
|||
if ( !options.entry || ( !options.dest && !options.targets ) ) { |
|||
handleError({ code: 'WATCHER_MISSING_INPUT_OR_OUTPUT' }); |
|||
} |
|||
|
|||
try { |
|||
const watch = relative( 'rollup-watch', process.cwd() ); |
|||
const watcher = watch( rollup, options ); |
|||
|
|||
watcher.on( 'event', event => { |
|||
switch ( event.code ) { |
|||
case 'STARTING': |
|||
stderr( 'checking rollup-watch version...' ); |
|||
break; |
|||
|
|||
case 'BUILD_START': |
|||
stderr( 'bundling...' ); |
|||
break; |
|||
|
|||
case 'BUILD_END': |
|||
stderr( 'bundled in ' + event.duration + 'ms. Watching for changes...' ); |
|||
break; |
|||
|
|||
case 'ERROR': |
|||
handleError( event.error, true ); |
|||
break; |
|||
|
|||
default: |
|||
stderr( 'unknown event', event ); |
|||
} |
|||
}); |
|||
} catch ( err ) { |
|||
if ( err.code === 'MODULE_NOT_FOUND' ) { |
|||
err.code = 'ROLLUP_WATCH_NOT_INSTALLED'; |
|||
} |
|||
|
|||
handleError( err ); |
|||
} |
|||
} else { |
|||
bundle( options ).catch( handleError ); |
|||
} |
|||
} catch ( err ) { |
|||
handleError( err ); |
|||
} |
|||
} |
|||
|
|||
function clone ( object ) { |
|||
return assign( {}, object ); |
|||
} |
|||
|
|||
function assign ( target, source ) { |
|||
Object.keys( source ).forEach( key => { |
|||
target[ key ] = source[ key ]; |
|||
}); |
|||
return target; |
|||
} |
|||
|
|||
function bundle ( options ) { |
|||
if ( !options.entry ) { |
|||
handleError({ code: 'MISSING_INPUT_OPTION' }); |
|||
} |
|||
|
|||
return rollup.rollup( options ).then( bundle => { |
|||
if ( options.dest ) { |
|||
return bundle.write( options ); |
|||
} |
|||
|
|||
if ( options.targets ) { |
|||
let result = null; |
|||
|
|||
options.targets.forEach( target => { |
|||
result = bundle.write( assign( clone( options ), target ) ); |
|||
}); |
|||
|
|||
return result; |
|||
} |
|||
|
|||
if ( options.sourceMap && options.sourceMap !== 'inline' ) { |
|||
handleError({ code: 'MISSING_OUTPUT_OPTION' }); |
|||
} |
|||
|
|||
let { code, map } = bundle.generate( options ); |
|||
|
|||
if ( options.sourceMap === 'inline' ) { |
|||
code += `\n//# ${SOURCEMAPPING_URL}=${map.toUrl()}\n`; |
|||
} |
|||
|
|||
process.stdout.write( code ); |
|||
}); |
|||
} |
@ -0,0 +1,4 @@ |
|||
let SOURCEMAPPING_URL = 'sourceMa'; |
|||
SOURCEMAPPING_URL += 'ppingURL'; |
|||
|
|||
export default SOURCEMAPPING_URL; |
@ -0,0 +1,80 @@ |
|||
export const absolutePath = /^(?:\/|(?:[A-Za-z]:)?[\\|\/])/; |
|||
export const relativePath = /^\.?\.\//; |
|||
|
|||
export function isAbsolute ( path ) { |
|||
return absolutePath.test( path ); |
|||
} |
|||
|
|||
export function isRelative ( path ) { |
|||
return relativePath.test( path ); |
|||
} |
|||
|
|||
export function normalize ( path ) { |
|||
return path.replace( /\\/g, '/' ); |
|||
} |
|||
|
|||
export function basename ( path ) { |
|||
return path.split( /(\/|\\)/ ).pop(); |
|||
} |
|||
|
|||
export function dirname ( path ) { |
|||
const match = /(\/|\\)[^\/\\]*$/.exec( path ); |
|||
if ( !match ) return '.'; |
|||
|
|||
const dir = path.slice( 0, -match[0].length ); |
|||
|
|||
// If `dir` is the empty string, we're at root.
|
|||
return dir ? dir : '/'; |
|||
} |
|||
|
|||
export function extname ( path ) { |
|||
const match = /\.[^\.]+$/.exec( basename( path ) ); |
|||
if ( !match ) return ''; |
|||
return match[0]; |
|||
} |
|||
|
|||
export function relative ( from, to ) { |
|||
const fromParts = from.split( /[\/\\]/ ).filter( Boolean ); |
|||
const toParts = to.split( /[\/\\]/ ).filter( Boolean ); |
|||
|
|||
while ( fromParts[0] && toParts[0] && fromParts[0] === toParts[0] ) { |
|||
fromParts.shift(); |
|||
toParts.shift(); |
|||
} |
|||
|
|||
while ( toParts[0] === '.' || toParts[0] === '..' ) { |
|||
const toPart = toParts.shift(); |
|||
if ( toPart === '..' ) { |
|||
fromParts.pop(); |
|||
} |
|||
} |
|||
|
|||
while ( fromParts.pop() ) { |
|||
toParts.unshift( '..' ); |
|||
} |
|||
|
|||
return toParts.join( '/' ); |
|||
} |
|||
|
|||
export function resolve ( ...paths ) { |
|||
let resolvedParts = paths.shift().split( /[\/\\]/ ); |
|||
|
|||
paths.forEach( path => { |
|||
if ( isAbsolute( path ) ) { |
|||
resolvedParts = path.split( /[\/\\]/ ); |
|||
} else { |
|||
const parts = path.split( /[\/\\]/ ); |
|||
|
|||
while ( parts[0] === '.' || parts[0] === '..' ) { |
|||
const part = parts.shift(); |
|||
if ( part === '..' ) { |
|||
resolvedParts.pop(); |
|||
} |
|||
} |
|||
|
|||
resolvedParts.push.apply( resolvedParts, parts ); |
|||
} |
|||
}); |
|||
|
|||
return resolvedParts.join( '/' ); // TODO windows...
|
|||
} |
@ -1 +0,0 @@ |
|||
export default window.Promise; |
@ -0,0 +1,34 @@ |
|||
import buble from 'rollup-plugin-buble'; |
|||
import json from 'rollup-plugin-json'; |
|||
import string from 'rollup-plugin-string'; |
|||
import nodeResolve from 'rollup-plugin-node-resolve'; |
|||
import commonjs from 'rollup-plugin-commonjs'; |
|||
|
|||
export default { |
|||
entry: 'bin/src/index.js', |
|||
dest: 'bin/rollup', |
|||
format: 'cjs', |
|||
banner: '#!/usr/bin/env node', |
|||
plugins: [ |
|||
string({ include: '**/*.md' }), |
|||
json(), |
|||
buble(), |
|||
commonjs({ |
|||
include: 'node_modules/**', |
|||
namedExports: { chalk: [ 'red', 'cyan', 'grey' ] } |
|||
}), |
|||
nodeResolve({ |
|||
main: true |
|||
}) |
|||
], |
|||
external: [ |
|||
'fs', |
|||
'path', |
|||
'module', |
|||
'source-map-support', |
|||
'rollup' |
|||
], |
|||
paths: { |
|||
rollup: '../dist/rollup.js' |
|||
} |
|||
}; |
@ -1,30 +0,0 @@ |
|||
export class Reference { |
|||
constructor ( node, scope, statement ) { |
|||
this.node = node; |
|||
this.scope = scope; |
|||
this.statement = statement; |
|||
|
|||
this.declaration = null; // bound later
|
|||
|
|||
this.parts = []; |
|||
|
|||
let root = node; |
|||
while ( root.type === 'MemberExpression' ) { |
|||
this.parts.unshift( root.property ); |
|||
root = root.object; |
|||
} |
|||
|
|||
this.name = root.name; |
|||
|
|||
this.start = node.start; |
|||
this.end = node.start + this.name.length; // can be overridden in the case of namespace members
|
|||
this.rewritten = false; |
|||
} |
|||
} |
|||
|
|||
export class SyntheticReference { |
|||
constructor ( name ) { |
|||
this.name = name; |
|||
this.parts = []; |
|||
} |
|||
} |
@ -1,155 +0,0 @@ |
|||
import { walk } from 'estree-walker'; |
|||
import Scope from './ast/Scope.js'; |
|||
import attachScopes from './ast/attachScopes.js'; |
|||
import modifierNodes, { isModifierNode } from './ast/modifierNodes.js'; |
|||
import isFunctionDeclaration from './ast/isFunctionDeclaration.js'; |
|||
import isReference from './ast/isReference.js'; |
|||
import getLocation from './utils/getLocation.js'; |
|||
import run from './utils/run.js'; |
|||
import { Reference } from './Reference.js'; |
|||
|
|||
export default class Statement { |
|||
constructor ( node, module, start, end ) { |
|||
this.node = node; |
|||
this.module = module; |
|||
this.start = start; |
|||
this.end = end; |
|||
this.next = null; // filled in later
|
|||
|
|||
this.scope = new Scope({ statement: this }); |
|||
|
|||
this.references = []; |
|||
this.stringLiteralRanges = []; |
|||
|
|||
this.isIncluded = false; |
|||
this.ran = false; |
|||
|
|||
this.isImportDeclaration = node.type === 'ImportDeclaration'; |
|||
this.isExportDeclaration = /^Export/.test( node.type ); |
|||
this.isReexportDeclaration = this.isExportDeclaration && !!node.source; |
|||
|
|||
this.isFunctionDeclaration = isFunctionDeclaration( node ) || |
|||
this.isExportDeclaration && isFunctionDeclaration( node.declaration ); |
|||
} |
|||
|
|||
firstPass () { |
|||
if ( this.isImportDeclaration ) return; // nothing to analyse
|
|||
|
|||
// attach scopes
|
|||
attachScopes( this ); |
|||
|
|||
// find references
|
|||
const statement = this; |
|||
let { module, references, scope, stringLiteralRanges } = this; |
|||
let readDepth = 0; |
|||
|
|||
walk( this.node, { |
|||
enter ( node, parent, prop ) { |
|||
// warn about eval
|
|||
if ( node.type === 'CallExpression' && node.callee.name === 'eval' && !scope.contains( 'eval' ) ) { |
|||
// TODO show location
|
|||
module.bundle.onwarn( `Use of \`eval\` (in ${module.id}) is strongly discouraged, as it poses security risks and may cause issues with minification. See https://github.com/rollup/rollup/wiki/Troubleshooting#avoiding-eval for more details` ); |
|||
} |
|||
|
|||
// skip re-export declarations
|
|||
if ( node.type === 'ExportNamedDeclaration' && node.source ) return this.skip(); |
|||
|
|||
if ( node.type === 'TemplateElement' ) stringLiteralRanges.push([ node.start, node.end ]); |
|||
if ( node.type === 'Literal' && typeof node.value === 'string' && /\n/.test( node.raw ) ) { |
|||
stringLiteralRanges.push([ node.start + 1, node.end - 1 ]); |
|||
} |
|||
|
|||
if ( node._scope ) scope = node._scope; |
|||
if ( /Function/.test( node.type ) ) readDepth += 1; |
|||
|
|||
let isReassignment; |
|||
|
|||
if ( parent && isModifierNode( parent ) ) { |
|||
let subject = parent[ modifierNodes[ parent.type ] ]; |
|||
|
|||
if ( node === subject ) { |
|||
let depth = 0; |
|||
|
|||
while ( subject.type === 'MemberExpression' ) { |
|||
subject = subject.object; |
|||
depth += 1; |
|||
} |
|||
|
|||
const importDeclaration = module.imports[ subject.name ]; |
|||
|
|||
if ( !scope.contains( subject.name ) && importDeclaration ) { |
|||
const minDepth = importDeclaration.name === '*' ? |
|||
2 : // cannot do e.g. `namespace.foo = bar`
|
|||
1; // cannot do e.g. `foo = bar`, but `foo.bar = bar` is fine
|
|||
|
|||
if ( depth < minDepth ) { |
|||
const err = new Error( `Illegal reassignment to import '${subject.name}'` ); |
|||
err.file = module.id; |
|||
err.loc = getLocation( module.magicString.original, subject.start ); |
|||
throw err; |
|||
} |
|||
} |
|||
|
|||
isReassignment = !depth; |
|||
} |
|||
} |
|||
|
|||
if ( isReference( node, parent ) ) { |
|||
// function declaration IDs are a special case – they're associated
|
|||
// with the parent scope
|
|||
const referenceScope = parent.type === 'FunctionDeclaration' && node === parent.id ? |
|||
scope.parent : |
|||
scope; |
|||
|
|||
const isShorthandProperty = parent.type === 'Property' && parent.shorthand; |
|||
|
|||
// Since `node.key` can equal `node.value` for shorthand properties
|
|||
// we must use the `prop` argument provided by `estree-walker` to determine
|
|||
// if we're looking at the key or the value.
|
|||
// If they are equal, we'll return to not create duplicate references.
|
|||
if ( isShorthandProperty && parent.value === parent.key && prop === 'value' ) { |
|||
return; |
|||
} |
|||
|
|||
const reference = new Reference( node, referenceScope, statement ); |
|||
reference.isReassignment = isReassignment; |
|||
reference.isShorthandProperty = isShorthandProperty; |
|||
references.push( reference ); |
|||
|
|||
this.skip(); // don't descend from `foo.bar.baz` into `foo.bar`
|
|||
} |
|||
}, |
|||
leave ( node ) { |
|||
if ( node._scope ) scope = scope.parent; |
|||
if ( /Function/.test( node.type ) ) readDepth -= 1; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
mark () { |
|||
if ( this.isIncluded ) return; // prevent infinite loops
|
|||
this.isIncluded = true; |
|||
|
|||
this.references.forEach( reference => { |
|||
if ( reference.declaration ) reference.declaration.use(); |
|||
}); |
|||
} |
|||
|
|||
run ( strongDependencies ) { |
|||
if ( ( this.ran && this.isIncluded ) || this.isImportDeclaration || this.isFunctionDeclaration ) return; |
|||
this.ran = true; |
|||
|
|||
if ( run( this.node, this.scope, this, strongDependencies, false ) ) { |
|||
this.mark(); |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
source () { |
|||
return this.module.source.slice( this.start, this.end ); |
|||
} |
|||
|
|||
toString () { |
|||
return this.module.magicString.slice( this.start, this.end ); |
|||
} |
|||
} |
@ -0,0 +1,100 @@ |
|||
import { UNKNOWN } from './values.js'; |
|||
import getLocation from '../utils/getLocation.js'; |
|||
|
|||
export default class Node { |
|||
bind ( scope ) { |
|||
this.eachChild( child => child.bind( this.scope || scope ) ); |
|||
} |
|||
|
|||
eachChild ( callback ) { |
|||
for ( const key of this.keys ) { |
|||
if ( this.shorthand && key === 'key' ) continue; // key and value are the same
|
|||
|
|||
const value = this[ key ]; |
|||
|
|||
if ( value ) { |
|||
if ( 'length' in value ) { |
|||
for ( const child of value ) { |
|||
if ( child ) callback( child ); |
|||
} |
|||
} else if ( value ) { |
|||
callback( value ); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
findParent ( selector ) { |
|||
return selector.test( this.type ) ? this : this.parent.findParent( selector ); |
|||
} |
|||
|
|||
// TODO abolish findScope. if a node needs to store scope, store it
|
|||
findScope ( functionScope ) { |
|||
return this.parent.findScope( functionScope ); |
|||
} |
|||
|
|||
gatherPossibleValues ( values ) { |
|||
//this.eachChild( child => child.gatherPossibleValues( values ) );
|
|||
values.add( UNKNOWN ); |
|||
} |
|||
|
|||
getValue () { |
|||
return UNKNOWN; |
|||
} |
|||
|
|||
hasEffects ( scope ) { |
|||
if ( this.scope ) scope = this.scope; |
|||
|
|||
for ( const key of this.keys ) { |
|||
const value = this[ key ]; |
|||
|
|||
if ( value ) { |
|||
if ( 'length' in value ) { |
|||
for ( const child of value ) { |
|||
if ( child && child.hasEffects( scope ) ) { |
|||
return true; |
|||
} |
|||
} |
|||
} else if ( value && value.hasEffects( scope ) ) { |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
initialise ( scope ) { |
|||
this.eachChild( child => child.initialise( this.scope || scope ) ); |
|||
} |
|||
|
|||
insertSemicolon ( code ) { |
|||
if ( code.original[ this.end - 1 ] !== ';' ) { |
|||
code.insertLeft( this.end, ';' ); |
|||
} |
|||
} |
|||
|
|||
locate () { |
|||
// useful for debugging
|
|||
const location = getLocation( this.module.code, this.start ); |
|||
location.file = this.module.id; |
|||
location.toString = () => JSON.stringify( location ); |
|||
|
|||
return location; |
|||
} |
|||
|
|||
render ( code, es ) { |
|||
this.eachChild( child => child.render( code, es ) ); |
|||
} |
|||
|
|||
run ( scope ) { |
|||
if ( this.ran ) return; |
|||
this.ran = true; |
|||
|
|||
this.eachChild( child => { |
|||
child.run( this.scope || scope ); |
|||
}); |
|||
} |
|||
|
|||
toString () { |
|||
return this.module.code.slice( this.start, this.end ); |
|||
} |
|||
} |
@ -1,52 +0,0 @@ |
|||
import { blank, keys } from '../utils/object.js'; |
|||
import Declaration from '../Declaration.js'; |
|||
import extractNames from './extractNames.js'; |
|||
|
|||
export default class Scope { |
|||
constructor ( options ) { |
|||
options = options || {}; |
|||
|
|||
this.parent = options.parent; |
|||
this.statement = options.statement || this.parent.statement; |
|||
this.isBlockScope = !!options.block; |
|||
this.isTopLevel = !this.parent || ( this.parent.isTopLevel && this.isBlockScope ); |
|||
|
|||
this.declarations = blank(); |
|||
|
|||
if ( options.params ) { |
|||
options.params.forEach( param => { |
|||
extractNames( param ).forEach( name => { |
|||
this.declarations[ name ] = new Declaration( param, true, this.statement ); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
addDeclaration ( node, isBlockDeclaration, isVar ) { |
|||
if ( !isBlockDeclaration && this.isBlockScope ) { |
|||
// it's a `var` or function node, and this
|
|||
// is a block scope, so we need to go up
|
|||
this.parent.addDeclaration( node, isBlockDeclaration, isVar ); |
|||
} else { |
|||
extractNames( node.id ).forEach( name => { |
|||
this.declarations[ name ] = new Declaration( node, false, this.statement ); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
contains ( name ) { |
|||
return this.declarations[ name ] || |
|||
( this.parent ? this.parent.contains( name ) : false ); |
|||
} |
|||
|
|||
eachDeclaration ( fn ) { |
|||
keys( this.declarations ).forEach( key => { |
|||
fn( key, this.declarations[ key ] ); |
|||
}); |
|||
} |
|||
|
|||
findDeclaration ( name ) { |
|||
return this.declarations[ name ] || |
|||
( this.parent && this.parent.findDeclaration( name ) ); |
|||
} |
|||
} |
@ -1,78 +0,0 @@ |
|||
import { walk } from 'estree-walker'; |
|||
import Scope from './Scope.js'; |
|||
|
|||
const blockDeclarations = { |
|||
'const': true, |
|||
'let': true |
|||
}; |
|||
|
|||
export default function attachScopes ( statement ) { |
|||
let { node, scope } = statement; |
|||
|
|||
walk( node, { |
|||
enter ( node, parent ) { |
|||
// function foo () {...}
|
|||
// class Foo {...}
|
|||
if ( /(Function|Class)Declaration/.test( node.type ) ) { |
|||
scope.addDeclaration( node, false, false ); |
|||
} |
|||
|
|||
// var foo = 1, bar = 2
|
|||
if ( node.type === 'VariableDeclaration' ) { |
|||
const isBlockDeclaration = blockDeclarations[ node.kind ]; |
|||
|
|||
node.declarations.forEach( declarator => { |
|||
scope.addDeclaration( declarator, isBlockDeclaration, true ); |
|||
}); |
|||
} |
|||
|
|||
let newScope; |
|||
|
|||
// create new function scope
|
|||
if ( /(Function|Class)/.test( node.type ) ) { |
|||
newScope = new Scope({ |
|||
parent: scope, |
|||
block: false, |
|||
params: node.params |
|||
}); |
|||
|
|||
// named function expressions - the name is considered
|
|||
// part of the function's scope
|
|||
if ( /(Function|Class)Expression/.test( node.type ) && node.id ) { |
|||
newScope.addDeclaration( node, false, false ); |
|||
} |
|||
} |
|||
|
|||
// create new block scope
|
|||
if ( node.type === 'BlockStatement' && ( !parent || !/Function/.test( parent.type ) ) ) { |
|||
newScope = new Scope({ |
|||
parent: scope, |
|||
block: true |
|||
}); |
|||
} |
|||
|
|||
// catch clause has its own block scope
|
|||
if ( node.type === 'CatchClause' ) { |
|||
newScope = new Scope({ |
|||
parent: scope, |
|||
params: [ node.param ], |
|||
block: true |
|||
}); |
|||
} |
|||
|
|||
if ( newScope ) { |
|||
Object.defineProperty( node, '_scope', { |
|||
value: newScope, |
|||
configurable: true |
|||
}); |
|||
|
|||
scope = newScope; |
|||
} |
|||
}, |
|||
leave ( node ) { |
|||
if ( node._scope ) { |
|||
scope = scope.parent; |
|||
} |
|||
} |
|||
}); |
|||
} |
@ -1,38 +0,0 @@ |
|||
export function isTruthy ( node ) { |
|||
if ( node.type === 'Literal' ) return !!node.value; |
|||
if ( node.type === 'ParenthesizedExpression' ) return isTruthy( node.expression ); |
|||
if ( node.operator in operators ) return operators[ node.operator ]( node ); |
|||
} |
|||
|
|||
export function isFalsy ( node ) { |
|||
return not( isTruthy( node ) ); |
|||
} |
|||
|
|||
function not ( value ) { |
|||
return value === undefined ? value : !value; |
|||
} |
|||
|
|||
function equals ( a, b, strict ) { |
|||
if ( a.type !== b.type ) return undefined; |
|||
if ( a.type === 'Literal' ) return strict ? a.value === b.value : a.value == b.value; |
|||
} |
|||
|
|||
const operators = { |
|||
'==': x => { |
|||
return equals( x.left, x.right, false ); |
|||
}, |
|||
|
|||
'!=': x => not( operators['==']( x ) ), |
|||
|
|||
'===': x => { |
|||
return equals( x.left, x.right, true ); |
|||
}, |
|||
|
|||
'!==': x => not( operators['===']( x ) ), |
|||
|
|||
'!': x => isFalsy( x.argument ), |
|||
|
|||
'&&': x => isTruthy( x.left ) && isTruthy( x.right ), |
|||
|
|||
'||': x => isTruthy( x.left ) || isTruthy( x.right ) |
|||
}; |
@ -1,7 +0,0 @@ |
|||
export function emptyBlockStatement ( start, end ) { |
|||
return { |
|||
start, end, |
|||
type: 'BlockStatement', |
|||
body: [] |
|||
}; |
|||
} |
@ -0,0 +1,63 @@ |
|||
import nodes from './nodes/index.js'; |
|||
import Node from './Node.js'; |
|||
import keys from './keys.js'; |
|||
|
|||
const newline = /\n/; |
|||
|
|||
export default function enhance ( ast, module, comments ) { |
|||
enhanceNode( ast, module, module, module.magicString ); |
|||
|
|||
let comment = comments.shift(); |
|||
|
|||
for ( const node of ast.body ) { |
|||
if ( comment && ( comment.start < node.start ) ) { |
|||
node.leadingCommentStart = comment.start; |
|||
} |
|||
|
|||
while ( comment && comment.end < node.end ) comment = comments.shift(); |
|||
|
|||
// if the next comment is on the same line as the end of the node,
|
|||
// treat is as a trailing comment
|
|||
if ( comment && !newline.test( module.code.slice( node.end, comment.start ) ) ) { |
|||
node.trailingCommentEnd = comment.end; // TODO is node.trailingCommentEnd used anywhere?
|
|||
comment = comments.shift(); |
|||
} |
|||
|
|||
node.initialise( module.scope ); |
|||
} |
|||
} |
|||
|
|||
function enhanceNode ( raw, parent, module, code ) { |
|||
if ( !raw ) return; |
|||
|
|||
if ( 'length' in raw ) { |
|||
for ( let i = 0; i < raw.length; i += 1 ) { |
|||
enhanceNode( raw[i], parent, module, code ); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
// with e.g. shorthand properties, key and value are
|
|||
// the same node. We don't want to enhance an object twice
|
|||
if ( raw.__enhanced ) return; |
|||
raw.__enhanced = true; |
|||
|
|||
if ( !keys[ raw.type ] ) { |
|||
keys[ raw.type ] = Object.keys( raw ).filter( key => typeof raw[ key ] === 'object' ); |
|||
} |
|||
|
|||
raw.parent = parent; |
|||
raw.module = module; |
|||
raw.keys = keys[ raw.type ]; |
|||
|
|||
code.addSourcemapLocation( raw.start ); |
|||
code.addSourcemapLocation( raw.end ); |
|||
|
|||
for ( const key of keys[ raw.type ] ) { |
|||
enhanceNode( raw[ key ], raw, module, code ); |
|||
} |
|||
|
|||
const type = nodes[ raw.type ] || Node; |
|||
raw.__proto__ = type.prototype; |
|||
} |
@ -1,6 +0,0 @@ |
|||
export default function isFunctionDeclaration ( node ) { |
|||
if ( !node ) return false; |
|||
|
|||
return node.type === 'FunctionDeclaration' || |
|||
( node.type === 'VariableDeclaration' && node.init && /FunctionExpression/.test( node.init.type ) ); |
|||
} |
@ -0,0 +1,4 @@ |
|||
export default { |
|||
Program: [ 'body' ], |
|||
Literal: [] |
|||
}; |
@ -1,19 +0,0 @@ |
|||
const modifierNodes = { |
|||
AssignmentExpression: 'left', |
|||
UpdateExpression: 'argument', |
|||
UnaryExpression: 'argument' |
|||
}; |
|||
|
|||
export default modifierNodes; |
|||
|
|||
export function isModifierNode ( node ) { |
|||
if ( !( node.type in modifierNodes ) ) { |
|||
return false; |
|||
} |
|||
|
|||
if ( node.type === 'UnaryExpression' ) { |
|||
return node.operator === 'delete'; |
|||
} |
|||
|
|||
return true; |
|||
} |
@ -0,0 +1,8 @@ |
|||
import Node from '../Node.js'; |
|||
import { ARRAY } from '../values.js'; |
|||
|
|||
export default class ArrayExpression extends Node { |
|||
gatherPossibleValues ( values ) { |
|||
values.add( ARRAY ); |
|||
} |
|||
} |
@ -0,0 +1,38 @@ |
|||
import Node from '../Node.js'; |
|||
import Scope from '../scopes/Scope.js'; |
|||
import extractNames from '../utils/extractNames.js'; |
|||
|
|||
export default class ArrowFunctionExpression extends Node { |
|||
bind ( scope ) { |
|||
super.bind( this.scope || scope ); |
|||
} |
|||
|
|||
findScope ( functionScope ) { |
|||
return this.scope || this.parent.findScope( functionScope ); |
|||
} |
|||
|
|||
hasEffects () { |
|||
return false; |
|||
} |
|||
|
|||
initialise ( scope ) { |
|||
if ( this.body.type === 'BlockStatement' ) { |
|||
this.body.createScope( scope ); |
|||
this.scope = this.body.scope; |
|||
} else { |
|||
this.scope = new Scope({ |
|||
parent: scope, |
|||
isBlockScope: false, |
|||
isLexicalBoundary: false |
|||
}); |
|||
|
|||
for ( const param of this.params ) { |
|||
for ( const name of extractNames( param ) ) { |
|||
this.scope.addDeclaration( name, null, null, true ); // TODO ugh
|
|||
} |
|||
} |
|||
} |
|||
|
|||
super.initialise( this.scope ); |
|||
} |
|||
} |
@ -0,0 +1,50 @@ |
|||
import Node from '../Node.js'; |
|||
import disallowIllegalReassignment from './shared/disallowIllegalReassignment.js'; |
|||
import isUsedByBundle from './shared/isUsedByBundle.js'; |
|||
import isProgramLevel from '../utils/isProgramLevel.js'; |
|||
import { NUMBER, STRING } from '../values.js'; |
|||
|
|||
export default class AssignmentExpression extends Node { |
|||
bind ( scope ) { |
|||
const subject = this.left; |
|||
|
|||
this.subject = subject; |
|||
disallowIllegalReassignment( scope, subject ); |
|||
|
|||
if ( subject.type === 'Identifier' ) { |
|||
const declaration = scope.findDeclaration( subject.name ); |
|||
declaration.isReassigned = true; |
|||
|
|||
if ( declaration.possibleValues ) { // TODO this feels hacky
|
|||
if ( this.operator === '=' ) { |
|||
declaration.possibleValues.add( this.right ); |
|||
} else if ( this.operator === '+=' ) { |
|||
declaration.possibleValues.add( STRING ).add( NUMBER ); |
|||
} else { |
|||
declaration.possibleValues.add( NUMBER ); |
|||
} |
|||
} |
|||
} |
|||
|
|||
super.bind( scope ); |
|||
} |
|||
|
|||
hasEffects ( scope ) { |
|||
const hasEffects = this.isUsedByBundle() || this.right.hasEffects( scope ); |
|||
return hasEffects; |
|||
} |
|||
|
|||
initialise ( scope ) { |
|||
this.scope = scope; |
|||
|
|||
if ( isProgramLevel( this ) ) { |
|||
this.module.bundle.dependentExpressions.push( this ); |
|||
} |
|||
|
|||
super.initialise( scope ); |
|||
} |
|||
|
|||
isUsedByBundle () { |
|||
return isUsedByBundle( this.scope, this.subject ); |
|||
} |
|||
} |
@ -0,0 +1,38 @@ |
|||
import Node from '../Node.js'; |
|||
import { UNKNOWN } from '../values.js'; |
|||
|
|||
const operators = { |
|||
'==': ( left, right ) => left == right, |
|||
'!=': ( left, right ) => left != right, |
|||
'===': ( left, right ) => left === right, |
|||
'!==': ( left, right ) => left !== right, |
|||
'<': ( left, right ) => left < right, |
|||
'<=': ( left, right ) => left <= right, |
|||
'>': ( left, right ) => left > right, |
|||
'>=': ( left, right ) => left >= right, |
|||
'<<': ( left, right ) => left << right, |
|||
'>>': ( left, right ) => left >> right, |
|||
'>>>': ( left, right ) => left >>> right, |
|||
'+': ( left, right ) => left + right, |
|||
'-': ( left, right ) => left - right, |
|||
'*': ( left, right ) => left * right, |
|||
'/': ( left, right ) => left / right, |
|||
'%': ( left, right ) => left % right, |
|||
'|': ( left, right ) => left | right, |
|||
'^': ( left, right ) => left ^ right, |
|||
'&': ( left, right ) => left & right, |
|||
in: ( left, right ) => left in right, |
|||
instanceof: ( left, right ) => left instanceof right |
|||
}; |
|||
|
|||
export default class BinaryExpression extends Node { |
|||
getValue () { |
|||
const leftValue = this.left.getValue(); |
|||
if ( leftValue === UNKNOWN ) return UNKNOWN; |
|||
|
|||
const rightValue = this.right.getValue(); |
|||
if ( rightValue === UNKNOWN ) return UNKNOWN; |
|||
|
|||
return operators[ this.operator ]( leftValue, rightValue ); |
|||
} |
|||
} |
@ -0,0 +1,59 @@ |
|||
import Statement from './shared/Statement.js'; |
|||
import Scope from '../scopes/Scope.js'; |
|||
import extractNames from '../utils/extractNames.js'; |
|||
|
|||
export default class BlockStatement extends Statement { |
|||
bind () { |
|||
for ( const node of this.body ) { |
|||
node.bind( this.scope ); |
|||
} |
|||
} |
|||
|
|||
createScope ( parent ) { |
|||
this.parentIsFunction = /Function/.test( this.parent.type ); |
|||
this.isFunctionBlock = this.parentIsFunction || this.parent.type === 'Module'; |
|||
|
|||
this.scope = new Scope({ |
|||
parent, |
|||
isBlockScope: !this.isFunctionBlock, |
|||
isLexicalBoundary: this.isFunctionBlock && this.parent.type !== 'ArrowFunctionExpression', |
|||
owner: this // TODO is this used anywhere?
|
|||
}); |
|||
|
|||
const params = this.parent.params || ( this.parent.type === 'CatchClause' && [ this.parent.param ] ); |
|||
|
|||
if ( params && params.length ) { |
|||
params.forEach( node => { |
|||
extractNames( node ).forEach( name => { |
|||
this.scope.addDeclaration( name, node, false, true ); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
findScope ( functionScope ) { |
|||
return functionScope && !this.isFunctionBlock ? this.parent.findScope( functionScope ) : this.scope; |
|||
} |
|||
|
|||
initialise ( scope ) { |
|||
if ( !this.scope ) this.createScope( scope ); // scope can be created early in some cases, e.g for (let i... )
|
|||
|
|||
let lastNode; |
|||
for ( const node of this.body ) { |
|||
node.initialise( this.scope ); |
|||
|
|||
if ( lastNode ) lastNode.next = node.start; |
|||
lastNode = node; |
|||
} |
|||
} |
|||
|
|||
render ( code, es ) { |
|||
if (this.body.length) { |
|||
for ( const node of this.body ) { |
|||
node.render( code, es ); |
|||
} |
|||
} else { |
|||
Statement.prototype.render.call(this, code, es); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,43 @@ |
|||
import getLocation from '../../utils/getLocation.js'; |
|||
import error from '../../utils/error.js'; |
|||
import Node from '../Node.js'; |
|||
import isProgramLevel from '../utils/isProgramLevel.js'; |
|||
import callHasEffects from './shared/callHasEffects.js'; |
|||
|
|||
export default class CallExpression extends Node { |
|||
bind ( scope ) { |
|||
if ( this.callee.type === 'Identifier' ) { |
|||
const declaration = scope.findDeclaration( this.callee.name ); |
|||
|
|||
if ( declaration.isNamespace ) { |
|||
error({ |
|||
message: `Cannot call a namespace ('${this.callee.name}')`, |
|||
file: this.module.id, |
|||
pos: this.start, |
|||
loc: getLocation( this.module.code, this.start ) |
|||
}); |
|||
} |
|||
|
|||
if ( this.callee.name === 'eval' && declaration.isGlobal ) { |
|||
this.module.bundle.onwarn( `Use of \`eval\` (in ${this.module.id}) is strongly discouraged, as it poses security risks and may cause issues with minification. See https://github.com/rollup/rollup/wiki/Troubleshooting#avoiding-eval for more details` ); |
|||
} |
|||
} |
|||
|
|||
super.bind( scope ); |
|||
} |
|||
|
|||
hasEffects ( scope ) { |
|||
return callHasEffects( scope, this.callee ); |
|||
} |
|||
|
|||
initialise ( scope ) { |
|||
if ( isProgramLevel( this ) ) { |
|||
this.module.bundle.dependentExpressions.push( this ); |
|||
} |
|||
super.initialise( scope ); |
|||
} |
|||
|
|||
isUsedByBundle () { |
|||
return this.hasEffects( this.findScope() ); |
|||
} |
|||
} |
@ -0,0 +1,45 @@ |
|||
import Node from '../Node.js'; |
|||
|
|||
// TODO is this basically identical to FunctionDeclaration?
|
|||
export default class ClassDeclaration extends Node { |
|||
activate () { |
|||
if ( this.activated ) return; |
|||
this.activated = true; |
|||
|
|||
if ( this.superClass ) this.superClass.run( this.scope ); |
|||
this.body.run(); |
|||
} |
|||
|
|||
addReference () { |
|||
/* noop? */ |
|||
} |
|||
|
|||
gatherPossibleValues ( values ) { |
|||
values.add( this ); |
|||
} |
|||
|
|||
getName () { |
|||
return this.name; |
|||
} |
|||
|
|||
hasEffects () { |
|||
return false; |
|||
} |
|||
|
|||
initialise ( scope ) { |
|||
this.scope = scope; |
|||
|
|||
this.name = this.id.name; |
|||
|
|||
scope.addDeclaration( this.name, this, false, false ); |
|||
super.initialise( scope ); |
|||
} |
|||
|
|||
render ( code, es ) { |
|||
if ( this.activated ) { |
|||
super.render( code, es ); |
|||
} else { |
|||
code.remove( this.leadingCommentStart || this.start, this.next || this.end ); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,26 @@ |
|||
import Node from '../Node.js'; |
|||
import Scope from '../scopes/Scope.js'; |
|||
|
|||
export default class ClassExpression extends Node { |
|||
bind () { |
|||
super.bind( this.scope ); |
|||
} |
|||
|
|||
findScope () { |
|||
return this.scope; |
|||
} |
|||
|
|||
initialise () { |
|||
this.scope = new Scope({ |
|||
isBlockScope: true, |
|||
parent: this.parent.findScope( false ) |
|||
}); |
|||
|
|||
if ( this.id ) { |
|||
// function expression IDs belong to the child scope...
|
|||
this.scope.addDeclaration( this.id.name, this, false, true ); |
|||
} |
|||
|
|||
super.initialise( this.scope ); |
|||
} |
|||
} |
@ -0,0 +1,65 @@ |
|||
import Node from '../Node.js'; |
|||
import { UNKNOWN } from '../values.js'; |
|||
|
|||
export default class ConditionalExpression extends Node { |
|||
initialise ( scope ) { |
|||
if ( this.module.bundle.treeshake ) { |
|||
this.testValue = this.test.getValue(); |
|||
|
|||
if ( this.testValue === UNKNOWN ) { |
|||
super.initialise( scope ); |
|||
} |
|||
|
|||
else if ( this.testValue ) { |
|||
this.consequent.initialise( scope ); |
|||
this.alternate = null; |
|||
} else if ( this.alternate ) { |
|||
this.alternate.initialise( scope ); |
|||
this.consequent = null; |
|||
} |
|||
} |
|||
|
|||
else { |
|||
super.initialise( scope ); |
|||
} |
|||
} |
|||
|
|||
gatherPossibleValues ( values ) { |
|||
const testValue = this.test.getValue(); |
|||
|
|||
if ( testValue === UNKNOWN ) { |
|||
values.add( this.consequent ).add( this.alternate ); |
|||
} else { |
|||
values.add( testValue ? this.consequent : this.alternate ); |
|||
} |
|||
} |
|||
|
|||
getValue () { |
|||
const testValue = this.test.getValue(); |
|||
if ( testValue === UNKNOWN ) return UNKNOWN; |
|||
|
|||
return testValue ? this.consequent.getValue() : this.alternate.getValue(); |
|||
} |
|||
|
|||
render ( code, es ) { |
|||
if ( !this.module.bundle.treeshake ) { |
|||
super.render( code, es ); |
|||
} |
|||
|
|||
else { |
|||
if ( this.testValue === UNKNOWN ) { |
|||
super.render( code, es ); |
|||
} |
|||
|
|||
else if ( this.testValue ) { |
|||
code.remove( this.start, this.consequent.start ); |
|||
code.remove( this.consequent.end, this.end ); |
|||
this.consequent.render( code, es ); |
|||
} else { |
|||
code.remove( this.start, this.alternate.start ); |
|||
code.remove( this.alternate.end, this.end ); |
|||
this.alternate.render( code, es ); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
import Statement from './shared/Statement.js'; |
|||
|
|||
export default class EmptyStatement extends Statement { |
|||
render ( code ) { |
|||
if ( this.parent.type === 'BlockStatement' || this.parent.type === 'Program' ) { |
|||
code.remove( this.start, this.end ); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,11 @@ |
|||
import Node from '../Node.js'; |
|||
|
|||
export default class ExportAllDeclaration extends Node { |
|||
initialise () { |
|||
this.isExportDeclaration = true; |
|||
} |
|||
|
|||
render ( code ) { |
|||
code.remove( this.leadingCommentStart || this.start, this.next || this.end ); |
|||
} |
|||
} |
@ -0,0 +1,105 @@ |
|||
import Node from '../Node.js'; |
|||
|
|||
const functionOrClassDeclaration = /^(?:Function|Class)Declaration/; |
|||
|
|||
export default class ExportDefaultDeclaration extends Node { |
|||
initialise ( scope ) { |
|||
this.isExportDeclaration = true; |
|||
this.isDefault = true; |
|||
|
|||
this.name = ( this.declaration.id && this.declaration.id.name ) || this.declaration.name || this.module.basename(); |
|||
scope.declarations.default = this; |
|||
|
|||
this.declaration.initialise( scope ); |
|||
} |
|||
|
|||
activate () { |
|||
if ( this.activated ) return; |
|||
this.activated = true; |
|||
|
|||
this.run(); |
|||
} |
|||
|
|||
addReference ( reference ) { |
|||
this.name = reference.name; |
|||
if ( this.original ) this.original.addReference( reference ); |
|||
} |
|||
|
|||
bind ( scope ) { |
|||
const name = ( this.declaration.id && this.declaration.id.name ) || this.declaration.name; |
|||
if ( name ) this.original = scope.findDeclaration( name ); |
|||
|
|||
this.declaration.bind( scope ); |
|||
} |
|||
|
|||
gatherPossibleValues ( values ) { |
|||
this.declaration.gatherPossibleValues( values ); |
|||
} |
|||
|
|||
getName ( es ) { |
|||
if ( this.original && !this.original.isReassigned ) { |
|||
return this.original.getName( es ); |
|||
} |
|||
|
|||
return this.name; |
|||
} |
|||
|
|||
// TODO this is total chaos, tidy it up
|
|||
render ( code, es ) { |
|||
const treeshake = this.module.bundle.treeshake; |
|||
const name = this.getName( es ); |
|||
|
|||
// paren workaround: find first non-whitespace character position after `export default`
|
|||
let declaration_start; |
|||
if ( this.declaration ) { |
|||
const statementStr = code.original.slice( this.start, this.end ); |
|||
declaration_start = this.start + statementStr.match(/^\s*export\s+default\s+/)[0].length; |
|||
} |
|||
|
|||
if ( this.shouldInclude || this.declaration.activated ) { |
|||
if ( this.activated ) { |
|||
if ( functionOrClassDeclaration.test( this.declaration.type ) ) { |
|||
if ( this.declaration.id ) { |
|||
code.remove( this.start, declaration_start ); |
|||
} else { |
|||
throw new Error( 'TODO anonymous class/function declaration' ); |
|||
} |
|||
} |
|||
|
|||
else { |
|||
if ( this.original && this.original.getName( es ) === name ) { |
|||
// prevent `var foo = foo`
|
|||
code.remove( this.leadingCommentStart || this.start, this.next || this.end ); |
|||
return; // don't render children. TODO this seems like a bit of a hack
|
|||
} else { |
|||
code.overwrite( this.start, declaration_start, `${this.module.bundle.varOrConst} ${name} = ` ); |
|||
} |
|||
|
|||
this.insertSemicolon( code ); |
|||
} |
|||
} else { |
|||
// remove `var foo` from `var foo = bar()`, if `foo` is unused
|
|||
code.remove( this.start, declaration_start ); |
|||
} |
|||
|
|||
super.render( code, es ); |
|||
} else { |
|||
if ( treeshake ) { |
|||
if ( functionOrClassDeclaration.test( this.declaration.type ) ) { |
|||
code.remove( this.leadingCommentStart || this.start, this.next || this.end ); |
|||
} else { |
|||
const hasEffects = this.declaration.hasEffects( this.module.scope ); |
|||
code.remove( this.start, hasEffects ? declaration_start : this.next || this.end ); |
|||
} |
|||
} else { |
|||
code.overwrite( this.start, declaration_start, `${this.module.bundle.varOrConst} ${name} = ` ); |
|||
} |
|||
// code.remove( this.start, this.next || this.end );
|
|||
} |
|||
} |
|||
|
|||
run ( scope ) { |
|||
this.shouldInclude = true; |
|||
super.run( scope ); |
|||
} |
|||
} |
@ -0,0 +1,80 @@ |
|||
import { find } from '../../utils/array.js'; |
|||
import Node from '../Node.js'; |
|||
|
|||
class UnboundDefaultExport { |
|||
constructor ( original ) { |
|||
this.original = original; |
|||
this.name = original.name; |
|||
} |
|||
|
|||
activate () { |
|||
if ( this.activated ) return; |
|||
this.activated = true; |
|||
|
|||
this.original.activate(); |
|||
} |
|||
|
|||
addReference ( reference ) { |
|||
this.name = reference.name; |
|||
this.original.addReference( reference ); |
|||
} |
|||
|
|||
bind ( scope ) { |
|||
this.original.bind( scope ); |
|||
} |
|||
|
|||
gatherPossibleValues ( values ) { |
|||
this.original.gatherPossibleValues( values ); |
|||
} |
|||
|
|||
getName ( es ) { |
|||
if ( this.original && !this.original.isReassigned ) { |
|||
return this.original.getName( es ); |
|||
} |
|||
|
|||
return this.name; |
|||
} |
|||
} |
|||
|
|||
export default class ExportNamedDeclaration extends Node { |
|||
initialise ( scope ) { |
|||
this.scope = scope; |
|||
this.isExportDeclaration = true; |
|||
|
|||
// special case – `export { foo as default }` should not create a live binding
|
|||
const defaultExport = find( this.specifiers, specifier => specifier.exported.name === 'default' ); |
|||
if ( defaultExport ) { |
|||
const declaration = this.scope.findDeclaration( defaultExport.local.name ); |
|||
this.defaultExport = new UnboundDefaultExport( declaration ); |
|||
scope.declarations.default = this.defaultExport; |
|||
} |
|||
|
|||
if ( this.declaration ) this.declaration.initialise( scope ); |
|||
} |
|||
|
|||
bind ( scope ) { |
|||
if ( this.declaration ) this.declaration.bind( scope ); |
|||
} |
|||
|
|||
render ( code, es ) { |
|||
if ( this.declaration ) { |
|||
code.remove( this.start, this.declaration.start ); |
|||
this.declaration.render( code, es ); |
|||
} else { |
|||
const start = this.leadingCommentStart || this.start; |
|||
const end = this.next || this.end; |
|||
|
|||
if ( this.defaultExport ) { |
|||
const name = this.defaultExport.getName( es ); |
|||
const originalName = this.defaultExport.original.getName( es ); |
|||
|
|||
if ( name !== originalName ) { |
|||
code.overwrite( start, end, `var ${name} = ${originalName};` ); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
code.remove( start, end ); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
import Statement from './shared/Statement.js'; |
|||
|
|||
export default class ExpressionStatement extends Statement { |
|||
render ( code, es ) { |
|||
super.render( code, es ); |
|||
if ( this.shouldInclude ) this.insertSemicolon( code ); |
|||
} |
|||
} |
@ -0,0 +1,22 @@ |
|||
import Statement from './shared/Statement.js'; |
|||
import assignTo from './shared/assignTo.js'; |
|||
import Scope from '../scopes/Scope.js'; |
|||
import { STRING } from '../values.js'; |
|||
|
|||
export default class ForInStatement extends Statement { |
|||
initialise ( scope ) { |
|||
if ( this.body.type === 'BlockStatement' ) { |
|||
this.body.createScope( scope ); |
|||
this.scope = this.body.scope; |
|||
} else { |
|||
this.scope = new Scope({ |
|||
parent: scope, |
|||
isBlockScope: true, |
|||
isLexicalBoundary: false |
|||
}); |
|||
} |
|||
|
|||
super.initialise( this.scope ); |
|||
assignTo( this.left, this.scope, STRING ); |
|||
} |
|||
} |
@ -0,0 +1,22 @@ |
|||
import Statement from './shared/Statement.js'; |
|||
import assignTo from './shared/assignTo.js'; |
|||
import Scope from '../scopes/Scope.js'; |
|||
import { UNKNOWN } from '../values.js'; |
|||
|
|||
export default class ForOfStatement extends Statement { |
|||
initialise ( scope ) { |
|||
if ( this.body.type === 'BlockStatement' ) { |
|||
this.body.createScope( scope ); |
|||
this.scope = this.body.scope; |
|||
} else { |
|||
this.scope = new Scope({ |
|||
parent: scope, |
|||
isBlockScope: true, |
|||
isLexicalBoundary: false |
|||
}); |
|||
} |
|||
|
|||
super.initialise( this.scope ); |
|||
assignTo( this.left, this.scope, UNKNOWN ); |
|||
} |
|||
} |
@ -0,0 +1,23 @@ |
|||
import Statement from './shared/Statement.js'; |
|||
import Scope from '../scopes/Scope.js'; |
|||
|
|||
export default class ForStatement extends Statement { |
|||
initialise ( scope ) { |
|||
if ( this.body.type === 'BlockStatement' ) { |
|||
this.body.createScope( scope ); |
|||
this.scope = this.body.scope; |
|||
} else { |
|||
this.scope = new Scope({ |
|||
parent: scope, |
|||
isBlockScope: true, |
|||
isLexicalBoundary: false |
|||
}); |
|||
} |
|||
|
|||
// can't use super, because we need to control the order
|
|||
if ( this.init ) this.init.initialise( this.scope ); |
|||
if ( this.test ) this.test.initialise( this.scope ); |
|||
if ( this.update ) this.update.initialise( this.scope ); |
|||
this.body.initialise( this.scope ); |
|||
} |
|||
} |
@ -0,0 +1,53 @@ |
|||
import Node from '../Node.js'; |
|||
|
|||
export default class FunctionDeclaration extends Node { |
|||
activate () { |
|||
if ( this.activated ) return; |
|||
this.activated = true; |
|||
|
|||
const scope = this.body.scope; |
|||
this.params.forEach( param => param.run( scope ) ); // in case of assignment patterns
|
|||
this.body.run(); |
|||
} |
|||
|
|||
addReference () { |
|||
/* noop? */ |
|||
} |
|||
|
|||
bind ( scope ) { |
|||
this.id.bind( scope ); |
|||
this.params.forEach( param => param.bind( this.body.scope ) ); |
|||
this.body.bind( scope ); |
|||
} |
|||
|
|||
gatherPossibleValues ( values ) { |
|||
values.add( this ); |
|||
} |
|||
|
|||
getName () { |
|||
return this.name; |
|||
} |
|||
|
|||
hasEffects () { |
|||
return false; |
|||
} |
|||
|
|||
initialise ( scope ) { |
|||
this.name = this.id.name; // may be overridden by bundle.deconflict
|
|||
scope.addDeclaration( this.name, this, false, false ); |
|||
|
|||
this.body.createScope( scope ); |
|||
|
|||
this.id.initialise( scope ); |
|||
this.params.forEach( param => param.initialise( this.body.scope ) ); |
|||
this.body.initialise(); |
|||
} |
|||
|
|||
render ( code, es ) { |
|||
if ( !this.module.bundle.treeshake || this.activated ) { |
|||
super.render( code, es ); |
|||
} else { |
|||
code.remove( this.leadingCommentStart || this.start, this.next || this.end ); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,21 @@ |
|||
import Node from '../Node.js'; |
|||
|
|||
export default class FunctionExpression extends Node { |
|||
bind () { |
|||
if ( this.id ) this.id.bind( this.body.scope ); |
|||
this.params.forEach( param => param.bind( this.body.scope ) ); |
|||
this.body.bind(); |
|||
} |
|||
|
|||
hasEffects () { |
|||
return false; |
|||
} |
|||
|
|||
initialise ( scope ) { |
|||
this.body.createScope( scope ); |
|||
|
|||
if ( this.id ) this.id.initialise( this.body.scope ); |
|||
this.params.forEach( param => param.initialise( this.body.scope ) ); |
|||
this.body.initialise(); |
|||
} |
|||
} |
@ -0,0 +1,35 @@ |
|||
import Node from '../Node.js'; |
|||
import isReference from '../utils/isReference.js'; |
|||
|
|||
export default class Identifier extends Node { |
|||
bind ( scope ) { |
|||
if ( isReference( this, this.parent ) ) { |
|||
this.declaration = scope.findDeclaration( this.name ); |
|||
this.declaration.addReference( this ); // TODO necessary?
|
|||
} |
|||
} |
|||
|
|||
gatherPossibleValues ( values ) { |
|||
if ( isReference( this, this.parent ) ) { |
|||
values.add( this ); |
|||
} |
|||
} |
|||
|
|||
render ( code, es ) { |
|||
if ( this.declaration ) { |
|||
const name = this.declaration.getName( es ); |
|||
if ( name !== this.name ) { |
|||
code.overwrite( this.start, this.end, name, true ); |
|||
|
|||
// special case
|
|||
if ( this.parent.type === 'Property' && this.parent.shorthand ) { |
|||
code.insertLeft( this.start, `${this.name}: ` ); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
run () { |
|||
if ( this.declaration ) this.declaration.activate(); |
|||
} |
|||
} |
@ -0,0 +1,76 @@ |
|||
import Statement from './shared/Statement.js'; |
|||
import { UNKNOWN } from '../values.js'; |
|||
|
|||
// Statement types which may contain if-statements as direct children.
|
|||
const statementsWithIfStatements = new Set([ |
|||
'DoWhileStatement', |
|||
'ForInStatement', |
|||
'ForOfStatement', |
|||
'ForStatement', |
|||
'IfStatement', |
|||
'WhileStatement' |
|||
]); |
|||
|
|||
// TODO DRY this out
|
|||
export default class IfStatement extends Statement { |
|||
initialise ( scope ) { |
|||
this.testValue = this.test.getValue(); |
|||
|
|||
if ( this.module.bundle.treeshake ) { |
|||
if ( this.testValue === UNKNOWN ) { |
|||
super.initialise( scope ); |
|||
} |
|||
|
|||
else if ( this.testValue ) { |
|||
this.consequent.initialise( scope ); |
|||
this.alternate = null; |
|||
} |
|||
|
|||
else { |
|||
if ( this.alternate ) this.alternate.initialise( scope ); |
|||
this.consequent = null; |
|||
} |
|||
} |
|||
|
|||
else { |
|||
super.initialise( scope ); |
|||
} |
|||
} |
|||
|
|||
render ( code, es ) { |
|||
if ( this.module.bundle.treeshake ) { |
|||
if ( this.testValue === UNKNOWN ) { |
|||
super.render( code, es ); |
|||
} |
|||
|
|||
else { |
|||
code.overwrite( this.test.start, this.test.end, JSON.stringify( this.testValue ) ); |
|||
|
|||
// TODO if no block-scoped declarations, remove enclosing
|
|||
// curlies and dedent block (if there is a block)
|
|||
|
|||
if ( this.testValue ) { |
|||
code.remove( this.start, this.consequent.start ); |
|||
code.remove( this.consequent.end, this.end ); |
|||
this.consequent.render( code, es ); |
|||
} |
|||
|
|||
else { |
|||
code.remove( this.start, this.alternate ? this.alternate.start : this.next || this.end ); |
|||
|
|||
if ( this.alternate ) { |
|||
this.alternate.render( code, es ); |
|||
} |
|||
|
|||
else if ( statementsWithIfStatements.has( this.parent.type ) ) { |
|||
code.insertRight( this.start, '{}' ); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
else { |
|||
super.render( code, es ); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,16 @@ |
|||
import Node from '../Node.js'; |
|||
|
|||
export default class ImportDeclaration extends Node { |
|||
bind () { |
|||
// noop
|
|||
// TODO do the inter-module binding setup here?
|
|||
} |
|||
|
|||
initialise () { |
|||
this.isImportDeclaration = true; |
|||
} |
|||
|
|||
render ( code ) { |
|||
code.remove( this.start, this.next || this.end ); |
|||
} |
|||
} |
@ -0,0 +1,17 @@ |
|||
import Node from '../Node.js'; |
|||
|
|||
export default class Literal extends Node { |
|||
getValue () { |
|||
return this.value; |
|||
} |
|||
|
|||
gatherPossibleValues ( values ) { |
|||
values.add( this ); |
|||
} |
|||
|
|||
render ( code ) { |
|||
if ( typeof this.value === 'string' ) { |
|||
code.indentExclusionRanges.push([ this.start + 1, this.end - 1 ]); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,84 @@ |
|||
import getLocation from '../../utils/getLocation.js'; |
|||
import relativeId from '../../utils/relativeId.js'; |
|||
import isReference from '../utils/isReference.js'; |
|||
import Node from '../Node.js'; |
|||
import { UNKNOWN } from '../values.js'; |
|||
|
|||
class Keypath { |
|||
constructor ( node ) { |
|||
this.parts = []; |
|||
|
|||
while ( node.type === 'MemberExpression' ) { |
|||
this.parts.unshift( node.property ); |
|||
node = node.object; |
|||
} |
|||
|
|||
this.root = node; |
|||
} |
|||
} |
|||
|
|||
export default class MemberExpression extends Node { |
|||
bind ( scope ) { |
|||
// if this resolves to a namespaced declaration, prepare
|
|||
// to replace it
|
|||
// TODO this code is a bit inefficient
|
|||
if ( isReference( this ) ) { // TODO optimise namespace access like `foo['bar']` as well
|
|||
const keypath = new Keypath( this ); |
|||
|
|||
let declaration = scope.findDeclaration( keypath.root.name ); |
|||
|
|||
while ( declaration.isNamespace && keypath.parts.length ) { |
|||
const exporterId = declaration.module.id; |
|||
|
|||
const part = keypath.parts[0]; |
|||
declaration = declaration.module.traceExport( part.name ); |
|||
|
|||
if ( !declaration ) { |
|||
const { line, column } = getLocation( this.module.code, this.start ); |
|||
this.module.bundle.onwarn( `${relativeId( this.module.id )} (${line}:${column}) '${part.name}' is not exported by '${relativeId( exporterId )}'. See https://github.com/rollup/rollup/wiki/Troubleshooting#name-is-not-exported-by-module` ); |
|||
this.replacement = 'undefined'; |
|||
return; |
|||
} |
|||
|
|||
keypath.parts.shift(); |
|||
} |
|||
|
|||
if ( keypath.parts.length ) { |
|||
super.bind( scope ); |
|||
return; // not a namespaced declaration
|
|||
} |
|||
|
|||
this.declaration = declaration; |
|||
|
|||
if ( declaration.isExternal ) { |
|||
declaration.module.suggestName( keypath.root.name ); |
|||
} |
|||
} |
|||
|
|||
else { |
|||
super.bind( scope ); |
|||
} |
|||
} |
|||
|
|||
gatherPossibleValues ( values ) { |
|||
values.add( UNKNOWN ); // TODO
|
|||
} |
|||
|
|||
render ( code, es ) { |
|||
if ( this.declaration ) { |
|||
const name = this.declaration.getName( es ); |
|||
if ( name !== this.name ) code.overwrite( this.start, this.end, name, true ); |
|||
} |
|||
|
|||
else if ( this.replacement ) { |
|||
code.overwrite( this.start, this.end, this.replacement, true ); |
|||
} |
|||
|
|||
super.render( code, es ); |
|||
} |
|||
|
|||
run ( scope ) { |
|||
if ( this.declaration ) this.declaration.activate(); |
|||
super.run( scope ); |
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
import Node from '../Node.js'; |
|||
import callHasEffects from './shared/callHasEffects.js'; |
|||
|
|||
export default class NewExpression extends Node { |
|||
hasEffects ( scope ) { |
|||
return callHasEffects( scope, this.callee ); |
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
import Node from '../Node.js'; |
|||
import { OBJECT } from '../values.js'; |
|||
|
|||
export default class ObjectExpression extends Node { |
|||
gatherPossibleValues ( values ) { |
|||
values.add( OBJECT ); |
|||
} |
|||
} |
@ -0,0 +1,7 @@ |
|||
import Node from '../Node.js'; |
|||
|
|||
export default class ReturnStatement extends Node { |
|||
// hasEffects () {
|
|||
// return true;
|
|||
// }
|
|||
} |
@ -0,0 +1,8 @@ |
|||
import Node from '../Node.js'; |
|||
|
|||
export default class TemplateLiteral extends Node { |
|||
render ( code, es ) { |
|||
code.indentExclusionRanges.push([ this.start, this.end ]); |
|||
super.render( code, es ); |
|||
} |
|||
} |
@ -0,0 +1,26 @@ |
|||
import Node from '../Node.js'; |
|||
import getLocation from '../../utils/getLocation.js'; |
|||
import relativeId from '../../utils/relativeId.js'; |
|||
|
|||
const warning = `The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten. See https://github.com/rollup/rollup/wiki/Troubleshooting#this-is-undefined for more information`; |
|||
|
|||
export default class ThisExpression extends Node { |
|||
initialise ( scope ) { |
|||
const lexicalBoundary = scope.findLexicalBoundary(); |
|||
|
|||
if ( lexicalBoundary.isModuleScope ) { |
|||
this.alias = this.module.context; |
|||
if ( this.alias === 'undefined' ) { |
|||
const { line, column } = getLocation( this.module.code, this.start ); |
|||
const detail = `${relativeId( this.module.id )} (${line}:${column + 1})`; // use one-based column number convention
|
|||
this.module.bundle.onwarn( `${detail} ${warning}` ); |
|||
} |
|||
} |
|||
} |
|||
|
|||
render ( code ) { |
|||
if ( this.alias ) { |
|||
code.overwrite( this.start, this.end, this.alias, true ); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,7 @@ |
|||
import Node from '../Node.js'; |
|||
|
|||
export default class ThrowStatement extends Node { |
|||
hasEffects ( scope ) { |
|||
return scope.findLexicalBoundary().isModuleScope; // TODO should this just be `true`? probably...
|
|||
} |
|||
} |
@ -0,0 +1,34 @@ |
|||
import Node from '../Node.js'; |
|||
import { UNKNOWN } from '../values.js'; |
|||
|
|||
const operators = { |
|||
"-": value => -value, |
|||
"+": value => +value, |
|||
"!": value => !value, |
|||
"~": value => ~value, |
|||
typeof: value => typeof value, |
|||
void: () => undefined, |
|||
delete: () => UNKNOWN |
|||
}; |
|||
|
|||
export default class UnaryExpression extends Node { |
|||
bind ( scope ) { |
|||
if ( this.value === UNKNOWN ) super.bind( scope ); |
|||
} |
|||
|
|||
getValue () { |
|||
const argumentValue = this.argument.getValue(); |
|||
if ( argumentValue === UNKNOWN ) return UNKNOWN; |
|||
|
|||
return operators[ this.operator ]( argumentValue ); |
|||
} |
|||
|
|||
hasEffects ( scope ) { |
|||
return this.operator === 'delete' || this.argument.hasEffects( scope ); |
|||
} |
|||
|
|||
initialise ( scope ) { |
|||
this.value = this.getValue(); |
|||
if ( this.value === UNKNOWN ) super.initialise( scope ); |
|||
} |
|||
} |
@ -0,0 +1,39 @@ |
|||
import Node from '../Node.js'; |
|||
import disallowIllegalReassignment from './shared/disallowIllegalReassignment.js'; |
|||
import isUsedByBundle from './shared/isUsedByBundle.js'; |
|||
import { NUMBER } from '../values.js'; |
|||
|
|||
export default class UpdateExpression extends Node { |
|||
bind ( scope ) { |
|||
const subject = this.argument; |
|||
|
|||
this.subject = subject; |
|||
disallowIllegalReassignment( scope, this.argument ); |
|||
|
|||
if ( subject.type === 'Identifier' ) { |
|||
const declaration = scope.findDeclaration( subject.name ); |
|||
declaration.isReassigned = true; |
|||
|
|||
if ( declaration.possibleValues ) { |
|||
declaration.possibleValues.add( NUMBER ); |
|||
} |
|||
} |
|||
|
|||
super.bind( scope ); |
|||
} |
|||
|
|||
hasEffects ( scope ) { |
|||
return isUsedByBundle( scope, this.subject ); |
|||
} |
|||
|
|||
initialise ( scope ) { |
|||
this.scope = scope; |
|||
|
|||
this.module.bundle.dependentExpressions.push( this ); |
|||
super.initialise( scope ); |
|||
} |
|||
|
|||
isUsedByBundle () { |
|||
return isUsedByBundle( this.scope, this.subject ); |
|||
} |
|||
} |
@ -0,0 +1,107 @@ |
|||
import Node from '../Node.js'; |
|||
import extractNames from '../utils/extractNames.js'; |
|||
|
|||
function getSeparator ( code, start ) { |
|||
let c = start; |
|||
|
|||
while ( c > 0 && code[ c - 1 ] !== '\n' ) { |
|||
c -= 1; |
|||
if ( code[c] === ';' || code[c] === '{' ) return '; '; |
|||
} |
|||
|
|||
const lineStart = code.slice( c, start ).match( /^\s*/ )[0]; |
|||
|
|||
return `;\n${lineStart}`; |
|||
} |
|||
|
|||
const forStatement = /^For(?:Of|In)?Statement/; |
|||
|
|||
export default class VariableDeclaration extends Node { |
|||
initialise ( scope ) { |
|||
this.scope = scope; |
|||
super.initialise( scope ); |
|||
} |
|||
|
|||
render ( code, es ) { |
|||
const treeshake = this.module.bundle.treeshake; |
|||
|
|||
let shouldSeparate = false; |
|||
let separator; |
|||
|
|||
if ( this.scope.isModuleScope && !forStatement.test( this.parent.type ) ) { |
|||
shouldSeparate = true; |
|||
separator = getSeparator( this.module.code, this.start ); |
|||
} |
|||
|
|||
let c = this.start; |
|||
let empty = true; |
|||
|
|||
for ( let i = 0; i < this.declarations.length; i += 1 ) { |
|||
const declarator = this.declarations[i]; |
|||
|
|||
const prefix = empty ? '' : separator; // TODO indentation
|
|||
|
|||
if ( declarator.id.type === 'Identifier' ) { |
|||
const proxy = declarator.proxies.get( declarator.id.name ); |
|||
const isExportedAndReassigned = !es && proxy.exportName && proxy.isReassigned; |
|||
|
|||
if ( isExportedAndReassigned ) { |
|||
if ( declarator.init ) { |
|||
if ( shouldSeparate ) code.overwrite( c, declarator.start, prefix ); |
|||
c = declarator.end; |
|||
empty = false; |
|||
} |
|||
} else if ( !treeshake || proxy.activated ) { |
|||
if ( shouldSeparate ) code.overwrite( c, declarator.start, `${prefix}${this.kind} ` ); // TODO indentation
|
|||
c = declarator.end; |
|||
empty = false; |
|||
} |
|||
} |
|||
|
|||
else { |
|||
const exportAssignments = []; |
|||
let activated = false; |
|||
|
|||
extractNames( declarator.id ).forEach( name => { |
|||
const proxy = declarator.proxies.get( name ); |
|||
const isExportedAndReassigned = !es && proxy.exportName && proxy.isReassigned; |
|||
|
|||
if ( isExportedAndReassigned ) { |
|||
// code.overwrite( c, declarator.start, prefix );
|
|||
// c = declarator.end;
|
|||
// empty = false;
|
|||
exportAssignments.push( 'TODO' ); |
|||
} else if ( declarator.activated ) { |
|||
activated = true; |
|||
} |
|||
}); |
|||
|
|||
if ( !treeshake || activated ) { |
|||
if ( shouldSeparate ) code.overwrite( c, declarator.start, `${prefix}${this.kind} ` ); // TODO indentation
|
|||
c = declarator.end; |
|||
empty = false; |
|||
} |
|||
|
|||
if ( exportAssignments.length ) { |
|||
throw new Error( 'TODO' ); |
|||
} |
|||
} |
|||
|
|||
declarator.render( code, es ); |
|||
} |
|||
|
|||
if ( treeshake && empty ) { |
|||
code.remove( this.leadingCommentStart || this.start, this.next || this.end ); |
|||
} else { |
|||
// always include a semi-colon (https://github.com/rollup/rollup/pull/1013),
|
|||
// unless it's a var declaration in a loop head
|
|||
const needsSemicolon = !forStatement.test( this.parent.type ); |
|||
|
|||
if ( this.end > c ) { |
|||
code.overwrite( c, this.end, needsSemicolon ? ';' : '' ); |
|||
} else if ( needsSemicolon ) { |
|||
this.insertSemicolon( code ); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,91 @@ |
|||
import Node from '../Node.js'; |
|||
import extractNames from '../utils/extractNames.js'; |
|||
import { UNKNOWN } from '../values.js'; |
|||
|
|||
class DeclaratorProxy { |
|||
constructor ( name, declarator, isTopLevel, init ) { |
|||
this.name = name; |
|||
this.declarator = declarator; |
|||
|
|||
this.activated = false; |
|||
this.isReassigned = false; |
|||
this.exportName = null; |
|||
|
|||
this.duplicates = []; |
|||
this.possibleValues = new Set( init ? [ init ] : null ); |
|||
} |
|||
|
|||
activate () { |
|||
this.activated = true; |
|||
this.declarator.activate(); |
|||
this.duplicates.forEach( dupe => dupe.activate() ); |
|||
} |
|||
|
|||
addReference () { |
|||
/* noop? */ |
|||
} |
|||
|
|||
gatherPossibleValues ( values ) { |
|||
this.possibleValues.forEach( value => values.add( value ) ); |
|||
} |
|||
|
|||
getName ( es ) { |
|||
// TODO desctructuring...
|
|||
if ( es ) return this.name; |
|||
if ( !this.isReassigned || !this.exportName ) return this.name; |
|||
|
|||
return `exports.${this.exportName}`; |
|||
} |
|||
|
|||
toString () { |
|||
return this.name; |
|||
} |
|||
} |
|||
|
|||
export default class VariableDeclarator extends Node { |
|||
activate () { |
|||
if ( this.activated ) return; |
|||
this.activated = true; |
|||
|
|||
this.run( this.findScope() ); |
|||
} |
|||
|
|||
hasEffects ( scope ) { |
|||
return this.init && this.init.hasEffects( scope ); |
|||
} |
|||
|
|||
initialise ( scope ) { |
|||
this.proxies = new Map(); |
|||
|
|||
const lexicalBoundary = scope.findLexicalBoundary(); |
|||
|
|||
const init = this.init ? |
|||
( this.id.type === 'Identifier' ? this.init : UNKNOWN ) : // TODO maybe UNKNOWN is unnecessary
|
|||
null; |
|||
|
|||
extractNames( this.id ).forEach( name => { |
|||
const proxy = new DeclaratorProxy( name, this, lexicalBoundary.isModuleScope, init ); |
|||
|
|||
this.proxies.set( name, proxy ); |
|||
scope.addDeclaration( name, proxy, this.parent.kind === 'var' ); |
|||
}); |
|||
|
|||
super.initialise( scope ); |
|||
} |
|||
|
|||
render ( code, es ) { |
|||
extractNames( this.id ).forEach( name => { |
|||
const declaration = this.proxies.get( name ); |
|||
|
|||
if ( !es && declaration.exportName && declaration.isReassigned ) { |
|||
if ( this.init ) { |
|||
code.overwrite( this.start, this.id.end, declaration.getName( es ) ); |
|||
} else if ( this.module.bundle.treeshake ) { |
|||
code.remove( this.start, this.end ); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
super.render( code, es ); |
|||
} |
|||
} |
@ -0,0 +1,76 @@ |
|||
import ArrayExpression from './ArrayExpression.js'; |
|||
import ArrowFunctionExpression from './ArrowFunctionExpression.js'; |
|||
import AssignmentExpression from './AssignmentExpression.js'; |
|||
import BinaryExpression from './BinaryExpression.js'; |
|||
import BlockStatement from './BlockStatement.js'; |
|||
import CallExpression from './CallExpression.js'; |
|||
import ClassDeclaration from './ClassDeclaration.js'; |
|||
import ClassExpression from './ClassExpression.js'; |
|||
import ConditionalExpression from './ConditionalExpression.js'; |
|||
import EmptyStatement from './EmptyStatement.js'; |
|||
import ExportAllDeclaration from './ExportAllDeclaration.js'; |
|||
import ExportDefaultDeclaration from './ExportDefaultDeclaration.js'; |
|||
import ExportNamedDeclaration from './ExportNamedDeclaration.js'; |
|||
import ExpressionStatement from './ExpressionStatement.js'; |
|||
import ForStatement from './ForStatement.js'; |
|||
import ForInStatement from './ForInStatement.js'; |
|||
import ForOfStatement from './ForOfStatement.js'; |
|||
import FunctionDeclaration from './FunctionDeclaration.js'; |
|||
import FunctionExpression from './FunctionExpression.js'; |
|||
import Identifier from './Identifier.js'; |
|||
import IfStatement from './IfStatement.js'; |
|||
import ImportDeclaration from './ImportDeclaration.js'; |
|||
import Literal from './Literal.js'; |
|||
import MemberExpression from './MemberExpression.js'; |
|||
import NewExpression from './NewExpression.js'; |
|||
import ObjectExpression from './ObjectExpression.js'; |
|||
import ReturnStatement from './ReturnStatement.js'; |
|||
import Statement from './shared/Statement.js'; |
|||
import TemplateLiteral from './TemplateLiteral.js'; |
|||
import ThisExpression from './ThisExpression.js'; |
|||
import ThrowStatement from './ThrowStatement.js'; |
|||
import UnaryExpression from './UnaryExpression.js'; |
|||
import UpdateExpression from './UpdateExpression.js'; |
|||
import VariableDeclarator from './VariableDeclarator.js'; |
|||
import VariableDeclaration from './VariableDeclaration.js'; |
|||
|
|||
export default { |
|||
ArrayExpression, |
|||
ArrowFunctionExpression, |
|||
AssignmentExpression, |
|||
BinaryExpression, |
|||
BlockStatement, |
|||
CallExpression, |
|||
ClassDeclaration, |
|||
ClassExpression, |
|||
ConditionalExpression, |
|||
DoWhileStatement: Statement, |
|||
EmptyStatement, |
|||
ExportAllDeclaration, |
|||
ExportDefaultDeclaration, |
|||
ExportNamedDeclaration, |
|||
ExpressionStatement, |
|||
ForStatement, |
|||
ForInStatement, |
|||
ForOfStatement, |
|||
FunctionDeclaration, |
|||
FunctionExpression, |
|||
Identifier, |
|||
IfStatement, |
|||
ImportDeclaration, |
|||
Literal, |
|||
MemberExpression, |
|||
NewExpression, |
|||
ObjectExpression, |
|||
ReturnStatement, |
|||
SwitchStatement: Statement, |
|||
TemplateLiteral, |
|||
ThisExpression, |
|||
ThrowStatement, |
|||
TryStatement: Statement, |
|||
UnaryExpression, |
|||
UpdateExpression, |
|||
VariableDeclarator, |
|||
VariableDeclaration, |
|||
WhileStatement: Statement |
|||
}; |
@ -0,0 +1,16 @@ |
|||
import Node from '../../Node.js'; |
|||
|
|||
export default class Statement extends Node { |
|||
render ( code, es ) { |
|||
if ( !this.module.bundle.treeshake || this.shouldInclude ) { |
|||
super.render( code, es ); |
|||
} else { |
|||
code.remove( this.leadingCommentStart || this.start, this.next || this.end ); |
|||
} |
|||
} |
|||
|
|||
run ( scope ) { |
|||
this.shouldInclude = true; |
|||
super.run( scope ); |
|||
} |
|||
} |
@ -0,0 +1,27 @@ |
|||
import extractNames from '../../utils/extractNames.js'; |
|||
|
|||
export default function assignToForLoopLeft ( node, scope, value ) { |
|||
if ( node.type === 'VariableDeclaration' ) { |
|||
for ( const proxy of node.declarations[0].proxies.values() ) { |
|||
proxy.possibleValues.add( value ); |
|||
} |
|||
} |
|||
|
|||
else { |
|||
if ( node.type === 'MemberExpression' ) { |
|||
// apparently this is legal JavaScript? Though I don't know what
|
|||
// kind of monster would write `for ( foo.bar of thing ) {...}`
|
|||
|
|||
// for now, do nothing, as I'm not sure anything needs to happen...
|
|||
} |
|||
|
|||
else { |
|||
for ( const name of extractNames( node ) ) { |
|||
const declaration = scope.findDeclaration( name ); |
|||
if ( declaration.possibleValues ) { |
|||
declaration.possibleValues.add( value ); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,67 @@ |
|||
import flatten from '../../utils/flatten.js'; |
|||
import isReference from '../../utils/isReference.js'; |
|||
import pureFunctions from './pureFunctions.js'; |
|||
import { UNKNOWN } from '../../values.js'; |
|||
|
|||
const currentlyCalling = new Set(); |
|||
|
|||
function fnHasEffects ( fn ) { |
|||
if ( currentlyCalling.has( fn ) ) return false; // prevent infinite loops... TODO there must be a better way
|
|||
currentlyCalling.add( fn ); |
|||
|
|||
// handle body-less arrow functions
|
|||
const scope = fn.body.scope || fn.scope; |
|||
const body = fn.body.type === 'BlockStatement' ? fn.body.body : [ fn.body ]; |
|||
|
|||
for ( const node of body ) { |
|||
if ( node.hasEffects( scope ) ) { |
|||
currentlyCalling.delete( fn ); |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
currentlyCalling.delete( fn ); |
|||
return false; |
|||
} |
|||
|
|||
export default function callHasEffects ( scope, callee ) { |
|||
const values = new Set([ callee ]); |
|||
|
|||
for ( const node of values ) { |
|||
if ( node === UNKNOWN ) return true; // err on side of caution
|
|||
|
|||
if ( /Function/.test( node.type ) ) { |
|||
if ( fnHasEffects( node ) ) return true; |
|||
} |
|||
|
|||
else if ( isReference( node ) ) { |
|||
const flattened = flatten( node ); |
|||
const declaration = scope.findDeclaration( flattened.name ); |
|||
|
|||
if ( declaration.isGlobal ) { |
|||
if ( !pureFunctions[ flattened.keypath ] ) return true; |
|||
} |
|||
|
|||
else if ( declaration.isExternal ) { |
|||
return true; // TODO make this configurable? e.g. `path.[whatever]`
|
|||
} |
|||
|
|||
else { |
|||
if ( node.declaration ) { |
|||
node.declaration.gatherPossibleValues( values ); |
|||
} else { |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
else { |
|||
if ( !node.gatherPossibleValues ) { |
|||
throw new Error( 'TODO' ); |
|||
} |
|||
node.gatherPossibleValues( values ); |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
@ -0,0 +1,28 @@ |
|||
import getLocation from '../../../utils/getLocation.js'; |
|||
import error from '../../../utils/error.js'; |
|||
|
|||
// TODO tidy this up a bit (e.g. they can both use node.module.imports)
|
|||
export default function disallowIllegalReassignment ( scope, node ) { |
|||
if ( node.type === 'MemberExpression' && node.object.type === 'Identifier' ) { |
|||
const declaration = scope.findDeclaration( node.object.name ); |
|||
if ( declaration.isNamespace ) { |
|||
error({ |
|||
message: `Illegal reassignment to import '${node.object.name}'`, |
|||
file: node.module.id, |
|||
pos: node.start, |
|||
loc: getLocation( node.module.code, node.start ) |
|||
}); |
|||
} |
|||
} |
|||
|
|||
else if ( node.type === 'Identifier' ) { |
|||
if ( node.module.imports[ node.name ] && !scope.contains( node.name ) ) { |
|||
error({ |
|||
message: `Illegal reassignment to import '${node.name}'`, |
|||
file: node.module.id, |
|||
pos: node.start, |
|||
loc: getLocation( node.module.code, node.start ) |
|||
}); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,38 @@ |
|||
import { UNKNOWN } from '../../values.js'; |
|||
|
|||
export default function isUsedByBundle ( scope, node ) { |
|||
// const expression = node;
|
|||
while ( node.type === 'MemberExpression' ) node = node.object; |
|||
|
|||
const declaration = scope.findDeclaration( node.name ); |
|||
|
|||
if ( declaration.isParam ) { |
|||
return true; |
|||
|
|||
// TODO if we mutate a parameter, assume the worst
|
|||
// return node !== expression;
|
|||
} |
|||
|
|||
if ( declaration.activated ) return true; |
|||
|
|||
const values = new Set(); |
|||
declaration.gatherPossibleValues( values ); |
|||
for ( const value of values ) { |
|||
if ( value === UNKNOWN ) { |
|||
return true; |
|||
} |
|||
|
|||
if ( value.type === 'Identifier' ) { |
|||
if ( value.declaration.activated ) { |
|||
return true; |
|||
} |
|||
value.declaration.gatherPossibleValues( values ); |
|||
} |
|||
|
|||
else if ( value.gatherPossibleValues ) { |
|||
value.gatherPossibleValues( values ); |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
@ -1,9 +1,9 @@ |
|||
let pureFunctions = {}; |
|||
const pureFunctions = {}; |
|||
|
|||
const arrayTypes = 'Array Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array'.split( ' ' ); |
|||
const simdTypes = 'Int8x16 Int16x8 Int32x4 Float32x4 Float64x2'.split( ' ' ); |
|||
const simdMethods = 'abs add and bool check div equal extractLane fromFloat32x4 fromFloat32x4Bits fromFloat64x2 fromFloat64x2Bits fromInt16x8Bits fromInt32x4 fromInt32x4Bits fromInt8x16Bits greaterThan greaterThanOrEqual lessThan lessThanOrEqual load max maxNum min minNum mul neg not notEqual or reciprocalApproximation reciprocalSqrtApproximation replaceLane select selectBits shiftLeftByScalar shiftRightArithmeticByScalar shiftRightLogicalByScalar shuffle splat sqrt store sub swizzle xor'.split( ' ' ); |
|||
let allSimdMethods = []; |
|||
const allSimdMethods = []; |
|||
simdTypes.forEach( t => { |
|||
simdMethods.forEach( m => { |
|||
allSimdMethods.push( `SIMD.${t}.${m}` ); |
@ -0,0 +1,40 @@ |
|||
import Scope from './Scope.js'; |
|||
import { UNKNOWN } from '../values'; |
|||
|
|||
class SyntheticGlobalDeclaration { |
|||
constructor ( name ) { |
|||
this.name = name; |
|||
this.isExternal = true; |
|||
this.isGlobal = true; |
|||
this.isReassigned = false; |
|||
|
|||
this.activated = true; |
|||
} |
|||
|
|||
activate () { |
|||
/* noop */ |
|||
} |
|||
|
|||
addReference ( reference ) { |
|||
reference.declaration = this; |
|||
if ( reference.isReassignment ) this.isReassigned = true; |
|||
} |
|||
|
|||
gatherPossibleValues ( values ) { |
|||
values.add( UNKNOWN ); |
|||
} |
|||
|
|||
getName () { |
|||
return this.name; |
|||
} |
|||
} |
|||
|
|||
export default class BundleScope extends Scope { |
|||
findDeclaration ( name ) { |
|||
if ( !this.declarations[ name ] ) { |
|||
this.declarations[ name ] = new SyntheticGlobalDeclaration( name ); |
|||
} |
|||
|
|||
return this.declarations[ name ]; |
|||
} |
|||
} |
@ -0,0 +1,53 @@ |
|||
import { forOwn } from '../../utils/object.js'; |
|||
import Scope from './Scope.js'; |
|||
|
|||
export default class ModuleScope extends Scope { |
|||
constructor ( module ) { |
|||
super({ |
|||
isBlockScope: false, |
|||
isLexicalBoundary: true, |
|||
isModuleScope: true, |
|||
parent: module.bundle.scope |
|||
}); |
|||
|
|||
this.module = module; |
|||
} |
|||
|
|||
deshadow ( names ) { |
|||
names = new Map( names ); |
|||
|
|||
forOwn( this.module.imports, specifier => { |
|||
if ( specifier.module.isExternal ) return; |
|||
|
|||
specifier.module.getExports().forEach( name => { |
|||
names.set(name); |
|||
}); |
|||
|
|||
if ( specifier.name !== '*' ) { |
|||
const declaration = specifier.module.traceExport( specifier.name ); |
|||
if ( !declaration ) { |
|||
this.module.bundle.onwarn( `Non-existent export '${specifier.name}' is imported from ${specifier.module.id} by ${this.module.id}` ); |
|||
return; |
|||
} |
|||
const name = declaration.getName( true ); |
|||
if ( name !== specifier.name ) { |
|||
names.set( declaration.getName( true ) ); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
super.deshadow( names ); |
|||
} |
|||
|
|||
findDeclaration ( name ) { |
|||
if ( this.declarations[ name ] ) { |
|||
return this.declarations[ name ]; |
|||
} |
|||
|
|||
return this.module.trace( name ) || this.parent.findDeclaration( name ); |
|||
} |
|||
|
|||
findLexicalBoundary () { |
|||
return this; |
|||
} |
|||
} |
@ -0,0 +1,96 @@ |
|||
import { blank, keys } from '../../utils/object.js'; |
|||
import { UNKNOWN } from '../values.js'; |
|||
|
|||
class Parameter { |
|||
constructor ( name ) { |
|||
this.name = name; |
|||
|
|||
this.isParam = true; |
|||
this.activated = true; |
|||
} |
|||
|
|||
activate () { |
|||
// noop
|
|||
} |
|||
|
|||
addReference () { |
|||
// noop?
|
|||
} |
|||
|
|||
gatherPossibleValues ( values ) { |
|||
values.add( UNKNOWN ); // TODO populate this at call time
|
|||
} |
|||
|
|||
getName () { |
|||
return this.name; |
|||
} |
|||
} |
|||
|
|||
export default class Scope { |
|||
constructor ( options = {} ) { |
|||
this.parent = options.parent; |
|||
this.isBlockScope = !!options.isBlockScope; |
|||
this.isLexicalBoundary = !!options.isLexicalBoundary; |
|||
this.isModuleScope = !!options.isModuleScope; |
|||
|
|||
this.children = []; |
|||
if ( this.parent ) this.parent.children.push( this ); |
|||
|
|||
this.declarations = blank(); |
|||
|
|||
if ( this.isLexicalBoundary && !this.isModuleScope ) { |
|||
this.declarations.arguments = new Parameter( 'arguments' ); |
|||
} |
|||
} |
|||
|
|||
addDeclaration ( name, declaration, isVar, isParam ) { |
|||
if ( isVar && this.isBlockScope ) { |
|||
this.parent.addDeclaration( name, declaration, isVar, isParam ); |
|||
} else { |
|||
const existingDeclaration = this.declarations[ name ]; |
|||
|
|||
if ( existingDeclaration && existingDeclaration.duplicates ) { |
|||
// TODO warn/throw on duplicates?
|
|||
existingDeclaration.duplicates.push( declaration ); |
|||
} else { |
|||
this.declarations[ name ] = isParam ? new Parameter( name ) : declaration; |
|||
} |
|||
} |
|||
} |
|||
|
|||
contains ( name ) { |
|||
return !!this.declarations[ name ] || |
|||
( this.parent ? this.parent.contains( name ) : false ); |
|||
} |
|||
|
|||
deshadow ( names ) { |
|||
keys( this.declarations ).forEach( key => { |
|||
const declaration = this.declarations[ key ]; |
|||
|
|||
// we can disregard exports.foo etc
|
|||
if ( declaration.exportName && declaration.isReassigned ) return; |
|||
|
|||
const name = declaration.getName( true ); |
|||
let deshadowed = name; |
|||
|
|||
let i = 1; |
|||
|
|||
while ( names.has( deshadowed ) ) { |
|||
deshadowed = `${name}$$${i++}`; |
|||
} |
|||
|
|||
declaration.name = deshadowed; |
|||
}); |
|||
|
|||
this.children.forEach( scope => scope.deshadow( names ) ); |
|||
} |
|||
|
|||
findDeclaration ( name ) { |
|||
return this.declarations[ name ] || |
|||
( this.parent && this.parent.findDeclaration( name ) ); |
|||
} |
|||
|
|||
findLexicalBoundary () { |
|||
return this.isLexicalBoundary ? this : this.parent.findLexicalBoundary(); |
|||
} |
|||
} |
@ -1,5 +1,5 @@ |
|||
export default function flatten ( node ) { |
|||
let parts = []; |
|||
const parts = []; |
|||
while ( node.type === 'MemberExpression' ) { |
|||
if ( node.computed ) return null; |
|||
parts.unshift( node.property.name ); |
@ -0,0 +1,10 @@ |
|||
export default function isProgramLevel ( node ) { |
|||
do { |
|||
if ( node.type === 'Program' ) { |
|||
return true; |
|||
} |
|||
node = node.parent; |
|||
} while ( node && !/Function/.test( node.type ) ); |
|||
|
|||
return false; |
|||
} |
@ -0,0 +1,8 @@ |
|||
// properties are for debugging purposes only
|
|||
export const ARRAY = { ARRAY: true, toString: () => '[[ARRAY]]' }; |
|||
export const BOOLEAN = { BOOLEAN: true, toString: () => '[[BOOLEAN]]' }; |
|||
export const FUNCTION = { FUNCTION: true, toString: () => '[[FUNCTION]]' }; |
|||
export const NUMBER = { NUMBER: true, toString: () => '[[NUMBER]]' }; |
|||
export const OBJECT = { OBJECT: true, toString: () => '[[OBJECT]]' }; |
|||
export const STRING = { STRING: true, toString: () => '[[STRING]]' }; |
|||
export const UNKNOWN = { UNKNOWN: true, toString: () => '[[UNKNOWN]]' }; |
@ -1,7 +1,7 @@ |
|||
import amd from './amd.js'; |
|||
import cjs from './cjs.js'; |
|||
import es6 from './es6.js'; |
|||
import es from './es.js'; |
|||
import iife from './iife.js'; |
|||
import umd from './umd.js'; |
|||
|
|||
export default { amd, cjs, es6, iife, umd }; |
|||
export default { amd, cjs, es, iife, umd }; |
|||
|
@ -0,0 +1 @@ |
|||
export default `Object.defineProperty(exports, '__esModule', { value: true });`; |
@ -0,0 +1,18 @@ |
|||
// Generate strings which dereference dotted properties, but use array notation `['prop-deref']`
|
|||
// if the property name isn't trivial
|
|||
|
|||
const shouldUseDot = /^[a-zA-Z$_][a-zA-Z0-9$_]*$/; |
|||
const dereferenceString = prop => |
|||
prop.match(shouldUseDot) ? `.${prop}` : `['${prop}']`; |
|||
|
|||
/** |
|||
* returns a function which generates property dereference strings for the given name |
|||
* |
|||
* const getGlobalProp = propertyStringFor('global'); |
|||
* getGlobalProp('foo.bar-baz.qux') => `global.bar['bar-baz'].qux` |
|||
*/ |
|||
const propertyStringFor = objName => propName => |
|||
objName + propName.split('.').map(dereferenceString).join(''); |
|||
|
|||
|
|||
export default propertyStringFor; |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue