diff --git a/.eslintrc b/.eslintrc index cab1314..d9150f9 100644 --- a/.eslintrc +++ b/.eslintrc @@ -27,9 +27,18 @@ "browser": true, "node": true }, - "extends": "eslint:recommended", + "extends": [ + "eslint:recommended", + "plugin:import/errors", + "plugin:import/warnings" + ], "parserOptions": { "ecmaVersion": 6, "sourceType": "module" + }, + "settings": { + "import/ignore": [ 0, [ + "\\.path.js$" + ] ] } } diff --git a/CHANGELOG.md b/CHANGELOG.md index acd43df..aa4f31b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,91 @@ # rollup changelog +## 0.35.15 + +* Warn on missing unused imports in deshadowing phase ([#928](https://github.com/rollup/rollup/issues/928)) +* Always add a newline to the end of bundles ([#958](https://github.com/rollup/rollup/issues/958)) + +## 0.35.14 + +* Include all parent statements of expression with effects, up to function boundary ([#930](https://github.com/rollup/rollup/issues/930)) + +## 0.35.13 + +* Include superclasses when including their subclasses ([#932](https://github.com/rollup/rollup/issues/932)) + +## 0.35.12 + +* Add `interop: false` option to disable unwrapping of external imports ([#939](https://github.com/rollup/rollup/issues/939)) + +## 0.35.11 + +* Deconflict reified namespaces with other declarations ([#910](https://github.com/rollup/rollup/issues/910)) + +## 0.35.10 + +* Only remove EmptyStatement nodes directly inside blocks ([#913](https://github.com/rollup/rollup/issues/931)) + +## 0.35.9 + +* Support Node 0.12 ([#909](https://github.com/rollup/rollup/issues/909)) + +## 0.35.8 + +* Correctly deshadow re-assigned module functions ([#910](https://github.com/rollup/rollup/issues/910)) + +## 0.35.7 + +* Refactor `flushTime.js` ([#922](https://github.com/rollup/rollup/pull/922)) + +## 0.35.6 + +* Fix browser build + +## 0.35.5 + +* Allow empty for loop heads ([#919](https://github.com/rollup/rollup/issues/919)) + +## 0.35.4 + +* Preserve effects in for-of and for-in loops ([#870](https://github.com/rollup/rollup/issues/870)) +* Remove empty statements ([#918](https://github.com/rollup/rollup/pull/918)) + +## 0.35.3 + +* Render identifiers inside template literals + +## 0.35.2 + +* Fix broken build caused by out of date locally installed dependencies + +## 0.35.1 + +* Rewrite deconflicted class identifiers ([#915](https://github.com/rollup/rollup/pull/915)) +* Include `dependencies` in `bundle.modules` objects ([#903](https://github.com/rollup/rollup/issues/903)) +* Update to Acorn 4 ([#914](https://github.com/rollup/rollup/pull/914)) + +## 0.35.0 + +* Rewrite analysis/tree-shaking code ([#902](https://github.com/rollup/rollup/pull/902)) +* Include conditional mutations of global objects ([#901](https://github.com/rollup/rollup/issues/901)) +* Only reify namespaces if necessary ([#898](https://github.com/rollup/rollup/issues/898)) +* Track mutations of aliased globals ([#893](https://github.com/rollup/rollup/issues/893)) +* Include duplicated var declarations ([#716](https://github.com/rollup/rollup/issues/716)) + +## 0.34.13 + +* Pass `{ format }` through to `transformBundle` ([#867](https://github.com/rollup/rollup/issues/867)) + +## 0.34.12 + +* Fix `rollup --watch` ([#887](https://github.com/rollup/rollup/issues/887)) +* Case-sensitive paths ([#862](https://github.com/rollup/rollup/issues/862)) + +## 0.34.11 + +* Prevent leaky state when `bundle` is reused ([#875](https://github.com/rollup/rollup/issues/875)) +* Ensure `intro` appears before interop block ([#880](https://github.com/rollup/rollup/issues/880)) + ## 0.34.10 * Allow custom `options.context` to replace top-level `this` ([#851](https://github.com/rollup/rollup/issues/851)) diff --git a/LICENSE.md b/LICENSE.md index ae037ce..ec3180a 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 [these people](https://github.com/rollup/rollup/graphs/contributors) +Copyright (c) 2016 [these people](https://github.com/rollup/rollup/graphs/contributors) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 6e02e51..c5de896 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ alt="dependency status"> - Coverage via Codecov + Coverage via Codecov ) -o, --output Output (if absent, prints to stdout) --f, --format [es6] Type of output (amd, cjs, es6, iife, umd) +-f, --format [es] Type of output (amd, cjs, es, iife, umd) -e, --external Comma-separate list of module IDs to exclude -g, --globals Comma-separate list of `module ID:Global` pairs Any module IDs defined here are added to external diff --git a/bin/src/runRollup.js b/bin/src/runRollup.js index 270654f..53a6231 100644 --- a/bin/src/runRollup.js +++ b/bin/src/runRollup.js @@ -1,10 +1,9 @@ import { realpathSync } from 'fs'; +import * as rollup from 'rollup'; import relative from 'require-relative'; import handleError from './handleError'; import SOURCEMAPPING_URL from './sourceMappingUrl.js'; -const rollup = require( '../dist/rollup.js' ); // TODO make this an import, somehow - import { install as installSourcemapSupport } from 'source-map-support'; installSourcemapSupport(); diff --git a/package.json b/package.json index 25191db..84ae321 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rollup", - "version": "0.34.10", + "version": "0.35.15", "description": "Next-generation ES6 module bundler", "main": "dist/rollup.js", "module": "dist/rollup.es.js", @@ -11,14 +11,17 @@ "scripts": { "pretest": "npm run build && npm run build:cli", "test": "mocha", + "test:quick": "rollup -c && mocha", "pretest-coverage": "npm run build", "test-coverage": "rm -rf coverage/* && istanbul cover --report json node_modules/.bin/_mocha -- -u exports -R spec test/test.js", "posttest-coverage": "remap-istanbul -i coverage/coverage-final.json -o coverage/coverage-remapped.json -b dist && remap-istanbul -i coverage/coverage-final.json -o coverage/coverage-remapped.lcov -t lcovonly -b dist && remap-istanbul -i coverage/coverage-final.json -o coverage/coverage-remapped -t html -b dist", "ci": "npm run test-coverage && codecov < coverage/coverage-remapped.lcov", "build": "git rev-parse HEAD > .commithash && rollup -c", - "watch": "rollup -c -w", "build:cli": "rollup -c rollup.config.cli.js", - "build:browser": "git rev-parse HEAD > .commithash && rollup -c rollup.config.browser.js -o dist/rollup.browser.js", + "build:browser": "git rev-parse HEAD > .commithash && rollup -c rollup.config.browser.js", + "watch": "rollup -c -w", + "watch:browser": "rollup -c rollup.config.browser.js -w", + "watch:cli": "rollup -c rollup.config.cli.js -w", "prepublish": "npm run lint && npm test && npm run build:browser", "lint": "eslint src browser test/test.js test/utils test/**/_config.js" }, @@ -44,12 +47,13 @@ }, "homepage": "https://github.com/rollup/rollup", "devDependencies": { - "acorn": "^3.2.0", + "acorn": "^4.0.1", "buble": "^0.12.5", "chalk": "^1.1.3", "codecov.io": "^0.1.6", - "console-group": "^0.2.1", + "console-group": "^0.3.1", "eslint": "^2.13.0", + "eslint-plugin-import": "^1.14.0", "estree-walker": "^0.2.1", "istanbul": "^0.4.3", "magic-string": "^0.15.2", @@ -57,7 +61,7 @@ "mocha": "^3.0.0", "remap-istanbul": "^0.6.4", "require-relative": "^0.8.7", - "rollup": "^0.34.2", + "rollup": "^0.34.0", "rollup-plugin-buble": "^0.12.1", "rollup-plugin-commonjs": "^3.0.0", "rollup-plugin-json": "^2.0.0", diff --git a/rollup.config.browser.js b/rollup.config.browser.js index 687f257..95977b4 100644 --- a/rollup.config.browser.js +++ b/rollup.config.browser.js @@ -9,5 +9,6 @@ config.plugins.push({ }); config.format = 'umd'; +config.dest = 'dist/rollup.browser.js'; export default config; diff --git a/rollup.config.cli.js b/rollup.config.cli.js index 95e8588..982f6b0 100644 --- a/rollup.config.cli.js +++ b/rollup.config.cli.js @@ -15,7 +15,7 @@ export default { buble(), commonjs({ include: 'node_modules/**', - namedExports: { 'chalk': [ 'red', 'cyan', 'grey' ] } + namedExports: { chalk: [ 'red', 'cyan', 'grey' ] } }), nodeResolve({ main: true @@ -25,6 +25,10 @@ export default { 'fs', 'path', 'module', - 'source-map-support' - ] + 'source-map-support', + 'rollup' + ], + paths: { + rollup: '../dist/rollup.js' + } }; diff --git a/rollup.config.js b/rollup.config.js index 7847ff7..cc0b9ba 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -22,7 +22,10 @@ export default { entry: 'src/rollup.js', plugins: [ buble({ - include: [ 'src/**', 'node_modules/acorn/**' ] + include: [ 'src/**', 'node_modules/acorn/**' ], + target: { + node: '0.12' + } }), nodeResolve({ diff --git a/src/Bundle.js b/src/Bundle.js index ffffbf6..a4e8c53 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -1,3 +1,4 @@ +import { timeStart, timeEnd } from './utils/flushTime.js'; import { decode } from 'sourcemap-codec'; import { Bundle as MagicStringBundle } from 'magic-string'; import first from './utils/first.js'; @@ -17,6 +18,7 @@ import collapseSourcemaps from './utils/collapseSourcemaps.js'; import SOURCEMAPPING_URL from './utils/sourceMappingURL.js'; import callIfFunction from './utils/callIfFunction.js'; import { dirname, isRelative, isAbsolute, normalize, relative, resolve } from './utils/path.js'; +import BundleScope from './ast/scopes/BundleScope.js'; export default class Bundle { constructor ( options ) { @@ -59,14 +61,17 @@ export default class Bundle { ( id => options.paths.hasOwnProperty( id ) ? options.paths[ id ] : this.getPathRelativeToEntryDirname( id ) ) : id => this.getPathRelativeToEntryDirname( id ); + this.scope = new BundleScope(); + // TODO strictly speaking, this only applies with non-ES6, non-default-only bundles + [ 'module', 'exports', '_interopDefault' ].forEach( name => { + this.scope.findDeclaration( name ); // creates global declaration as side-effect + }); + this.moduleById = new Map(); this.modules = []; - this.externalModules = []; - this.internalNamespaces = []; this.context = String( options.context ); - this.assumedGlobals = blank(); if ( typeof options.external === 'function' ) { this.isExternal = options.external; @@ -77,11 +82,10 @@ export default class Bundle { this.onwarn = options.onwarn || makeOnwarn(); - // TODO strictly speaking, this only applies with non-ES6, non-default-only bundles - [ 'module', 'exports', '_interopDefault' ].forEach( global => this.assumedGlobals[ global ] = true ); - this.varOrConst = options.preferConst ? 'const' : 'var'; this.acornOptions = options.acorn || {}; + + this.dependentExpressions = []; } build () { @@ -99,36 +103,71 @@ export default class Bundle { // Phase 2 – binding. We link references to their declarations // to generate a complete picture of the bundle + + timeStart( 'phase 2' ); + this.modules.forEach( module => module.bindImportSpecifiers() ); - this.modules.forEach( module => module.bindAliases() ); this.modules.forEach( module => module.bindReferences() ); + timeEnd( 'phase 2' ); + // Phase 3 – marking. We 'run' each statement to see which ones // need to be included in the generated bundle + timeStart( 'phase 3' ); + // mark all export statements entryModule.getExports().forEach( name => { const declaration = entryModule.traceExport( name ); + declaration.exportName = name; + declaration.activate(); - declaration.use(); + if ( declaration.isNamespace ) { + declaration.needsNamespaceBlock = true; + } }); // mark statements that should appear in the bundle - let settled = false; - while ( !settled ) { - settled = true; - + if ( this.treeshake ) { this.modules.forEach( module => { - if ( module.run( this.treeshake ) ) settled = false; + module.run(); }); + + let settled = false; + while ( !settled ) { + settled = true; + + let i = this.dependentExpressions.length; + while ( i-- ) { + const expression = this.dependentExpressions[i]; + + let statement = expression; + while ( statement.parent && !/Function/.test( statement.parent.type ) ) statement = statement.parent; + + if ( !statement || statement.ran ) { + this.dependentExpressions.splice( i, 1 ); + } else if ( expression.isUsedByBundle() ) { + settled = false; + statement.run( statement.findScope() ); + this.dependentExpressions.splice( i, 1 ); + } + } + } } + timeEnd( 'phase 3' ); + // Phase 4 – final preparation. We order the modules with an // enhanced topological sort that accounts for cycles, then // ensure that names are deconflicted throughout the bundle + + timeStart( 'phase 4' ); + this.orderedModules = this.sort(); this.deconflict(); + + timeEnd( 'phase 4' ); }); } @@ -136,7 +175,7 @@ export default class Bundle { const used = blank(); // ensure no conflicts with globals - keys( this.assumedGlobals ).forEach( name => used[ name ] = 1 ); + keys( this.scope.declarations ).forEach( name => used[ name ] = 1 ); function getSafeName ( name ) { while ( used[ name ] ) { @@ -147,27 +186,39 @@ export default class Bundle { return name; } + const toDeshadow = new Map(); + this.externalModules.forEach( module => { - module.name = getSafeName( module.name ); + const safeName = getSafeName( module.name ); + toDeshadow.set( safeName, true ); + module.name = safeName; // ensure we don't shadow named external imports, if // we're creating an ES6 bundle forOwn( module.declarations, ( declaration, name ) => { - declaration.setSafeName( getSafeName( name ) ); + const safeName = getSafeName( name ); + toDeshadow.set( safeName, true ); + declaration.setSafeName( safeName ); }); }); this.modules.forEach( module => { - forOwn( module.declarations, ( declaration, originalName ) => { - if ( declaration.isGlobal ) return; - - if ( originalName === 'default' ) { - if ( declaration.original && !declaration.original.isReassigned ) return; + forOwn( module.scope.declarations, ( declaration ) => { + if ( declaration.isDefault && declaration.declaration.id ) { + return; } declaration.name = getSafeName( declaration.name ); }); + + // deconflict reified namespaces + const namespace = module.namespace(); + if ( namespace.needsNamespaceBlock ) { + namespace.name = getSafeName( namespace.name ); + } }); + + this.scope.deshadow( toDeshadow ); } fetchModule ( id, importer ) { @@ -304,29 +355,38 @@ export default class Bundle { let magicString = new MagicStringBundle({ separator: '\n\n' }); const usedModules = []; + timeStart( 'render modules' ); + this.orderedModules.forEach( module => { const source = module.render( format === 'es' ); + if ( source.toString().length ) { magicString.addSource( source ); usedModules.push( module ); } }); - const intro = [ options.intro ] + timeEnd( 'render modules' ); + + let intro = [ options.intro ] .concat( this.plugins.map( plugin => plugin.intro && plugin.intro() ) ) .filter( Boolean ) .join( '\n\n' ); - if ( intro ) magicString.prepend( intro + '\n' ); + if ( intro ) intro += '\n'; const indentString = getIndentString( magicString, options ); const finalise = finalisers[ format ]; if ( !finalise ) throw new Error( `You must specify an output type - valid options are ${keys( finalisers ).join( ', ' )}` ); - magicString = finalise( this, magicString.trim(), { exportMode, indentString }, options ); + timeStart( 'render format' ); + + magicString = finalise( this, magicString.trim(), { exportMode, indentString, intro }, options ); + + timeEnd( 'render format' ); const banner = [ options.banner ] .concat( this.plugins.map( plugin => plugin.banner ) ) @@ -347,10 +407,12 @@ export default class Bundle { let map = null; const bundleSourcemapChain = []; - code = transformBundle( code, this.plugins, bundleSourcemapChain ) + code = transformBundle( code, this.plugins, bundleSourcemapChain, options ) .replace( new RegExp( `\\/\\/#\\s+${SOURCEMAPPING_URL}=.+\\n?`, 'g' ), '' ); if ( options.sourceMap ) { + timeStart( 'sourceMap' ); + let file = options.sourceMapFile || options.dest; if ( file ) file = resolve( typeof process !== 'undefined' ? process.cwd() : '', file ); @@ -365,8 +427,11 @@ export default class Bundle { } map.sources = map.sources.map( normalize ); + + timeEnd( 'sourceMap' ); } + if ( code[ code.length - 1 ] !== '\n' ) code += '\n'; return { code, map }; } diff --git a/src/Declaration.js b/src/Declaration.js index c383081..8d0934e 100644 --- a/src/Declaration.js +++ b/src/Declaration.js @@ -1,35 +1,24 @@ import { blank, forOwn, keys } from './utils/object.js'; import makeLegalIdentifier from './utils/makeLegalIdentifier.js'; -import run from './utils/run.js'; -import { SyntheticReference } from './Reference.js'; - -const use = alias => alias.use(); +import { UNKNOWN } from './ast/values.js'; export default class Declaration { - constructor ( node, isParam, statement ) { - if ( node ) { - if ( node.type === 'FunctionDeclaration' ) { - this.isFunctionDeclaration = true; - this.functionNode = node; - } else if ( node.type === 'VariableDeclarator' && node.init && /FunctionExpression/.test( node.init.type ) ) { - this.isFunctionDeclaration = true; - this.functionNode = node.init; - } - } + constructor ( node, isParam ) { + this.node = node; - this.statement = statement; this.name = node.id ? node.id.name : node.name; this.exportName = null; this.isParam = isParam; this.isReassigned = false; - this.aliases = []; - - this.isUsed = false; } - addAlias ( declaration ) { - this.aliases.push( declaration ); + activate () { + if ( this.activated ) return; + this.activated = true; + + if ( this.isParam ) return; + this.node.activate(); } addReference ( reference ) { @@ -48,142 +37,15 @@ export default class Declaration { return `exports.${this.exportName}`; } - - run ( strongDependencies ) { - if ( this.tested ) return this.hasSideEffects; - - - if ( !this.functionNode ) { - this.hasSideEffects = true; // err on the side of caution. TODO handle unambiguous `var x; x = y => z` cases - } else { - if ( this.running ) return true; // short-circuit infinite loop - this.running = true; - - this.hasSideEffects = run( this.functionNode.body, this.functionNode._scope, this.statement, strongDependencies, false ); - - this.running = false; - } - - this.tested = true; - return this.hasSideEffects; - } - - use () { - if ( this.isUsed ) return; - - this.isUsed = true; - if ( this.statement ) this.statement.mark(); - - this.aliases.forEach( use ); - } -} - -export class SyntheticDefaultDeclaration { - constructor ( node, statement, name ) { - this.node = node; - this.statement = statement; - this.name = name; - - this.original = null; - this.exportName = null; - this.aliases = []; - } - - addAlias ( declaration ) { - this.aliases.push( declaration ); - } - - addReference ( reference ) { - // Bind the reference to `this` declaration. - reference.declaration = this; - - // Don't change the name to `default`; it's not a valid identifier name. - if ( reference.name === 'default' ) return; - - this.name = reference.name; - } - - bind ( declaration ) { - this.original = declaration; - } - - render () { - return !this.original || this.original.isReassigned ? - this.name : - this.original.render(); - } - - run ( strongDependencies ) { - if ( this.original ) { - return this.original.run( strongDependencies ); - } - - let declaration = this.node.declaration; - while ( declaration.type === 'ParenthesizedExpression' ) declaration = declaration.expression; - - if ( /FunctionExpression/.test( declaration.type ) ) { - return run( declaration.body, this.statement.scope, this.statement, strongDependencies, false ); - } - - // otherwise assume the worst - return true; - } - - use () { - this.isUsed = true; - this.statement.mark(); - - if ( this.original ) this.original.use(); - - this.aliases.forEach( use ); - } -} - -export class SyntheticGlobalDeclaration { - constructor ( name ) { - this.name = name; - this.isExternal = true; - this.isGlobal = true; - this.isReassigned = false; - - this.aliases = []; - - this.isUsed = false; - } - - addAlias ( declaration ) { - this.aliases.push( declaration ); - } - - addReference ( reference ) { - reference.declaration = this; - if ( reference.isReassignment ) this.isReassigned = true; - } - - render () { - return this.name; - } - - run () { - return true; - } - - use () { - if ( this.isUsed ) return; - this.isUsed = true; - - this.aliases.forEach( use ); - } } export class SyntheticNamespaceDeclaration { constructor ( module ) { this.isNamespace = true; this.module = module; - this.name = null; + this.name = module.basename(); this.needsNamespaceBlock = false; - this.aliases = []; this.originals = blank(); module.getExports().forEach( name => { @@ -191,70 +53,40 @@ export class SyntheticNamespaceDeclaration { }); } - addAlias ( declaration ) { - this.aliases.push( declaration ); - } + activate () { + this.needsNamespaceBlock = true; - addReference ( reference ) { - // if we have e.g. `foo.bar`, we can optimise - // the reference by pointing directly to `bar` - if ( reference.parts.length ) { - const ref = reference.parts.shift(); - reference.name = ref.name; - reference.end = ref.end; - - const original = this.originals[ reference.name ]; - - // throw with an informative error message if the reference doesn't exist. - if ( !original ) { - this.module.bundle.onwarn( `Export '${reference.name}' is not defined by '${this.module.id}'` ); - reference.isUndefined = true; - return; - } + // add synthetic references, in case of chained + // namespace imports + forOwn( this.originals, original => { + original.activate(); + }); + } - original.addReference( reference ); - return; - } + addReference ( node ) { + this.name = node.name; + } - // otherwise we're accessing the namespace directly, - // which means we need to mark all of this module's - // exports and render a namespace block in the bundle - if ( !this.needsNamespaceBlock ) { - this.needsNamespaceBlock = true; - this.module.bundle.internalNamespaces.push( this ); - - // add synthetic references, in case of chained - // namespace imports - forOwn( this.originals, ( original, name ) => { - original.addReference( new SyntheticReference( name ) ); - }); - } + gatherPossibleValues ( values ) { + values.add( UNKNOWN ); + } - reference.declaration = this; - this.name = reference.name; + getName () { + return this.name; } - renderBlock ( indentString ) { + renderBlock ( es, indentString ) { const members = keys( this.originals ).map( name => { const original = this.originals[ name ]; if ( original.isReassigned ) { - return `${indentString}get ${name} () { return ${original.render()}; }`; + return `${indentString}get ${name} () { return ${original.getName( es )}; }`; } - return `${indentString}${name}: ${original.render()}`; + return `${indentString}${name}: ${original.getName( es )}`; }); - return `${this.module.bundle.varOrConst} ${this.render()} = Object.freeze({\n${members.join( ',\n' )}\n});\n\n`; - } - - render () { - return this.name; - } - - use () { - forOwn( this.originals, use ); - this.aliases.forEach( use ); + return `${this.module.bundle.varOrConst} ${this.getName( es )} = Object.freeze({\n${members.join( ',\n' )}\n});\n\n`; } } @@ -265,10 +97,12 @@ export class ExternalDeclaration { this.safeName = null; this.isExternal = true; + this.activated = true; + this.isNamespace = name === '*'; } - addAlias () { + activate () { // noop } @@ -280,7 +114,7 @@ export class ExternalDeclaration { } } - render ( es ) { + getName ( es ) { if ( this.name === '*' ) { return this.module.name; } @@ -294,15 +128,7 @@ export class ExternalDeclaration { return es ? this.safeName : `${this.module.name}.${this.name}`; } - run () { - return true; - } - setSafeName ( name ) { this.safeName = name; } - - use () { - // noop? - } } diff --git a/src/Module.js b/src/Module.js index 11abeca..6e58131 100644 --- a/src/Module.js +++ b/src/Module.js @@ -1,20 +1,31 @@ +import { timeStart, timeEnd } from './utils/flushTime.js'; import { parse } from 'acorn/src/index.js'; import MagicString from 'magic-string'; -import { walk } from 'estree-walker'; -import Statement from './Statement.js'; -import { assign, blank, keys } from './utils/object.js'; +import { assign, blank, deepClone, keys } from './utils/object.js'; import { basename, extname } from './utils/path.js'; import getLocation from './utils/getLocation.js'; import makeLegalIdentifier from './utils/makeLegalIdentifier.js'; import SOURCEMAPPING_URL from './utils/sourceMappingURL.js'; -import { - SyntheticDefaultDeclaration, - SyntheticGlobalDeclaration, - SyntheticNamespaceDeclaration -} from './Declaration.js'; -import { isFalsy, isTruthy } from './ast/conditions.js'; -import { emptyBlockStatement } from './ast/create.js'; -import extractNames from './ast/extractNames.js'; +import { SyntheticNamespaceDeclaration } from './Declaration.js'; +import extractNames from './ast/utils/extractNames.js'; +import enhance from './ast/enhance.js'; +import ModuleScope from './ast/scopes/ModuleScope.js'; + +function tryParse ( code, comments, acornOptions, id ) { + try { + return parse( code, assign({ + ecmaVersion: 7, + sourceType: 'module', + onComment: ( block, text, start, end ) => comments.push({ block, text, start, end }), + preserveParens: true + }, acornOptions )); + } catch ( err ) { + err.code = 'PARSE_ERROR'; + err.file = id; // see above - not necessarily true, but true enough + err.message += ` in ${id}`; + throw err; + } +} export default class Module { constructor ({ id, code, originalCode, originalSourceMap, ast, sourceMapChain, resolvedIds, bundle }) { @@ -23,6 +34,15 @@ export default class Module { this.originalSourceMap = originalSourceMap; this.sourceMapChain = sourceMapChain; + this.comments = []; + + timeStart( 'ast' ); + + this.ast = ast || tryParse( code, this.comments, bundle.acornOptions, id ); // TODO what happens to comments if AST is provided? + this.astClone = deepClone( this.ast ); + + timeEnd( 'ast' ); + this.bundle = bundle; this.id = id; this.excludeFromSourcemap = /\0/.test( id ); @@ -55,18 +75,20 @@ export default class Module { this.magicString.remove( match.index, match.index + match[0].length ); } - this.comments = []; - this.ast = ast; - this.statements = this.parse(); - this.declarations = blank(); + this.type = 'Module'; // TODO only necessary so that Scope knows this should be treated as a function scope... messy + this.scope = new ModuleScope( this ); + + timeStart( 'analyse' ); + this.analyse(); + timeEnd( 'analyse' ); + this.strongDependencies = []; } - addExport ( statement ) { - const node = statement.node; + addExport ( node ) { const source = node.source && node.source.value; // export { name } from './other.js' @@ -114,7 +136,7 @@ export default class Module { }; // create a synthetic declaration - this.declarations.default = new SyntheticDefaultDeclaration( node, statement, identifier || this.basename() ); + //this.declarations.default = new SyntheticDefaultDeclaration( node, identifier || this.basename() ); } // export var { foo, bar } = ... @@ -156,8 +178,7 @@ export default class Module { } } - addImport ( statement ) { - const node = statement.node; + addImport ( node ) { const source = node.source.value; if ( !~this.sources.indexOf( source ) ) this.sources.push( source ); @@ -181,17 +202,21 @@ export default class Module { } analyse () { + enhance( this.ast, this, this.comments ); + // discover this module's imports and exports - this.statements.forEach( statement => { - if ( statement.isImportDeclaration ) this.addImport( statement ); - else if ( statement.isExportDeclaration ) this.addExport( statement ); + let lastNode; - statement.firstPass(); + for ( const node of this.ast.body ) { + if ( node.isImportDeclaration ) { + this.addImport( node ); + } else if ( node.isExportDeclaration ) { + this.addExport( node ); + } - statement.scope.eachDeclaration( ( name, declaration ) => { - this.declarations[ name ] = declaration; - }); - }); + if ( lastNode ) lastNode.next = node.leadingCommentStart || node.start; + lastNode = node; + } } basename () { @@ -201,27 +226,6 @@ export default class Module { return makeLegalIdentifier( ext ? base.slice( 0, -ext.length ) : base ); } - bindAliases () { - keys( this.declarations ).forEach( name => { - if ( name === '*' ) return; - - const declaration = this.declarations[ name ]; - const statement = declaration.statement; - - if ( !statement || statement.node.type !== 'VariableDeclaration' ) return; - - const init = statement.node.declarations[0].init; - if ( !init || init.type === 'FunctionExpression' ) return; - - statement.references.forEach( reference => { - if ( reference.name === name ) return; - - const otherDeclaration = this.trace( reference.name ); - if ( otherDeclaration ) otherDeclaration.addAlias( declaration ); - }); - }); - } - bindImportSpecifiers () { [ this.imports, this.reexports ].forEach( specifiers => { keys( specifiers ).forEach( name => { @@ -246,32 +250,25 @@ export default class Module { } bindReferences () { - if ( this.declarations.default ) { - if ( this.exports.default.identifier ) { - const declaration = this.trace( this.exports.default.identifier ); - if ( declaration ) this.declarations.default.bind( declaration ); - } + for ( const node of this.ast.body ) { + node.bind( this.scope ); } - this.statements.forEach( statement => { - // skip `export { foo, bar, baz }`... - if ( statement.node.type === 'ExportNamedDeclaration' && statement.node.specifiers.length ) { - // ...unless this is the entry module - if ( this !== this.bundle.entryModule ) return; - } + // if ( this.declarations.default ) { + // if ( this.exports.default.identifier ) { + // const declaration = this.trace( this.exports.default.identifier ); + // if ( declaration ) this.declarations.default.bind( declaration ); + // } + // } + } - statement.references.forEach( reference => { - const declaration = reference.scope.findDeclaration( reference.name ) || - this.trace( reference.name ); + findParent () { + // TODO what does it mean if we're here? + return null; + } - if ( declaration ) { - declaration.addReference( reference ); - } else { - // TODO handle globals - this.bundle.assumedGlobals[ reference.name ] = true; - } - }); - }); + findScope () { + return this.scope; } getExports () { @@ -286,6 +283,8 @@ export default class Module { }); this.exportAllModules.forEach( module => { + if ( module.isExternal ) return; // TODO + module.getExports().forEach( name => { if ( name !== 'default' ) exports[ name ] = true; }); @@ -302,374 +301,46 @@ export default class Module { return this.declarations['*']; } - parse () { - // The ast can be supplied programmatically (but usually won't be) - if ( !this.ast ) { - // Try to extract a list of top-level statements/declarations. If - // the parse fails, attach file info and abort - try { - this.ast = parse( this.code, assign({ - ecmaVersion: 6, - sourceType: 'module', - onComment: ( block, text, start, end ) => this.comments.push({ block, text, start, end }), - preserveParens: true - }, this.bundle.acornOptions )); - } catch ( err ) { - err.code = 'PARSE_ERROR'; - err.file = this.id; // see above - not necessarily true, but true enough - err.message += ` in ${this.id}`; - throw err; - } - } - - walk( this.ast, { - enter: node => { - // eliminate dead branches early - if ( node.type === 'IfStatement' ) { - if ( isFalsy( node.test ) ) { - this.magicString.overwrite( node.consequent.start, node.consequent.end, '{}' ); - node.consequent = emptyBlockStatement( node.consequent.start, node.consequent.end ); - } else if ( node.alternate && isTruthy( node.test ) ) { - this.magicString.overwrite( node.alternate.start, node.alternate.end, '{}' ); - node.alternate = emptyBlockStatement( node.alternate.start, node.alternate.end ); - } - } - - this.magicString.addSourcemapLocation( node.start ); - this.magicString.addSourcemapLocation( node.end ); - }, - - leave: ( node, parent, prop ) => { - // eliminate dead branches early - if ( node.type === 'ConditionalExpression' ) { - if ( isFalsy( node.test ) ) { - this.magicString.remove( node.start, node.alternate.start ); - parent[prop] = node.alternate; - } else if ( isTruthy( node.test ) ) { - this.magicString.remove( node.start, node.consequent.start ); - this.magicString.remove( node.consequent.end, node.end ); - parent[prop] = node.consequent; - } - } - } - }); - - const statements = []; - let lastChar = 0; - let commentIndex = 0; - - this.ast.body.forEach( node => { - if ( node.type === 'EmptyStatement' ) return; - - if ( - node.type === 'ExportNamedDeclaration' && - node.declaration && - node.declaration.type === 'VariableDeclaration' && - node.declaration.declarations && - node.declaration.declarations.length > 1 - ) { - // push a synthetic export declaration - const syntheticNode = { - type: 'ExportNamedDeclaration', - specifiers: node.declaration.declarations.map( declarator => { - const id = { name: declarator.id.name }; - return { - local: id, - exported: id - }; - }), - isSynthetic: true - }; - - const statement = new Statement( syntheticNode, this, node.start, node.start ); - statements.push( statement ); - - this.magicString.remove( node.start, node.declaration.start ); - node = node.declaration; - } - - // special case - top-level var declarations with multiple declarators - // should be split up. Otherwise, we may end up including code we - // don't need, just because an unwanted declarator is included - if ( node.type === 'VariableDeclaration' && node.declarations.length > 1 ) { - // remove the leading var/let/const... UNLESS the previous node - // was also a synthetic node, in which case it'll get removed anyway - const lastStatement = statements[ statements.length - 1 ]; - if ( !lastStatement || !lastStatement.node.isSynthetic ) { - this.magicString.remove( node.start, node.declarations[0].start ); - } - - node.declarations.forEach( declarator => { - const { start, end } = declarator; - - const syntheticNode = { - type: 'VariableDeclaration', - kind: node.kind, - start, - end, - declarations: [ declarator ], - isSynthetic: true - }; - - const statement = new Statement( syntheticNode, this, start, end ); - statements.push( statement ); - }); - - lastChar = node.end; // TODO account for trailing line comment - } - - else { - let comment; - do { - comment = this.comments[ commentIndex ]; - if ( !comment ) break; - if ( comment.start > node.start ) break; - commentIndex += 1; - } while ( comment.end < lastChar ); - - const start = comment ? Math.min( comment.start, node.start ) : node.start; - const end = node.end; // TODO account for trailing line comment - - const statement = new Statement( node, this, start, end ); - statements.push( statement ); - - lastChar = end; - } - }); - - let i = statements.length; - let next = this.code.length; - while ( i-- ) { - statements[i].next = next; - if ( !statements[i].isSynthetic ) next = statements[i].start; - } - - return statements; - } - render ( es ) { - const magicString = this.magicString; - - this.statements.forEach( statement => { - if ( !statement.isIncluded ) { - if ( statement.node.type === 'ImportDeclaration' ) { - magicString.remove( statement.node.start, statement.next ); - return; - } - - magicString.remove( statement.start, statement.next ); - return; - } - - statement.stringLiteralRanges.forEach( range => magicString.indentExclusionRanges.push( range ) ); - - // skip `export { foo, bar, baz }` - if ( statement.node.type === 'ExportNamedDeclaration' ) { - if ( statement.node.isSynthetic ) return; - - // skip `export { foo, bar, baz }` - if ( statement.node.declaration === null ) { - magicString.remove( statement.start, statement.next ); - return; - } - } - - // split up/remove var declarations as necessary - if ( statement.node.type === 'VariableDeclaration' ) { - const declarator = statement.node.declarations[0]; - - if ( declarator.id.type === 'Identifier' ) { - const declaration = this.declarations[ declarator.id.name ]; - - if ( declaration.exportName && declaration.isReassigned ) { // `var foo = ...` becomes `exports.foo = ...` - magicString.remove( statement.start, declarator.init ? declarator.start : statement.next ); - if ( !declarator.init ) return; - } - } - - else { - // we handle destructuring differently, because whereas we can rewrite - // `var foo = ...` as `exports.foo = ...`, in a case like `var { a, b } = c()` - // where `a` or `b` is exported and reassigned, we have to append - // `exports.a = a;` and `exports.b = b` instead - extractNames( declarator.id ).forEach( name => { - const declaration = this.declarations[ name ]; - - if ( declaration.exportName && declaration.isReassigned ) { - magicString.insertLeft( statement.end, `;\nexports.${name} = ${declaration.render( es )}` ); - } - }); - } - - if ( statement.node.isSynthetic ) { - // insert `var/let/const` if necessary - magicString.insertRight( statement.start, `${statement.node.kind} ` ); - magicString.insertLeft( statement.end, ';' ); - magicString.overwrite( statement.end, statement.next, '\n' ); // TODO account for trailing newlines - } - } - - const toDeshadow = blank(); - - statement.references.forEach( reference => { - const { start, end } = reference; - - if ( reference.isUndefined ) { - magicString.overwrite( start, end, 'undefined', true ); - } - - const declaration = reference.declaration; - - if ( declaration ) { - const name = declaration.render( es ); - - // the second part of this check is necessary because of - // namespace optimisation – name of `foo.bar` could be `bar` - if ( reference.name === name && name.length === end - start ) return; - - reference.rewritten = true; - - // prevent local variables from shadowing renamed references - const identifier = name.match( /[^\.]+/ )[0]; - if ( reference.scope.contains( identifier ) ) { - toDeshadow[ identifier ] = `${identifier}$$`; // TODO more robust mechanism - } - - if ( reference.isShorthandProperty ) { - magicString.insertLeft( end, `: ${name}` ); - } else { - magicString.overwrite( start, end, name, true ); - } - } - }); - - if ( keys( toDeshadow ).length ) { - statement.references.forEach( reference => { - if ( !reference.rewritten && reference.name in toDeshadow ) { - const replacement = toDeshadow[ reference.name ]; - magicString.overwrite( reference.start, reference.end, reference.isShorthandProperty ? `${reference.name}: ${replacement}` : replacement, true ); - } - }); - } - - // modify exports as necessary - if ( statement.isExportDeclaration ) { - // remove `export` from `export var foo = 42` - // TODO: can we do something simpler here? - // we just want to remove `export`, right? - if ( statement.node.type === 'ExportNamedDeclaration' && statement.node.declaration.type === 'VariableDeclaration' ) { - const name = extractNames( statement.node.declaration.declarations[ 0 ].id )[ 0 ]; - const declaration = this.declarations[ name ]; - - // TODO is this even possible? - if ( !declaration ) throw new Error( `Missing declaration for ${name}!` ); - - let end; - - if ( es ) { - end = statement.node.declaration.start; - } else { - if ( declaration.exportName && declaration.isReassigned ) { - const declarator = statement.node.declaration.declarations[0]; - end = declarator.init ? declarator.start : statement.next; - } else { - end = statement.node.declaration.start; - } - } - - magicString.remove( statement.node.start, end ); - } - - else if ( statement.node.type === 'ExportAllDeclaration' ) { - // TODO: remove once `export * from 'external'` is supported. - magicString.remove( statement.start, statement.next ); - } - - // remove `export` from `export class Foo {...}` or `export default Foo` - // TODO default exports need different treatment - else if ( statement.node.declaration.id ) { - magicString.remove( statement.node.start, statement.node.declaration.start ); - } + const magicString = this.magicString.clone(); - else if ( statement.node.type === 'ExportDefaultDeclaration' ) { - const defaultDeclaration = this.declarations.default; - - // prevent `var foo = foo` - if ( defaultDeclaration.original && !defaultDeclaration.original.isReassigned ) { - magicString.remove( statement.start, statement.next ); - return; - } - - const defaultName = defaultDeclaration.render(); - - // prevent `var undefined = sideEffectyDefault(foo)` - if ( !defaultDeclaration.exportName && !defaultDeclaration.isUsed ) { - magicString.remove( statement.start, statement.node.declaration.start ); - return; - } - - // anonymous functions should be converted into declarations - if ( statement.node.declaration.type === 'FunctionExpression' ) { - magicString.overwrite( statement.node.start, statement.node.declaration.start + 8, `function ${defaultName}` ); - } else { - magicString.overwrite( statement.node.start, statement.node.declaration.start, `${this.bundle.varOrConst} ${defaultName} = ` ); - } - } - - else { - throw new Error( 'Unhandled export' ); - } - } - }); + for ( const node of this.ast.body ) { + node.render( magicString, es ); + } - // add namespace block if necessary - const namespace = this.declarations['*']; - if ( namespace && namespace.needsNamespaceBlock ) { - magicString.append( '\n\n' + namespace.renderBlock( magicString.getIndentString() ) ); + if ( this.namespace().needsNamespaceBlock ) { + magicString.append( '\n\n' + this.namespace().renderBlock( es, '\t' ) ); // TODO use correct indentation } return magicString.trim(); } - /** - * Statically runs the module marking the top-level statements that must be - * included for the module to execute successfully. - * - * @param {boolean} treeshake - if we should tree-shake the module - * @return {boolean} marked - if any new statements were marked for inclusion - */ - run ( treeshake ) { - if ( !treeshake ) { - this.statements.forEach( statement => { - if ( statement.isImportDeclaration || ( statement.isExportDeclaration && statement.node.isSynthetic ) ) return; - - statement.mark(); - }); - return false; + run () { + for ( const node of this.ast.body ) { + if ( node.hasEffects( this.scope ) ) { + node.run( this.scope ); + } } - - let marked = false; - - this.statements.forEach( statement => { - marked = statement.run( this.strongDependencies ) || marked; - }); - - return marked; } toJSON () { return { id: this.id, + dependencies: this.dependencies.map( module => module.id ), code: this.code, originalCode: this.originalCode, - ast: this.ast, + ast: this.astClone, sourceMapChain: this.sourceMapChain, resolvedIds: this.resolvedIds }; } trace ( name ) { - if ( name in this.declarations ) return this.declarations[ name ]; + // TODO this is slightly circular + if ( name in this.scope.declarations ) { + return this.scope.declarations[ name ]; + } + if ( name in this.imports ) { const importDeclaration = this.imports[ name ]; const otherModule = importDeclaration.module; @@ -708,10 +379,7 @@ export default class Module { const name = exportDeclaration.localName; const declaration = this.trace( name ); - if ( declaration ) return declaration; - - this.bundle.assumedGlobals[ name ] = true; - return ( this.declarations[ name ] = new SyntheticGlobalDeclaration( name ) ); + return declaration || this.bundle.scope.findDeclaration( name ); } for ( let i = 0; i < this.exportAllModules.length; i += 1 ) { diff --git a/src/Reference.js b/src/Reference.js deleted file mode 100644 index 5c5c3e9..0000000 --- a/src/Reference.js +++ /dev/null @@ -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 = []; - } -} diff --git a/src/Statement.js b/src/Statement.js deleted file mode 100644 index 7ab8bf8..0000000 --- a/src/Statement.js +++ /dev/null @@ -1,160 +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 contextDepth = 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.type === 'ThisExpression' && contextDepth === 0 ) { - module.magicString.overwrite( node.start, node.end, module.bundle.context ); - if ( module.bundle.context === 'undefined' ) module.bundle.onwarn( 'The `this` keyword is equivalent to `undefined` at the top level of an ES module, and has been rewritten' ); - } - - if ( node._scope ) scope = node._scope; - if ( /^Function/.test( node.type ) ) contextDepth += 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 ) ) contextDepth -= 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 ); - } -} diff --git a/src/ast/Node.js b/src/ast/Node.js new file mode 100644 index 0000000..4f65871 --- /dev/null +++ b/src/ast/Node.js @@ -0,0 +1,94 @@ +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 ) ); + } + + 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 ); + } +} diff --git a/src/ast/Scope.js b/src/ast/Scope.js deleted file mode 100644 index 0dc48ec..0000000 --- a/src/ast/Scope.js +++ /dev/null @@ -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 ) ); - } -} diff --git a/src/ast/attachScopes.js b/src/ast/attachScopes.js deleted file mode 100644 index 83cbc85..0000000 --- a/src/ast/attachScopes.js +++ /dev/null @@ -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; - } - } - }); -} diff --git a/src/ast/conditions.js b/src/ast/conditions.js deleted file mode 100644 index b21e4d4..0000000 --- a/src/ast/conditions.js +++ /dev/null @@ -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 ) -}; diff --git a/src/ast/create.js b/src/ast/create.js deleted file mode 100644 index e767dbd..0000000 --- a/src/ast/create.js +++ /dev/null @@ -1,7 +0,0 @@ -export function emptyBlockStatement ( start, end ) { - return { - start, end, - type: 'BlockStatement', - body: [] - }; -} diff --git a/src/ast/enhance.js b/src/ast/enhance.js new file mode 100644 index 0000000..6a86dab --- /dev/null +++ b/src/ast/enhance.js @@ -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; +} diff --git a/src/ast/isFunctionDeclaration.js b/src/ast/isFunctionDeclaration.js deleted file mode 100644 index a1573e7..0000000 --- a/src/ast/isFunctionDeclaration.js +++ /dev/null @@ -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 ) ); -} diff --git a/src/ast/keys.js b/src/ast/keys.js new file mode 100644 index 0000000..194ccf7 --- /dev/null +++ b/src/ast/keys.js @@ -0,0 +1,4 @@ +export default { + Program: [ 'body' ], + Literal: [] +}; diff --git a/src/ast/modifierNodes.js b/src/ast/modifierNodes.js deleted file mode 100644 index 3696fd1..0000000 --- a/src/ast/modifierNodes.js +++ /dev/null @@ -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; -} diff --git a/src/ast/nodes/ArrayExpression.js b/src/ast/nodes/ArrayExpression.js new file mode 100644 index 0000000..ff60919 --- /dev/null +++ b/src/ast/nodes/ArrayExpression.js @@ -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 ); + } +} diff --git a/src/ast/nodes/ArrowFunctionExpression.js b/src/ast/nodes/ArrowFunctionExpression.js new file mode 100644 index 0000000..8d9a3d2 --- /dev/null +++ b/src/ast/nodes/ArrowFunctionExpression.js @@ -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 ); + } +} diff --git a/src/ast/nodes/AssignmentExpression.js b/src/ast/nodes/AssignmentExpression.js new file mode 100644 index 0000000..f1fbcf3 --- /dev/null +++ b/src/ast/nodes/AssignmentExpression.js @@ -0,0 +1,47 @@ +import Node from '../Node.js'; +import disallowIllegalReassignment from './shared/disallowIllegalReassignment.js'; +import isUsedByBundle from './shared/isUsedByBundle.js'; +import { NUMBER, STRING } from '../values.js'; + +export default class AssignmentExpression extends Node { + bind ( scope ) { + let subject = this.left; + while ( this.left.type === 'ParenthesizedExpression' ) subject = subject.expression; + + 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; + + this.module.bundle.dependentExpressions.push( this ); + super.initialise( scope ); + } + + isUsedByBundle () { + return isUsedByBundle( this.scope, this.subject ); + } +} diff --git a/src/ast/nodes/BinaryExpression.js b/src/ast/nodes/BinaryExpression.js new file mode 100644 index 0000000..da3d4da --- /dev/null +++ b/src/ast/nodes/BinaryExpression.js @@ -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 ); + } +} diff --git a/src/ast/nodes/BlockStatement.js b/src/ast/nodes/BlockStatement.js new file mode 100644 index 0000000..78e6e67 --- /dev/null +++ b/src/ast/nodes/BlockStatement.js @@ -0,0 +1,49 @@ +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; + } + } +} diff --git a/src/ast/nodes/CallExpression.js b/src/ast/nodes/CallExpression.js new file mode 100644 index 0000000..8fafc91 --- /dev/null +++ b/src/ast/nodes/CallExpression.js @@ -0,0 +1,40 @@ +import getLocation from '../../utils/getLocation.js'; +import error from '../../utils/error.js'; +import Node from '../Node.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 ) { + this.module.bundle.dependentExpressions.push( this ); + super.initialise( scope ); + } + + isUsedByBundle () { + return this.hasEffects( this.findScope() ); + } +} diff --git a/src/ast/nodes/ClassDeclaration.js b/src/ast/nodes/ClassDeclaration.js new file mode 100644 index 0000000..f1a1daa --- /dev/null +++ b/src/ast/nodes/ClassDeclaration.js @@ -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 ); + } + } +} diff --git a/src/ast/nodes/ClassExpression.js b/src/ast/nodes/ClassExpression.js new file mode 100644 index 0000000..63f1399 --- /dev/null +++ b/src/ast/nodes/ClassExpression.js @@ -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 ); + } +} diff --git a/src/ast/nodes/ConditionalExpression.js b/src/ast/nodes/ConditionalExpression.js new file mode 100644 index 0000000..9c7b444 --- /dev/null +++ b/src/ast/nodes/ConditionalExpression.js @@ -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 ); + } + } + } +} diff --git a/src/ast/nodes/EmptyStatement.js b/src/ast/nodes/EmptyStatement.js new file mode 100644 index 0000000..5f7309c --- /dev/null +++ b/src/ast/nodes/EmptyStatement.js @@ -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 ); + } + } +} diff --git a/src/ast/nodes/ExportAllDeclaration.js b/src/ast/nodes/ExportAllDeclaration.js new file mode 100644 index 0000000..a0f5308 --- /dev/null +++ b/src/ast/nodes/ExportAllDeclaration.js @@ -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 ); + } +} diff --git a/src/ast/nodes/ExportDefaultDeclaration.js b/src/ast/nodes/ExportDefaultDeclaration.js new file mode 100644 index 0000000..716ffcf --- /dev/null +++ b/src/ast/nodes/ExportDefaultDeclaration.js @@ -0,0 +1,96 @@ +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 ); + + if ( this.shouldInclude ) { + if ( this.activated ) { + if ( functionOrClassDeclaration.test( this.declaration.type ) ) { + if ( this.declaration.id ) { + code.remove( this.start, this.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, this.declaration.start, `${this.module.bundle.varOrConst} ${name} = ` ); + } + } + } else { + // remove `var foo` from `var foo = bar()`, if `foo` is unused + code.remove( this.start, this.declaration.start ); + } + + super.render( code, es ); + } else { + if ( treeshake ) { + if ( functionOrClassDeclaration.test( this.declaration.type ) && !this.declaration.activated ) { + code.remove( this.leadingCommentStart || this.start, this.next || this.end ); + } else { + const hasEffects = this.declaration.hasEffects( this.module.scope ); + code.remove( this.start, hasEffects ? this.declaration.start : this.next || this.end ); + } + } else { + code.overwrite( this.start, this.declaration.start, `${this.module.bundle.varOrConst} ${name} = ` ); + } + // code.remove( this.start, this.next || this.end ); + } + } + + run ( scope ) { + this.shouldInclude = true; + super.run( scope ); + } +} diff --git a/src/ast/nodes/ExportNamedDeclaration.js b/src/ast/nodes/ExportNamedDeclaration.js new file mode 100644 index 0000000..b815dd6 --- /dev/null +++ b/src/ast/nodes/ExportNamedDeclaration.js @@ -0,0 +1,25 @@ +import Node from '../Node.js'; + +export default class ExportNamedDeclaration extends Node { + initialise ( scope ) { + this.isExportDeclaration = true; + 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 { + code.remove( this.leadingCommentStart || this.start, this.next || this.end ); + } + } +} diff --git a/src/ast/nodes/ExpressionStatement.js b/src/ast/nodes/ExpressionStatement.js new file mode 100644 index 0000000..237441a --- /dev/null +++ b/src/ast/nodes/ExpressionStatement.js @@ -0,0 +1,5 @@ +import Statement from './shared/Statement.js'; + +export default class ExpressionStatement extends Statement { + +} diff --git a/src/ast/nodes/ForInStatement.js b/src/ast/nodes/ForInStatement.js new file mode 100644 index 0000000..1872b96 --- /dev/null +++ b/src/ast/nodes/ForInStatement.js @@ -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 ); + } +} diff --git a/src/ast/nodes/ForOfStatement.js b/src/ast/nodes/ForOfStatement.js new file mode 100644 index 0000000..a5225f8 --- /dev/null +++ b/src/ast/nodes/ForOfStatement.js @@ -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 ); + } +} diff --git a/src/ast/nodes/ForStatement.js b/src/ast/nodes/ForStatement.js new file mode 100644 index 0000000..11e98e5 --- /dev/null +++ b/src/ast/nodes/ForStatement.js @@ -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 ); + } +} diff --git a/src/ast/nodes/FunctionDeclaration.js b/src/ast/nodes/FunctionDeclaration.js new file mode 100644 index 0000000..dffed95 --- /dev/null +++ b/src/ast/nodes/FunctionDeclaration.js @@ -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 ); + } + } +} diff --git a/src/ast/nodes/FunctionExpression.js b/src/ast/nodes/FunctionExpression.js new file mode 100644 index 0000000..4d170af --- /dev/null +++ b/src/ast/nodes/FunctionExpression.js @@ -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(); + } +} diff --git a/src/ast/nodes/Identifier.js b/src/ast/nodes/Identifier.js new file mode 100644 index 0000000..836462b --- /dev/null +++ b/src/ast/nodes/Identifier.js @@ -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(); + } +} diff --git a/src/ast/nodes/IfStatement.js b/src/ast/nodes/IfStatement.js new file mode 100644 index 0000000..9e6f0fc --- /dev/null +++ b/src/ast/nodes/IfStatement.js @@ -0,0 +1,55 @@ +import Statement from './shared/Statement.js'; +import { UNKNOWN } from '../values.js'; + +// 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 { + super.render( code, es ); + } + } +} diff --git a/src/ast/nodes/ImportDeclaration.js b/src/ast/nodes/ImportDeclaration.js new file mode 100644 index 0000000..0edc68b --- /dev/null +++ b/src/ast/nodes/ImportDeclaration.js @@ -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 ); + } +} diff --git a/src/ast/nodes/Literal.js b/src/ast/nodes/Literal.js new file mode 100644 index 0000000..3b54f14 --- /dev/null +++ b/src/ast/nodes/Literal.js @@ -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 ]); + } + } +} diff --git a/src/ast/nodes/MemberExpression.js b/src/ast/nodes/MemberExpression.js new file mode 100644 index 0000000..ea373cd --- /dev/null +++ b/src/ast/nodes/MemberExpression.js @@ -0,0 +1,74 @@ +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 part = keypath.parts[0]; + declaration = declaration.module.traceExport( part.name ); + + if ( !declaration ) { + this.module.bundle.onwarn( `Export '${part.name}' is not defined by '${this.module.id}'` ); + break; + } + + 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 ); + } + + super.render( code, es ); + } + + run ( scope ) { + if ( this.declaration ) this.declaration.activate(); + super.run( scope ); + } +} diff --git a/src/ast/nodes/NewExpression.js b/src/ast/nodes/NewExpression.js new file mode 100644 index 0000000..8bfbb33 --- /dev/null +++ b/src/ast/nodes/NewExpression.js @@ -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 ); + } +} diff --git a/src/ast/nodes/ObjectExpression.js b/src/ast/nodes/ObjectExpression.js new file mode 100644 index 0000000..724cb00 --- /dev/null +++ b/src/ast/nodes/ObjectExpression.js @@ -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 ); + } +} diff --git a/src/ast/nodes/ParenthesizedExpression.js b/src/ast/nodes/ParenthesizedExpression.js new file mode 100644 index 0000000..7037804 --- /dev/null +++ b/src/ast/nodes/ParenthesizedExpression.js @@ -0,0 +1,11 @@ +import Node from '../Node.js'; + +export default class ParenthesizedExpression extends Node { + getPossibleValues ( values ) { + return this.expression.getPossibleValues( values ); + } + + getValue () { + return this.expression.getValue(); + } +} diff --git a/src/ast/nodes/ReturnStatement.js b/src/ast/nodes/ReturnStatement.js new file mode 100644 index 0000000..bff5ae1 --- /dev/null +++ b/src/ast/nodes/ReturnStatement.js @@ -0,0 +1,7 @@ +import Node from '../Node.js'; + +export default class ReturnStatement extends Node { + // hasEffects () { + // return true; + // } +} diff --git a/src/ast/nodes/TemplateLiteral.js b/src/ast/nodes/TemplateLiteral.js new file mode 100644 index 0000000..b25ef48 --- /dev/null +++ b/src/ast/nodes/TemplateLiteral.js @@ -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 ); + } +} diff --git a/src/ast/nodes/ThisExpression.js b/src/ast/nodes/ThisExpression.js new file mode 100644 index 0000000..e614194 --- /dev/null +++ b/src/ast/nodes/ThisExpression.js @@ -0,0 +1,20 @@ +import Node from '../Node.js'; + +export default class ThisExpression extends Node { + initialise ( scope ) { + const lexicalBoundary = scope.findLexicalBoundary(); + + if ( lexicalBoundary.isModuleScope ) { + this.alias = this.module.bundle.context; + if ( this.alias === 'undefined' ) { + this.module.bundle.onwarn( 'The `this` keyword is equivalent to `undefined` at the top level of an ES module, and has been rewritten' ); + } + } + } + + render ( code ) { + if ( this.alias ) { + code.overwrite( this.start, this.end, this.alias, true ); + } + } +} diff --git a/src/ast/nodes/ThrowStatement.js b/src/ast/nodes/ThrowStatement.js new file mode 100644 index 0000000..4e9047b --- /dev/null +++ b/src/ast/nodes/ThrowStatement.js @@ -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... + } +} diff --git a/src/ast/nodes/UnaryExpression.js b/src/ast/nodes/UnaryExpression.js new file mode 100644 index 0000000..9be1c2c --- /dev/null +++ b/src/ast/nodes/UnaryExpression.js @@ -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 ); + } +} diff --git a/src/ast/nodes/UpdateExpression.js b/src/ast/nodes/UpdateExpression.js new file mode 100644 index 0000000..656da69 --- /dev/null +++ b/src/ast/nodes/UpdateExpression.js @@ -0,0 +1,40 @@ +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 ) { + let subject = this.argument; + while ( this.argument.type === 'ParenthesizedExpression' ) subject = subject.expression; + + 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 ); + } +} diff --git a/src/ast/nodes/VariableDeclaration.js b/src/ast/nodes/VariableDeclaration.js new file mode 100644 index 0000000..e1be1a0 --- /dev/null +++ b/src/ast/nodes/VariableDeclaration.js @@ -0,0 +1,100 @@ +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 if ( this.end > c ) { + const hasSemicolon = code.original[ this.end - 1 ] === ';'; + code.overwrite( c, this.end, hasSemicolon ? ';' : '' ); + } + } +} diff --git a/src/ast/nodes/VariableDeclarator.js b/src/ast/nodes/VariableDeclarator.js new file mode 100644 index 0000000..dc35bff --- /dev/null +++ b/src/ast/nodes/VariableDeclarator.js @@ -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 ); + } +} diff --git a/src/ast/nodes/index.js b/src/ast/nodes/index.js new file mode 100644 index 0000000..94d2f4c --- /dev/null +++ b/src/ast/nodes/index.js @@ -0,0 +1,78 @@ +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 ParenthesizedExpression from './ParenthesizedExpression.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, + ParenthesizedExpression, + ReturnStatement, + SwitchStatement: Statement, + TemplateLiteral, + ThisExpression, + ThrowStatement, + TryStatement: Statement, + UnaryExpression, + UpdateExpression, + VariableDeclarator, + VariableDeclaration, + WhileStatement: Statement +}; diff --git a/src/ast/nodes/shared/Statement.js b/src/ast/nodes/shared/Statement.js new file mode 100644 index 0000000..5667652 --- /dev/null +++ b/src/ast/nodes/shared/Statement.js @@ -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 ); + } +} diff --git a/src/ast/nodes/shared/assignTo.js b/src/ast/nodes/shared/assignTo.js new file mode 100644 index 0000000..03512e1 --- /dev/null +++ b/src/ast/nodes/shared/assignTo.js @@ -0,0 +1,29 @@ +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 { + while ( node.type === 'ParenthesizedExpression' ) node = node.expression; + + 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 ); + } + } + } + } +} diff --git a/src/ast/nodes/shared/callHasEffects.js b/src/ast/nodes/shared/callHasEffects.js new file mode 100644 index 0000000..cf8d964 --- /dev/null +++ b/src/ast/nodes/shared/callHasEffects.js @@ -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.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; +} diff --git a/src/ast/nodes/shared/disallowIllegalReassignment.js b/src/ast/nodes/shared/disallowIllegalReassignment.js new file mode 100644 index 0000000..a40969f --- /dev/null +++ b/src/ast/nodes/shared/disallowIllegalReassignment.js @@ -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 ) + }); + } + } +} diff --git a/src/ast/nodes/shared/isUsedByBundle.js b/src/ast/nodes/shared/isUsedByBundle.js new file mode 100644 index 0000000..07e315f --- /dev/null +++ b/src/ast/nodes/shared/isUsedByBundle.js @@ -0,0 +1,40 @@ +import { UNKNOWN } from '../../values.js'; + +export default function isUsedByBundle ( scope, node ) { + while ( node.type === 'ParenthesizedExpression' ) node = node.expression; + + // 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; +} diff --git a/src/utils/pureFunctions.js b/src/ast/nodes/shared/pureFunctions.js similarity index 100% rename from src/utils/pureFunctions.js rename to src/ast/nodes/shared/pureFunctions.js diff --git a/src/ast/scopes/BundleScope.js b/src/ast/scopes/BundleScope.js new file mode 100644 index 0000000..6c0f0ea --- /dev/null +++ b/src/ast/scopes/BundleScope.js @@ -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 ]; + } +} diff --git a/src/ast/scopes/ModuleScope.js b/src/ast/scopes/ModuleScope.js new file mode 100644 index 0000000..5a18d6f --- /dev/null +++ b/src/ast/scopes/ModuleScope.js @@ -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; + } +} diff --git a/src/ast/scopes/Scope.js b/src/ast/scopes/Scope.js new file mode 100644 index 0000000..7bb1671 --- /dev/null +++ b/src/ast/scopes/Scope.js @@ -0,0 +1,98 @@ +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 ) { + options = 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(); + } +} diff --git a/src/ast/extractNames.js b/src/ast/utils/extractNames.js similarity index 100% rename from src/ast/extractNames.js rename to src/ast/utils/extractNames.js diff --git a/src/ast/flatten.js b/src/ast/utils/flatten.js similarity index 100% rename from src/ast/flatten.js rename to src/ast/utils/flatten.js diff --git a/src/ast/isReference.js b/src/ast/utils/isReference.js similarity index 100% rename from src/ast/isReference.js rename to src/ast/utils/isReference.js diff --git a/src/ast/values.js b/src/ast/values.js new file mode 100644 index 0000000..dadd74a --- /dev/null +++ b/src/ast/values.js @@ -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]]' }; diff --git a/src/finalisers/amd.js b/src/finalisers/amd.js index c04c88b..96626e2 100644 --- a/src/finalisers/amd.js +++ b/src/finalisers/amd.js @@ -3,7 +3,7 @@ import getInteropBlock from './shared/getInteropBlock.js'; import getExportBlock from './shared/getExportBlock.js'; import esModuleExport from './shared/esModuleExport.js'; -export default function amd ( bundle, magicString, { exportMode, indentString }, options ) { +export default function amd ( bundle, magicString, { exportMode, indentString, intro }, options ) { const deps = bundle.externalModules.map( quotePath ); const args = bundle.externalModules.map( getName ); @@ -17,12 +17,14 @@ export default function amd ( bundle, magicString, { exportMode, indentString }, ( deps.length ? `[${deps.join( ', ' )}], ` : `` ); const useStrict = options.useStrict !== false ? ` 'use strict';` : ``; - const intro = `define(${params}function (${args.join( ', ' )}) {${useStrict}\n\n`; + const wrapperStart = `define(${params}function (${args.join( ', ' )}) {${useStrict}\n\n`; // var foo__default = 'default' in foo ? foo['default'] : foo; - const interopBlock = getInteropBlock( bundle ); + const interopBlock = getInteropBlock( bundle, options ); if ( interopBlock ) magicString.prepend( interopBlock + '\n\n' ); + if ( intro ) magicString.prepend( intro ); + const exportBlock = getExportBlock( bundle.entryModule, exportMode ); if ( exportBlock ) magicString.append( '\n\n' + exportBlock ); if ( exportMode === 'named' ) magicString.append( `\n\n${esModuleExport}` ); @@ -31,5 +33,5 @@ export default function amd ( bundle, magicString, { exportMode, indentString }, return magicString .indent( indentString ) .append( '\n\n});' ) - .prepend( intro ); + .prepend( wrapperStart ); } diff --git a/src/finalisers/cjs.js b/src/finalisers/cjs.js index b12d621..5af4df7 100644 --- a/src/finalisers/cjs.js +++ b/src/finalisers/cjs.js @@ -1,18 +1,19 @@ import getExportBlock from './shared/getExportBlock.js'; import esModuleExport from './shared/esModuleExport.js'; -export default function cjs ( bundle, magicString, { exportMode }, options ) { - let intro = ( options.useStrict === false ? `` : `'use strict';\n\n` ) + - ( exportMode === 'named' ? `${esModuleExport}\n\n` : '' ); +export default function cjs ( bundle, magicString, { exportMode, intro }, options ) { + intro = ( options.useStrict === false ? intro : `'use strict';\n\n${intro}` ) + + ( exportMode === 'named' ? `${esModuleExport}\n\n` : '' ); let needsInterop = false; const varOrConst = bundle.varOrConst; + const interop = options.interop !== false; // TODO handle empty imports, once they're supported const importBlock = bundle.externalModules .map( module => { - if ( module.declarations.default ) { + if ( interop && module.declarations.default ) { if ( module.exportsNamespace ) { return `${varOrConst} ${module.name} = require('${module.path}');` + `\n${varOrConst} ${module.name}__default = ${module.name}['default'];`; diff --git a/src/finalisers/es.js b/src/finalisers/es.js index a788083..8251fe3 100644 --- a/src/finalisers/es.js +++ b/src/finalisers/es.js @@ -4,7 +4,7 @@ function notDefault ( name ) { return name !== 'default'; } -export default function es ( bundle, magicString, config, options ) { +export default function es ( bundle, magicString, { intro }, options ) { const importBlock = bundle.externalModules .map( module => { const specifiers = []; @@ -26,8 +26,8 @@ export default function es ( bundle, magicString, config, options ) { } } - const namespaceSpecifier = module.declarations['*'] ? `* as ${module.name}` : null; - const namedSpecifier = importedNames.length ? `{ ${importedNames.join( ', ' )} }` : null; + const namespaceSpecifier = module.declarations['*'] ? `* as ${module.name}` : null; // TODO prevent unnecessary namespace import, e.g form/external-imports + const namedSpecifier = importedNames.length ? `{ ${importedNames.sort().join( ', ' )} }` : null; if ( namespaceSpecifier && namedSpecifier ) { // Namespace and named specifiers cannot be combined. @@ -49,15 +49,14 @@ export default function es ( bundle, magicString, config, options ) { }) .join( '\n' ); - if ( importBlock ) { - magicString.prepend( importBlock + '\n\n' ); - } + if ( importBlock ) intro += importBlock + '\n\n'; + if ( intro ) magicString.prepend( intro ); const module = bundle.entryModule; const specifiers = module.getExports().filter( notDefault ).map( name => { const declaration = module.traceExport( name ); - const rendered = declaration.render( true ); + const rendered = declaration.getName( true ); return rendered === name ? name : @@ -68,7 +67,7 @@ export default function es ( bundle, magicString, config, options ) { const defaultExport = module.exports.default || module.reexports.default; if ( defaultExport ) { - exportBlock += `export default ${module.traceExport( 'default' ).render( true )};`; + exportBlock += `export default ${module.traceExport( 'default' ).getName( true )};`; } if ( exportBlock ) magicString.append( '\n\n' + exportBlock.trim() ); diff --git a/src/finalisers/iife.js b/src/finalisers/iife.js index 319bd90..8120594 100644 --- a/src/finalisers/iife.js +++ b/src/finalisers/iife.js @@ -16,7 +16,7 @@ function setupNamespace ( keypath ) { .join( '\n' ) + '\n'; } -export default function iife ( bundle, magicString, { exportMode, indentString }, options ) { +export default function iife ( bundle, magicString, { exportMode, indentString, intro }, options ) { const globalNameMaker = getGlobalNameMaker( options.globals || blank(), bundle.onwarn ); const name = options.moduleName; @@ -35,29 +35,31 @@ export default function iife ( bundle, magicString, { exportMode, indentString } args.unshift( 'exports' ); } - const useStrict = options.useStrict !== false ? `'use strict';` : ``; + const useStrict = options.useStrict !== false ? `${indentString}'use strict';\n\n` : ``; - let intro = `(function (${args}) {\n`; - const outro = `\n\n}(${dependencies}));`; + let wrapperIntro = `(function (${args}) {\n${useStrict}`; + const wrapperOutro = `\n\n}(${dependencies}));`; if ( exportMode === 'default' ) { - intro = ( isNamespaced ? `this.` : `${bundle.varOrConst} ` ) + `${name} = ${intro}`; + wrapperIntro = ( isNamespaced ? `this.` : `${bundle.varOrConst} ` ) + `${name} = ${wrapperIntro}`; } if ( isNamespaced ) { - intro = setupNamespace( name ) + intro; + wrapperIntro = setupNamespace( name ) + wrapperIntro; } // var foo__default = 'default' in foo ? foo['default'] : foo; - const interopBlock = getInteropBlock( bundle ); + const interopBlock = getInteropBlock( bundle, options ); if ( interopBlock ) magicString.prepend( interopBlock + '\n\n' ); - if ( useStrict ) magicString.prepend( useStrict + '\n\n' ); + + if ( intro ) magicString.prepend( intro ); + const exportBlock = getExportBlock( bundle.entryModule, exportMode ); if ( exportBlock ) magicString.append( '\n\n' + exportBlock ); if ( options.outro ) magicString.append( `\n${options.outro}` ); return magicString .indent( indentString ) - .prepend( intro ) - .append( outro ); + .prepend( wrapperIntro ) + .append( wrapperOutro ); } diff --git a/src/finalisers/shared/getExportBlock.js b/src/finalisers/shared/getExportBlock.js index 972da59..5a7f34f 100644 --- a/src/finalisers/shared/getExportBlock.js +++ b/src/finalisers/shared/getExportBlock.js @@ -1,6 +1,6 @@ export default function getExportBlock ( entryModule, exportMode, mechanism = 'return' ) { if ( exportMode === 'default' ) { - return `${mechanism} ${entryModule.traceExport( 'default' ).render( false )};`; + return `${mechanism} ${entryModule.traceExport( 'default' ).getName( false )};`; } return entryModule.getExports() @@ -9,7 +9,9 @@ export default function getExportBlock ( entryModule, exportMode, mechanism = 'r const declaration = entryModule.traceExport( name ); const lhs = `exports${prop}`; - const rhs = declaration.render( false ); + const rhs = declaration ? + declaration.getName( false ) : + name; // exporting a global // prevent `exports.count = exports.count` if ( lhs === rhs ) return null; diff --git a/src/finalisers/shared/getInteropBlock.js b/src/finalisers/shared/getInteropBlock.js index 67a26d7..6ecdba0 100644 --- a/src/finalisers/shared/getInteropBlock.js +++ b/src/finalisers/shared/getInteropBlock.js @@ -1,7 +1,7 @@ -export default function getInteropBlock ( bundle ) { +export default function getInteropBlock ( bundle, options ) { return bundle.externalModules .map( module => { - if ( !module.declarations.default ) return null; + if ( !module.declarations.default || options.interop === false ) return null; if ( module.exportsNamespace ) { return `${bundle.varOrConst} ${module.name}__default = ${module.name}['default'];`; diff --git a/src/finalisers/umd.js b/src/finalisers/umd.js index 11631f5..f2be2a2 100644 --- a/src/finalisers/umd.js +++ b/src/finalisers/umd.js @@ -16,7 +16,9 @@ function setupNamespace ( name ) { .join( ', ' ); } -export default function umd ( bundle, magicString, { exportMode, indentString }, options ) { +const wrapperOutro = '\n\n})));'; + +export default function umd ( bundle, magicString, { exportMode, indentString, intro }, options ) { if ( exportMode !== 'none' && !options.moduleName ) { throw new Error( 'You must supply options.moduleName for UMD bundles' ); } @@ -54,7 +56,7 @@ export default function umd ( bundle, magicString, { exportMode, indentString }, exports.noConflict = function() { global.${options.moduleName} = current; return exports; }; })()` : `(${defaultExport}factory(${globalDeps}))`; - const intro = + const wrapperIntro = `(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? ${cjsExport}factory(${cjsDeps.join( ', ' )}) : typeof define === 'function' && define.amd ? define(${amdParams}factory) : @@ -64,9 +66,11 @@ export default function umd ( bundle, magicString, { exportMode, indentString }, `.replace( /^\t\t/gm, '' ).replace( /^\t/gm, magicString.getIndentString() ); // var foo__default = 'default' in foo ? foo['default'] : foo; - const interopBlock = getInteropBlock( bundle ); + const interopBlock = getInteropBlock( bundle, options ); if ( interopBlock ) magicString.prepend( interopBlock + '\n\n' ); + if ( intro ) magicString.prepend( intro ); + const exportBlock = getExportBlock( bundle.entryModule, exportMode ); if ( exportBlock ) magicString.append( '\n\n' + exportBlock ); if ( exportMode === 'named' ) magicString.append( `\n\n${esModuleExport}` ); @@ -75,6 +79,6 @@ export default function umd ( bundle, magicString, { exportMode, indentString }, return magicString .trim() .indent( indentString ) - .append( '\n\n})));' ) - .prepend( intro ); + .append( wrapperOutro ) + .prepend( wrapperIntro ); } diff --git a/src/rollup.js b/src/rollup.js index 4c1a076..0868960 100644 --- a/src/rollup.js +++ b/src/rollup.js @@ -1,3 +1,4 @@ +import { timeStart, timeEnd, flushTime } from './utils/flushTime.js'; import { basename } from './utils/path.js'; import { writeFile } from './utils/fs.js'; import { assign, keys } from './utils/object.js'; @@ -21,6 +22,7 @@ const ALLOWED_KEYS = [ 'format', 'globals', 'indent', + 'interop', 'intro', 'moduleId', 'moduleName', @@ -54,10 +56,18 @@ export function rollup ( options ) { const bundle = new Bundle( options ); + timeStart( '--BUILD--' ); + return bundle.build().then( () => { + timeEnd( '--BUILD--' ); + function generate ( options ) { + timeStart( '--GENERATE--' ); + const rendered = bundle.render( options ); + timeEnd( '--GENERATE--' ); + bundle.plugins.forEach( plugin => { if ( plugin.ongenerate ) { plugin.ongenerate( assign({ @@ -66,6 +76,8 @@ export function rollup ( options ) { } }); + flushTime(); + return rendered; } @@ -96,7 +108,7 @@ export function rollup ( options ) { promises.push( writeFile( dest + '.map', map.toString() ) ); } - code += `\n//# ${SOURCEMAPPING_URL}=${url}\n`; + code += `//# ${SOURCEMAPPING_URL}=${url}\n`; } promises.push( writeFile( dest, code ) ); diff --git a/src/utils/defaults.js b/src/utils/defaults.js index 3073e81..c746a7f 100644 --- a/src/utils/defaults.js +++ b/src/utils/defaults.js @@ -1,5 +1,5 @@ -import { isFile, readFileSync } from './fs.js'; -import { dirname, isAbsolute, resolve } from './path.js'; +import { isFile, readdirSync, readFileSync } from './fs.js'; +import { basename, dirname, isAbsolute, resolve } from './path.js'; import { blank } from './object.js'; export function load ( id ) { @@ -7,10 +7,15 @@ export function load ( id ) { } function addJsExtensionIfNecessary ( file ) { - if ( isFile( file ) ) return file; - - file += '.js'; - if ( isFile( file ) ) return file; + try { + const name = basename( file ); + const files = readdirSync( dirname( file ) ); + + if ( ~files.indexOf( name ) && isFile( file ) ) return file; + if ( ~files.indexOf( `${name}.js` ) && isFile( `${file}.js` ) ) return `${file}.js`; + } catch ( err ) { + // noop + } return null; } diff --git a/src/utils/flushTime.js b/src/utils/flushTime.js new file mode 100644 index 0000000..9f32e45 --- /dev/null +++ b/src/utils/flushTime.js @@ -0,0 +1,55 @@ +const DEBUG = false; +const map = new Map; + +let timeStartHelper; +let timeEndHelper; + +if ( typeof process === 'undefined' ) { + timeStartHelper = function timeStartHelper () { + return window.performance.now(); + }; + + timeEndHelper = function timeEndHelper ( previous ) { + return window.performance.now() - previous; + }; +} else { + timeStartHelper = function timeStartHelper () { + return process.hrtime(); + }; + + timeEndHelper = function timeEndHelper ( previous ) { + const hrtime = process.hrtime( previous ); + return hrtime[0] * 1e3 + Math.floor( hrtime[1] / 1e6 ); + }; +} + +export function timeStart ( label ) { + if ( !map.has( label ) ) { + map.set( label, { + time: 0 + }); + } + map.get( label ).start = timeStartHelper(); +} + +export function timeEnd ( label ) { + if ( map.has( label ) ) { + const item = map.get( label ); + item.time += timeEndHelper( item.start ); + } +} + +export function flushTime ( log = defaultLog ) { + for ( const item of map.entries() ) { + log( item[0], item[1].time ); + } + map.clear(); +} + +function defaultLog ( label, time ) { + if ( DEBUG ) { + /* eslint-disable no-console */ + console.info( '%dms: %s', time, label ); + /* eslint-enable no-console */ + } +} diff --git a/src/utils/object.js b/src/utils/object.js index 4234de3..947c8e1 100644 --- a/src/utils/object.js +++ b/src/utils/object.js @@ -17,3 +17,24 @@ export function assign ( target, ...sources ) { return target; } + +const isArray = Array.isArray; + +// used for cloning ASTs. Not for use with cyclical structures! +export function deepClone ( obj ) { + if ( !obj ) return obj; + if ( typeof obj !== 'object' ) return obj; + + if ( isArray( obj ) ) { + const clone = new Array( obj.length ); + for ( let i = 0; i < obj.length; i += 1 ) clone[i] = deepClone( obj[i] ); + return clone; + } + + const clone = {}; + for ( const key in obj ) { + clone[ key ] = deepClone( obj[ key ] ); + } + + return clone; +} diff --git a/src/utils/path.js b/src/utils/path.js index b787028..d2ce64d 100644 --- a/src/utils/path.js +++ b/src/utils/path.js @@ -13,4 +13,4 @@ export function normalize ( path ) { return path.replace( /\\/g, '/' ); } -export * from 'path'; +export { basename, dirname, extname, relative, resolve } from 'path'; diff --git a/src/utils/run.js b/src/utils/run.js deleted file mode 100644 index 23b0493..0000000 --- a/src/utils/run.js +++ /dev/null @@ -1,119 +0,0 @@ -import { walk } from 'estree-walker'; -import modifierNodes, { isModifierNode } from '../ast/modifierNodes.js'; -import isReference from '../ast/isReference.js'; -import flatten from '../ast/flatten'; -import pureFunctions from './pureFunctions.js'; -import getLocation from './getLocation.js'; -import error from './error.js'; - -function call ( callee, scope, statement, strongDependencies ) { - while ( callee.type === 'ParenthesizedExpression' ) callee = callee.expression; - - if ( callee.type === 'Identifier' ) { - const declaration = scope.findDeclaration( callee.name ) || - statement.module.trace( callee.name ); - - if ( declaration ) { - if ( declaration.isNamespace ) { - error({ - message: `Cannot call a namespace ('${callee.name}')`, - file: statement.module.id, - pos: callee.start, - loc: getLocation( statement.module.code, callee.start ) - }); - } - - return declaration.run( strongDependencies ); - } - - return !pureFunctions[ callee.name ]; - } - - if ( /FunctionExpression/.test( callee.type ) ) { - return run( callee.body, scope, statement, strongDependencies ); - } - - if ( callee.type === 'MemberExpression' ) { - const flattened = flatten( callee ); - - if ( flattened ) { - // if we're calling e.g. Object.keys(thing), there are no side-effects - // TODO make pureFunctions configurable - const declaration = scope.findDeclaration( flattened.name ) || statement.module.trace( flattened.name ); - - return ( !!declaration || !pureFunctions[ flattened.keypath ] ); - } - } - - // complex case like `( a ? b : c )()` or foo[bar].baz()` - // – err on the side of caution - return true; -} - -export default function run ( node, scope, statement, strongDependencies, force ) { - let hasSideEffect = false; - - walk( node, { - enter ( node, parent ) { - if ( !force && /Function/.test( node.type ) ) return this.skip(); - - if ( node._scope ) scope = node._scope; - - if ( isReference( node, parent ) ) { - const flattened = flatten( node ); - - if ( flattened.name === 'arguments' ) { - hasSideEffect = true; - } - - else if ( !scope.contains( flattened.name ) ) { - const declaration = statement.module.trace( flattened.name ); - if ( declaration && !declaration.isExternal ) { - const module = declaration.module || declaration.statement.module; // TODO is this right? - if ( !module.isExternal && !~strongDependencies.indexOf( module ) ) strongDependencies.push( module ); - } - } - } - - else if ( node.type === 'DebuggerStatement' ) { - hasSideEffect = true; - } - - else if ( node.type === 'ThrowStatement' ) { - // we only care about errors thrown at the top level, otherwise - // any function with error checking gets included if called - if ( scope.isTopLevel ) hasSideEffect = true; - } - - else if ( node.type === 'CallExpression' || node.type === 'NewExpression' ) { - if ( call( node.callee, scope, statement, strongDependencies ) ) { - hasSideEffect = true; - } - } - - else if ( isModifierNode( node ) ) { - let subject = node[ modifierNodes[ node.type ] ]; - while ( subject.type === 'MemberExpression' ) subject = subject.object; - - let declaration = scope.findDeclaration( subject.name ); - - if ( declaration ) { - if ( declaration.isParam ) hasSideEffect = true; - } else if ( !scope.isTopLevel ) { - hasSideEffect = true; - } else { - declaration = statement.module.trace( subject.name ); - - if ( !declaration || declaration.isExternal || declaration.isUsed || ( declaration.original && declaration.original.isUsed ) ) { - hasSideEffect = true; - } - } - } - }, - leave ( node ) { - if ( node._scope ) scope = scope.parent; - } - }); - - return hasSideEffect; -} diff --git a/src/utils/transformBundle.js b/src/utils/transformBundle.js index 027c7f0..8569ad5 100644 --- a/src/utils/transformBundle.js +++ b/src/utils/transformBundle.js @@ -1,13 +1,13 @@ import { decode } from 'sourcemap-codec'; -export default function transformBundle ( code, plugins, sourceMapChain ) { +export default function transformBundle ( code, plugins, sourceMapChain, options ) { return plugins.reduce( ( code, plugin ) => { if ( !plugin.transformBundle ) return code; let result; try { - result = plugin.transformBundle( code ); + result = plugin.transformBundle( code, { format : options.format } ); } catch ( err ) { err.plugin = plugin.name; err.message = `Error transforming bundle${plugin.name ? ` with '${plugin.name}' plugin` : ''}: ${err.message}`; diff --git a/test/form/assignment-to-exports-class-declaration/_config.js b/test/form/assignment-to-exports-class-declaration/_config.js index 0f3d2ef..f7296e0 100644 --- a/test/form/assignment-to-exports-class-declaration/_config.js +++ b/test/form/assignment-to-exports-class-declaration/_config.js @@ -1,5 +1,5 @@ module.exports = { - description: 'does not rewrite class declaration IDs', + description: 'does not rewrite class expression IDs', options: { moduleName: 'myModule' } diff --git a/test/form/body-less-for-loops/_config.js b/test/form/body-less-for-loops/_config.js new file mode 100644 index 0000000..a748fa0 --- /dev/null +++ b/test/form/body-less-for-loops/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'supports body-less for loops' +}; diff --git a/test/form/body-less-for-loops/_expected/amd.js b/test/form/body-less-for-loops/_expected/amd.js new file mode 100644 index 0000000..464893d --- /dev/null +++ b/test/form/body-less-for-loops/_expected/amd.js @@ -0,0 +1,16 @@ +define(function () { 'use strict'; + + for ( let i = 0; i < 10; i += 1 ) console.log( i ); + for ( const letter of array ) console.log( letter ); + for ( const index in array ) console.log( index ); + + let i; + for ( i = 0; i < 10; i += 1 ) console.log( i ); + + let letter; + for ( letter of array ) console.log( letter ); + + let index; + for ( index in array ) console.log( index ); + +}); \ No newline at end of file diff --git a/test/form/body-less-for-loops/_expected/cjs.js b/test/form/body-less-for-loops/_expected/cjs.js new file mode 100644 index 0000000..f2e4fde --- /dev/null +++ b/test/form/body-less-for-loops/_expected/cjs.js @@ -0,0 +1,14 @@ +'use strict'; + +for ( let i = 0; i < 10; i += 1 ) console.log( i ); +for ( const letter of array ) console.log( letter ); +for ( const index in array ) console.log( index ); + +let i; +for ( i = 0; i < 10; i += 1 ) console.log( i ); + +let letter; +for ( letter of array ) console.log( letter ); + +let index; +for ( index in array ) console.log( index ); \ No newline at end of file diff --git a/test/form/body-less-for-loops/_expected/es.js b/test/form/body-less-for-loops/_expected/es.js new file mode 100644 index 0000000..63ee4e5 --- /dev/null +++ b/test/form/body-less-for-loops/_expected/es.js @@ -0,0 +1,12 @@ +for ( let i = 0; i < 10; i += 1 ) console.log( i ); +for ( const letter of array ) console.log( letter ); +for ( const index in array ) console.log( index ); + +let i; +for ( i = 0; i < 10; i += 1 ) console.log( i ); + +let letter; +for ( letter of array ) console.log( letter ); + +let index; +for ( index in array ) console.log( index ); \ No newline at end of file diff --git a/test/form/body-less-for-loops/_expected/iife.js b/test/form/body-less-for-loops/_expected/iife.js new file mode 100644 index 0000000..2214bd4 --- /dev/null +++ b/test/form/body-less-for-loops/_expected/iife.js @@ -0,0 +1,17 @@ +(function () { + 'use strict'; + + for ( let i = 0; i < 10; i += 1 ) console.log( i ); + for ( const letter of array ) console.log( letter ); + for ( const index in array ) console.log( index ); + + let i; + for ( i = 0; i < 10; i += 1 ) console.log( i ); + + let letter; + for ( letter of array ) console.log( letter ); + + let index; + for ( index in array ) console.log( index ); + +}()); \ No newline at end of file diff --git a/test/form/body-less-for-loops/_expected/umd.js b/test/form/body-less-for-loops/_expected/umd.js new file mode 100644 index 0000000..9840021 --- /dev/null +++ b/test/form/body-less-for-loops/_expected/umd.js @@ -0,0 +1,20 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + for ( let i = 0; i < 10; i += 1 ) console.log( i ); + for ( const letter of array ) console.log( letter ); + for ( const index in array ) console.log( index ); + + let i; + for ( i = 0; i < 10; i += 1 ) console.log( i ); + + let letter; + for ( letter of array ) console.log( letter ); + + let index; + for ( index in array ) console.log( index ); + +}))); \ No newline at end of file diff --git a/test/form/body-less-for-loops/main.js b/test/form/body-less-for-loops/main.js new file mode 100644 index 0000000..3aa7b7e --- /dev/null +++ b/test/form/body-less-for-loops/main.js @@ -0,0 +1,12 @@ +for ( let i = 0; i < 10; i += 1 ) console.log( i ); +for ( const letter of array ) console.log( letter ); +for ( const index in array ) console.log( index ); + +let i; +for ( i = 0; i < 10; i += 1 ) console.log( i ); + +let letter; +for ( letter of array ) console.log( letter ); + +let index; +for ( index in array ) console.log( index ); diff --git a/test/form/duplicated-var-declarations/_config.js b/test/form/duplicated-var-declarations/_config.js new file mode 100644 index 0000000..4c3038c --- /dev/null +++ b/test/form/duplicated-var-declarations/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'does not remove duplicated var declarations (#716)' +}; diff --git a/test/form/duplicated-var-declarations/_expected/amd.js b/test/form/duplicated-var-declarations/_expected/amd.js new file mode 100644 index 0000000..b43e2a4 --- /dev/null +++ b/test/form/duplicated-var-declarations/_expected/amd.js @@ -0,0 +1,17 @@ +define(function () { 'use strict'; + + var a = 1; + var b = 2; + + assert.equal( a, 1 ); + assert.equal( b, 2 ); + + var a = 3; + var b = 4; + var c = 5; + + assert.equal( a, 3 ); + assert.equal( b, 4 ); + assert.equal( c, 5 ); + +}); diff --git a/test/form/duplicated-var-declarations/_expected/cjs.js b/test/form/duplicated-var-declarations/_expected/cjs.js new file mode 100644 index 0000000..e47b61a --- /dev/null +++ b/test/form/duplicated-var-declarations/_expected/cjs.js @@ -0,0 +1,15 @@ +'use strict'; + +var a = 1; +var b = 2; + +assert.equal( a, 1 ); +assert.equal( b, 2 ); + +var a = 3; +var b = 4; +var c = 5; + +assert.equal( a, 3 ); +assert.equal( b, 4 ); +assert.equal( c, 5 ); diff --git a/test/form/duplicated-var-declarations/_expected/es.js b/test/form/duplicated-var-declarations/_expected/es.js new file mode 100644 index 0000000..cacf6d2 --- /dev/null +++ b/test/form/duplicated-var-declarations/_expected/es.js @@ -0,0 +1,13 @@ +var a = 1; +var b = 2; + +assert.equal( a, 1 ); +assert.equal( b, 2 ); + +var a = 3; +var b = 4; +var c = 5; + +assert.equal( a, 3 ); +assert.equal( b, 4 ); +assert.equal( c, 5 ); diff --git a/test/form/duplicated-var-declarations/_expected/iife.js b/test/form/duplicated-var-declarations/_expected/iife.js new file mode 100644 index 0000000..106066a --- /dev/null +++ b/test/form/duplicated-var-declarations/_expected/iife.js @@ -0,0 +1,18 @@ +(function () { + 'use strict'; + + var a = 1; + var b = 2; + + assert.equal( a, 1 ); + assert.equal( b, 2 ); + + var a = 3; + var b = 4; + var c = 5; + + assert.equal( a, 3 ); + assert.equal( b, 4 ); + assert.equal( c, 5 ); + +}()); diff --git a/test/form/duplicated-var-declarations/_expected/umd.js b/test/form/duplicated-var-declarations/_expected/umd.js new file mode 100644 index 0000000..4574799 --- /dev/null +++ b/test/form/duplicated-var-declarations/_expected/umd.js @@ -0,0 +1,21 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + var a = 1; + var b = 2; + + assert.equal( a, 1 ); + assert.equal( b, 2 ); + + var a = 3; + var b = 4; + var c = 5; + + assert.equal( a, 3 ); + assert.equal( b, 4 ); + assert.equal( c, 5 ); + +}))); diff --git a/test/form/duplicated-var-declarations/main.js b/test/form/duplicated-var-declarations/main.js new file mode 100644 index 0000000..1eabb5b --- /dev/null +++ b/test/form/duplicated-var-declarations/main.js @@ -0,0 +1,10 @@ +var a = 1, b = 2; + +assert.equal( a, 1 ); +assert.equal( b, 2 ); + +var a = 3, b = 4, c = 5; + +assert.equal( a, 3 ); +assert.equal( b, 4 ); +assert.equal( c, 5 ); diff --git a/test/form/effect-in-for-of-loop-in-functions/_config.js b/test/form/effect-in-for-of-loop-in-functions/_config.js new file mode 100644 index 0000000..79abe74 --- /dev/null +++ b/test/form/effect-in-for-of-loop-in-functions/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'includes effects in for-of loop (#870)' +} diff --git a/test/form/effect-in-for-of-loop-in-functions/_expected/amd.js b/test/form/effect-in-for-of-loop-in-functions/_expected/amd.js new file mode 100644 index 0000000..a7c1b48 --- /dev/null +++ b/test/form/effect-in-for-of-loop-in-functions/_expected/amd.js @@ -0,0 +1,28 @@ +define(function () { 'use strict'; + + const items = [{}, {}, {}]; + + function a () { + for ( const item of items.children ) { + item.foo = 'a'; + } + } + + a(); + + function c () { + let item; + for ( item of items.children ) { + item.bar = 'c'; + } + } + + c(); + + assert.deepEqual( items, [ + { foo: 'a', bar: 'c' }, + { foo: 'a', bar: 'c' }, + { foo: 'a', bar: 'c' } + ]); + +}); diff --git a/test/form/effect-in-for-of-loop-in-functions/_expected/cjs.js b/test/form/effect-in-for-of-loop-in-functions/_expected/cjs.js new file mode 100644 index 0000000..51c5f3b --- /dev/null +++ b/test/form/effect-in-for-of-loop-in-functions/_expected/cjs.js @@ -0,0 +1,26 @@ +'use strict'; + +const items = [{}, {}, {}]; + +function a () { + for ( const item of items.children ) { + item.foo = 'a'; + } +} + +a(); + +function c () { + let item; + for ( item of items.children ) { + item.bar = 'c'; + } +} + +c(); + +assert.deepEqual( items, [ + { foo: 'a', bar: 'c' }, + { foo: 'a', bar: 'c' }, + { foo: 'a', bar: 'c' } +]); diff --git a/test/form/effect-in-for-of-loop-in-functions/_expected/es.js b/test/form/effect-in-for-of-loop-in-functions/_expected/es.js new file mode 100644 index 0000000..0aafee1 --- /dev/null +++ b/test/form/effect-in-for-of-loop-in-functions/_expected/es.js @@ -0,0 +1,24 @@ +const items = [{}, {}, {}]; + +function a () { + for ( const item of items.children ) { + item.foo = 'a'; + } +} + +a(); + +function c () { + let item; + for ( item of items.children ) { + item.bar = 'c'; + } +} + +c(); + +assert.deepEqual( items, [ + { foo: 'a', bar: 'c' }, + { foo: 'a', bar: 'c' }, + { foo: 'a', bar: 'c' } +]); diff --git a/test/form/effect-in-for-of-loop-in-functions/_expected/iife.js b/test/form/effect-in-for-of-loop-in-functions/_expected/iife.js new file mode 100644 index 0000000..406ab3a --- /dev/null +++ b/test/form/effect-in-for-of-loop-in-functions/_expected/iife.js @@ -0,0 +1,29 @@ +(function () { + 'use strict'; + + const items = [{}, {}, {}]; + + function a () { + for ( const item of items.children ) { + item.foo = 'a'; + } + } + + a(); + + function c () { + let item; + for ( item of items.children ) { + item.bar = 'c'; + } + } + + c(); + + assert.deepEqual( items, [ + { foo: 'a', bar: 'c' }, + { foo: 'a', bar: 'c' }, + { foo: 'a', bar: 'c' } + ]); + +}()); diff --git a/test/form/effect-in-for-of-loop-in-functions/_expected/umd.js b/test/form/effect-in-for-of-loop-in-functions/_expected/umd.js new file mode 100644 index 0000000..33518c0 --- /dev/null +++ b/test/form/effect-in-for-of-loop-in-functions/_expected/umd.js @@ -0,0 +1,32 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + const items = [{}, {}, {}]; + + function a () { + for ( const item of items.children ) { + item.foo = 'a'; + } + } + + a(); + + function c () { + let item; + for ( item of items.children ) { + item.bar = 'c'; + } + } + + c(); + + assert.deepEqual( items, [ + { foo: 'a', bar: 'c' }, + { foo: 'a', bar: 'c' }, + { foo: 'a', bar: 'c' } + ]); + +}))); diff --git a/test/form/effect-in-for-of-loop-in-functions/main.js b/test/form/effect-in-for-of-loop-in-functions/main.js new file mode 100644 index 0000000..7ba1241 --- /dev/null +++ b/test/form/effect-in-for-of-loop-in-functions/main.js @@ -0,0 +1,41 @@ +const items = [{}, {}, {}]; + +function a () { + for ( const item of items.children ) { + item.foo = 'a'; + } +} + +a(); + +function b () { + for ( const item of items.children ) { + // do nothing + } +} + +b(); + +function c () { + let item; + for ( item of items.children ) { + item.bar = 'c'; + } +} + +c(); + +function d () { + let item; + for ( item of items.children ) { + // do nothing + } +} + +d(); + +assert.deepEqual( items, [ + { foo: 'a', bar: 'c' }, + { foo: 'a', bar: 'c' }, + { foo: 'a', bar: 'c' } +]); diff --git a/test/form/effect-in-for-of-loop/_config.js b/test/form/effect-in-for-of-loop/_config.js new file mode 100644 index 0000000..79abe74 --- /dev/null +++ b/test/form/effect-in-for-of-loop/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'includes effects in for-of loop (#870)' +} diff --git a/test/form/effect-in-for-of-loop/_expected/amd.js b/test/form/effect-in-for-of-loop/_expected/amd.js new file mode 100644 index 0000000..c252e26 --- /dev/null +++ b/test/form/effect-in-for-of-loop/_expected/amd.js @@ -0,0 +1,20 @@ +define(function () { 'use strict'; + + const items = [{}, {}, {}]; + + for ( const a of items.children ) { + a.foo = 'a'; + } + + let c; + for ( c of items.children ) { + c.bar = 'c'; + } + + assert.deepEqual( items, [ + { foo: 'a', bar: 'c' }, + { foo: 'a', bar: 'c' }, + { foo: 'a', bar: 'c' } + ]); + +}); diff --git a/test/form/effect-in-for-of-loop/_expected/cjs.js b/test/form/effect-in-for-of-loop/_expected/cjs.js new file mode 100644 index 0000000..197b73e --- /dev/null +++ b/test/form/effect-in-for-of-loop/_expected/cjs.js @@ -0,0 +1,18 @@ +'use strict'; + +const items = [{}, {}, {}]; + +for ( const a of items.children ) { + a.foo = 'a'; +} + +let c; +for ( c of items.children ) { + c.bar = 'c'; +} + +assert.deepEqual( items, [ + { foo: 'a', bar: 'c' }, + { foo: 'a', bar: 'c' }, + { foo: 'a', bar: 'c' } +]); diff --git a/test/form/effect-in-for-of-loop/_expected/es.js b/test/form/effect-in-for-of-loop/_expected/es.js new file mode 100644 index 0000000..3869419 --- /dev/null +++ b/test/form/effect-in-for-of-loop/_expected/es.js @@ -0,0 +1,16 @@ +const items = [{}, {}, {}]; + +for ( const a of items.children ) { + a.foo = 'a'; +} + +let c; +for ( c of items.children ) { + c.bar = 'c'; +} + +assert.deepEqual( items, [ + { foo: 'a', bar: 'c' }, + { foo: 'a', bar: 'c' }, + { foo: 'a', bar: 'c' } +]); diff --git a/test/form/effect-in-for-of-loop/_expected/iife.js b/test/form/effect-in-for-of-loop/_expected/iife.js new file mode 100644 index 0000000..7ca26c8 --- /dev/null +++ b/test/form/effect-in-for-of-loop/_expected/iife.js @@ -0,0 +1,21 @@ +(function () { + 'use strict'; + + const items = [{}, {}, {}]; + + for ( const a of items.children ) { + a.foo = 'a'; + } + + let c; + for ( c of items.children ) { + c.bar = 'c'; + } + + assert.deepEqual( items, [ + { foo: 'a', bar: 'c' }, + { foo: 'a', bar: 'c' }, + { foo: 'a', bar: 'c' } + ]); + +}()); diff --git a/test/form/effect-in-for-of-loop/_expected/umd.js b/test/form/effect-in-for-of-loop/_expected/umd.js new file mode 100644 index 0000000..08ef206 --- /dev/null +++ b/test/form/effect-in-for-of-loop/_expected/umd.js @@ -0,0 +1,24 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + const items = [{}, {}, {}]; + + for ( const a of items.children ) { + a.foo = 'a'; + } + + let c; + for ( c of items.children ) { + c.bar = 'c'; + } + + assert.deepEqual( items, [ + { foo: 'a', bar: 'c' }, + { foo: 'a', bar: 'c' }, + { foo: 'a', bar: 'c' } + ]); + +}))); diff --git a/test/form/effect-in-for-of-loop/main.js b/test/form/effect-in-for-of-loop/main.js new file mode 100644 index 0000000..122bf0f --- /dev/null +++ b/test/form/effect-in-for-of-loop/main.js @@ -0,0 +1,25 @@ +const items = [{}, {}, {}]; + +for ( const a of items.children ) { + a.foo = 'a'; +} + +for ( const b of items.children ) { + // do nothing +} + +let c; +for ( c of items.children ) { + c.bar = 'c'; +} + +let d; +for ( d of items.children ) { + // do nothing +} + +assert.deepEqual( items, [ + { foo: 'a', bar: 'c' }, + { foo: 'a', bar: 'c' }, + { foo: 'a', bar: 'c' } +]); diff --git a/test/form/empty-block-statement/_config.js b/test/form/empty-block-statement/_config.js new file mode 100644 index 0000000..283b0b6 --- /dev/null +++ b/test/form/empty-block-statement/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'removes an empty block statement' +}; diff --git a/test/form/empty-block-statement/_expected/amd.js b/test/form/empty-block-statement/_expected/amd.js new file mode 100644 index 0000000..6036f28 --- /dev/null +++ b/test/form/empty-block-statement/_expected/amd.js @@ -0,0 +1,6 @@ +define(function () { 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}); diff --git a/test/form/empty-block-statement/_expected/cjs.js b/test/form/empty-block-statement/_expected/cjs.js new file mode 100644 index 0000000..33f9de0 --- /dev/null +++ b/test/form/empty-block-statement/_expected/cjs.js @@ -0,0 +1,4 @@ +'use strict'; + +console.log( 1 ); +console.log( 2 ); diff --git a/test/form/empty-block-statement/_expected/es.js b/test/form/empty-block-statement/_expected/es.js new file mode 100644 index 0000000..472e544 --- /dev/null +++ b/test/form/empty-block-statement/_expected/es.js @@ -0,0 +1,2 @@ +console.log( 1 ); +console.log( 2 ); diff --git a/test/form/empty-block-statement/_expected/iife.js b/test/form/empty-block-statement/_expected/iife.js new file mode 100644 index 0000000..08a84a9 --- /dev/null +++ b/test/form/empty-block-statement/_expected/iife.js @@ -0,0 +1,7 @@ +(function () { + 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}()); diff --git a/test/form/empty-block-statement/_expected/umd.js b/test/form/empty-block-statement/_expected/umd.js new file mode 100644 index 0000000..0aae43f --- /dev/null +++ b/test/form/empty-block-statement/_expected/umd.js @@ -0,0 +1,10 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}))); diff --git a/test/form/empty-block-statement/main.js b/test/form/empty-block-statement/main.js new file mode 100644 index 0000000..edca010 --- /dev/null +++ b/test/form/empty-block-statement/main.js @@ -0,0 +1,5 @@ +console.log( 1 ); +{ + // empty +} +console.log( 2 ); diff --git a/test/form/empty-do-while-statement/_config.js b/test/form/empty-do-while-statement/_config.js new file mode 100644 index 0000000..00672ed --- /dev/null +++ b/test/form/empty-do-while-statement/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'removes an empty do-while statement' +}; diff --git a/test/form/empty-do-while-statement/_expected/amd.js b/test/form/empty-do-while-statement/_expected/amd.js new file mode 100644 index 0000000..e44e556 --- /dev/null +++ b/test/form/empty-do-while-statement/_expected/amd.js @@ -0,0 +1,6 @@ +define(function () { 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}); \ No newline at end of file diff --git a/test/form/empty-do-while-statement/_expected/cjs.js b/test/form/empty-do-while-statement/_expected/cjs.js new file mode 100644 index 0000000..1afb143 --- /dev/null +++ b/test/form/empty-do-while-statement/_expected/cjs.js @@ -0,0 +1,4 @@ +'use strict'; + +console.log( 1 ); +console.log( 2 ); \ No newline at end of file diff --git a/test/form/empty-do-while-statement/_expected/es.js b/test/form/empty-do-while-statement/_expected/es.js new file mode 100644 index 0000000..f97f3d0 --- /dev/null +++ b/test/form/empty-do-while-statement/_expected/es.js @@ -0,0 +1,2 @@ +console.log( 1 ); +console.log( 2 ); \ No newline at end of file diff --git a/test/form/empty-do-while-statement/_expected/iife.js b/test/form/empty-do-while-statement/_expected/iife.js new file mode 100644 index 0000000..39aedbf --- /dev/null +++ b/test/form/empty-do-while-statement/_expected/iife.js @@ -0,0 +1,7 @@ +(function () { + 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}()); \ No newline at end of file diff --git a/test/form/empty-do-while-statement/_expected/umd.js b/test/form/empty-do-while-statement/_expected/umd.js new file mode 100644 index 0000000..0445193 --- /dev/null +++ b/test/form/empty-do-while-statement/_expected/umd.js @@ -0,0 +1,10 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}))); \ No newline at end of file diff --git a/test/form/empty-do-while-statement/main.js b/test/form/empty-do-while-statement/main.js new file mode 100644 index 0000000..8b04429 --- /dev/null +++ b/test/form/empty-do-while-statement/main.js @@ -0,0 +1,6 @@ +console.log( 1 ); +var condition = true; +do { + condition = false; +} while ( condition ); +console.log( 2 ); diff --git a/test/form/empty-for-in-statement/_config.js b/test/form/empty-for-in-statement/_config.js new file mode 100644 index 0000000..b95837c --- /dev/null +++ b/test/form/empty-for-in-statement/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'removes an empty for-in statement' +}; diff --git a/test/form/empty-for-in-statement/_expected/amd.js b/test/form/empty-for-in-statement/_expected/amd.js new file mode 100644 index 0000000..e44e556 --- /dev/null +++ b/test/form/empty-for-in-statement/_expected/amd.js @@ -0,0 +1,6 @@ +define(function () { 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}); \ No newline at end of file diff --git a/test/form/empty-for-in-statement/_expected/cjs.js b/test/form/empty-for-in-statement/_expected/cjs.js new file mode 100644 index 0000000..1afb143 --- /dev/null +++ b/test/form/empty-for-in-statement/_expected/cjs.js @@ -0,0 +1,4 @@ +'use strict'; + +console.log( 1 ); +console.log( 2 ); \ No newline at end of file diff --git a/test/form/empty-for-in-statement/_expected/es.js b/test/form/empty-for-in-statement/_expected/es.js new file mode 100644 index 0000000..f97f3d0 --- /dev/null +++ b/test/form/empty-for-in-statement/_expected/es.js @@ -0,0 +1,2 @@ +console.log( 1 ); +console.log( 2 ); \ No newline at end of file diff --git a/test/form/empty-for-in-statement/_expected/iife.js b/test/form/empty-for-in-statement/_expected/iife.js new file mode 100644 index 0000000..39aedbf --- /dev/null +++ b/test/form/empty-for-in-statement/_expected/iife.js @@ -0,0 +1,7 @@ +(function () { + 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}()); \ No newline at end of file diff --git a/test/form/empty-for-in-statement/_expected/umd.js b/test/form/empty-for-in-statement/_expected/umd.js new file mode 100644 index 0000000..0445193 --- /dev/null +++ b/test/form/empty-for-in-statement/_expected/umd.js @@ -0,0 +1,10 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}))); \ No newline at end of file diff --git a/test/form/empty-for-in-statement/main.js b/test/form/empty-for-in-statement/main.js new file mode 100644 index 0000000..cb706f7 --- /dev/null +++ b/test/form/empty-for-in-statement/main.js @@ -0,0 +1,5 @@ +console.log( 1 ); +for ( const i in whatever ) { + // do nothing +} +console.log( 2 ); diff --git a/test/form/empty-for-of-statement/_config.js b/test/form/empty-for-of-statement/_config.js new file mode 100644 index 0000000..4a7bd91 --- /dev/null +++ b/test/form/empty-for-of-statement/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'removes an empty for-of statement' +}; diff --git a/test/form/empty-for-of-statement/_expected/amd.js b/test/form/empty-for-of-statement/_expected/amd.js new file mode 100644 index 0000000..e44e556 --- /dev/null +++ b/test/form/empty-for-of-statement/_expected/amd.js @@ -0,0 +1,6 @@ +define(function () { 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}); \ No newline at end of file diff --git a/test/form/empty-for-of-statement/_expected/cjs.js b/test/form/empty-for-of-statement/_expected/cjs.js new file mode 100644 index 0000000..1afb143 --- /dev/null +++ b/test/form/empty-for-of-statement/_expected/cjs.js @@ -0,0 +1,4 @@ +'use strict'; + +console.log( 1 ); +console.log( 2 ); \ No newline at end of file diff --git a/test/form/empty-for-of-statement/_expected/es.js b/test/form/empty-for-of-statement/_expected/es.js new file mode 100644 index 0000000..f97f3d0 --- /dev/null +++ b/test/form/empty-for-of-statement/_expected/es.js @@ -0,0 +1,2 @@ +console.log( 1 ); +console.log( 2 ); \ No newline at end of file diff --git a/test/form/empty-for-of-statement/_expected/iife.js b/test/form/empty-for-of-statement/_expected/iife.js new file mode 100644 index 0000000..39aedbf --- /dev/null +++ b/test/form/empty-for-of-statement/_expected/iife.js @@ -0,0 +1,7 @@ +(function () { + 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}()); \ No newline at end of file diff --git a/test/form/empty-for-of-statement/_expected/umd.js b/test/form/empty-for-of-statement/_expected/umd.js new file mode 100644 index 0000000..0445193 --- /dev/null +++ b/test/form/empty-for-of-statement/_expected/umd.js @@ -0,0 +1,10 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}))); \ No newline at end of file diff --git a/test/form/empty-for-of-statement/main.js b/test/form/empty-for-of-statement/main.js new file mode 100644 index 0000000..e591dde --- /dev/null +++ b/test/form/empty-for-of-statement/main.js @@ -0,0 +1,5 @@ +console.log( 1 ); +for ( const i of whatever ) { + // do nothing +} +console.log( 2 ); diff --git a/test/form/empty-for-statement/_config.js b/test/form/empty-for-statement/_config.js new file mode 100644 index 0000000..f448ddf --- /dev/null +++ b/test/form/empty-for-statement/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'removes an empty for statement' +}; diff --git a/test/form/empty-for-statement/_expected/amd.js b/test/form/empty-for-statement/_expected/amd.js new file mode 100644 index 0000000..6036f28 --- /dev/null +++ b/test/form/empty-for-statement/_expected/amd.js @@ -0,0 +1,6 @@ +define(function () { 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}); diff --git a/test/form/empty-for-statement/_expected/cjs.js b/test/form/empty-for-statement/_expected/cjs.js new file mode 100644 index 0000000..33f9de0 --- /dev/null +++ b/test/form/empty-for-statement/_expected/cjs.js @@ -0,0 +1,4 @@ +'use strict'; + +console.log( 1 ); +console.log( 2 ); diff --git a/test/form/empty-for-statement/_expected/es.js b/test/form/empty-for-statement/_expected/es.js new file mode 100644 index 0000000..472e544 --- /dev/null +++ b/test/form/empty-for-statement/_expected/es.js @@ -0,0 +1,2 @@ +console.log( 1 ); +console.log( 2 ); diff --git a/test/form/empty-for-statement/_expected/iife.js b/test/form/empty-for-statement/_expected/iife.js new file mode 100644 index 0000000..08a84a9 --- /dev/null +++ b/test/form/empty-for-statement/_expected/iife.js @@ -0,0 +1,7 @@ +(function () { + 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}()); diff --git a/test/form/empty-for-statement/_expected/umd.js b/test/form/empty-for-statement/_expected/umd.js new file mode 100644 index 0000000..0aae43f --- /dev/null +++ b/test/form/empty-for-statement/_expected/umd.js @@ -0,0 +1,10 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}))); diff --git a/test/form/empty-for-statement/main.js b/test/form/empty-for-statement/main.js new file mode 100644 index 0000000..3b5d176 --- /dev/null +++ b/test/form/empty-for-statement/main.js @@ -0,0 +1,5 @@ +console.log( 1 ); +for ( let i = 0; i < 10; i += 1 ) { + // do nothing +} +console.log( 2 ); diff --git a/test/form/empty-if-statement/_config.js b/test/form/empty-if-statement/_config.js new file mode 100644 index 0000000..abea889 --- /dev/null +++ b/test/form/empty-if-statement/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'removes an empty if statement' +}; diff --git a/test/form/empty-if-statement/_expected/amd.js b/test/form/empty-if-statement/_expected/amd.js new file mode 100644 index 0000000..e44e556 --- /dev/null +++ b/test/form/empty-if-statement/_expected/amd.js @@ -0,0 +1,6 @@ +define(function () { 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}); \ No newline at end of file diff --git a/test/form/empty-if-statement/_expected/cjs.js b/test/form/empty-if-statement/_expected/cjs.js new file mode 100644 index 0000000..1afb143 --- /dev/null +++ b/test/form/empty-if-statement/_expected/cjs.js @@ -0,0 +1,4 @@ +'use strict'; + +console.log( 1 ); +console.log( 2 ); \ No newline at end of file diff --git a/test/form/empty-if-statement/_expected/es.js b/test/form/empty-if-statement/_expected/es.js new file mode 100644 index 0000000..f97f3d0 --- /dev/null +++ b/test/form/empty-if-statement/_expected/es.js @@ -0,0 +1,2 @@ +console.log( 1 ); +console.log( 2 ); \ No newline at end of file diff --git a/test/form/empty-if-statement/_expected/iife.js b/test/form/empty-if-statement/_expected/iife.js new file mode 100644 index 0000000..39aedbf --- /dev/null +++ b/test/form/empty-if-statement/_expected/iife.js @@ -0,0 +1,7 @@ +(function () { + 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}()); \ No newline at end of file diff --git a/test/form/empty-if-statement/_expected/umd.js b/test/form/empty-if-statement/_expected/umd.js new file mode 100644 index 0000000..0445193 --- /dev/null +++ b/test/form/empty-if-statement/_expected/umd.js @@ -0,0 +1,10 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}))); \ No newline at end of file diff --git a/test/form/empty-if-statement/main.js b/test/form/empty-if-statement/main.js new file mode 100644 index 0000000..6db9dc1 --- /dev/null +++ b/test/form/empty-if-statement/main.js @@ -0,0 +1,5 @@ +console.log( 1 ); +if ( nothing ) { + // empty +} +console.log( 2 ); diff --git a/test/form/empty-statement-consequent/_config.js b/test/form/empty-statement-consequent/_config.js new file mode 100644 index 0000000..dc10538 --- /dev/null +++ b/test/form/empty-statement-consequent/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'preserves empty statements used as the consequent of conditionals' +}; diff --git a/test/form/empty-statement-consequent/_expected/amd.js b/test/form/empty-statement-consequent/_expected/amd.js new file mode 100644 index 0000000..9114028 --- /dev/null +++ b/test/form/empty-statement-consequent/_expected/amd.js @@ -0,0 +1,6 @@ +define(function () { 'use strict'; + + if ( a === 0 ); + else a++; + +}); \ No newline at end of file diff --git a/test/form/empty-statement-consequent/_expected/cjs.js b/test/form/empty-statement-consequent/_expected/cjs.js new file mode 100644 index 0000000..9b5e04a --- /dev/null +++ b/test/form/empty-statement-consequent/_expected/cjs.js @@ -0,0 +1,4 @@ +'use strict'; + +if ( a === 0 ); +else a++; \ No newline at end of file diff --git a/test/form/empty-statement-consequent/_expected/es.js b/test/form/empty-statement-consequent/_expected/es.js new file mode 100644 index 0000000..04006b3 --- /dev/null +++ b/test/form/empty-statement-consequent/_expected/es.js @@ -0,0 +1,2 @@ +if ( a === 0 ); +else a++; \ No newline at end of file diff --git a/test/form/empty-statement-consequent/_expected/iife.js b/test/form/empty-statement-consequent/_expected/iife.js new file mode 100644 index 0000000..45b92d8 --- /dev/null +++ b/test/form/empty-statement-consequent/_expected/iife.js @@ -0,0 +1,7 @@ +(function () { + 'use strict'; + + if ( a === 0 ); + else a++; + +}()); \ No newline at end of file diff --git a/test/form/empty-statement-consequent/_expected/umd.js b/test/form/empty-statement-consequent/_expected/umd.js new file mode 100644 index 0000000..08026c7 --- /dev/null +++ b/test/form/empty-statement-consequent/_expected/umd.js @@ -0,0 +1,10 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + if ( a === 0 ); + else a++; + +}))); \ No newline at end of file diff --git a/test/form/empty-statement-consequent/main.js b/test/form/empty-statement-consequent/main.js new file mode 100644 index 0000000..b6b36cf --- /dev/null +++ b/test/form/empty-statement-consequent/main.js @@ -0,0 +1,2 @@ +if ( a === 0 ); +else a++; diff --git a/test/form/empty-statement/_config.js b/test/form/empty-statement/_config.js new file mode 100644 index 0000000..76e4fa3 --- /dev/null +++ b/test/form/empty-statement/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'removes an empty statement' +}; diff --git a/test/form/empty-statement/_expected/amd.js b/test/form/empty-statement/_expected/amd.js new file mode 100644 index 0000000..d7ee383 --- /dev/null +++ b/test/form/empty-statement/_expected/amd.js @@ -0,0 +1,7 @@ +define(function () { 'use strict'; + + console.log( 1 ); + + console.log( 2 ); + +}); diff --git a/test/form/empty-statement/_expected/cjs.js b/test/form/empty-statement/_expected/cjs.js new file mode 100644 index 0000000..2ad8cd2 --- /dev/null +++ b/test/form/empty-statement/_expected/cjs.js @@ -0,0 +1,5 @@ +'use strict'; + +console.log( 1 ); + +console.log( 2 ); diff --git a/test/form/empty-statement/_expected/es.js b/test/form/empty-statement/_expected/es.js new file mode 100644 index 0000000..b149085 --- /dev/null +++ b/test/form/empty-statement/_expected/es.js @@ -0,0 +1,3 @@ +console.log( 1 ); + +console.log( 2 ); diff --git a/test/form/empty-statement/_expected/iife.js b/test/form/empty-statement/_expected/iife.js new file mode 100644 index 0000000..c9dbff5 --- /dev/null +++ b/test/form/empty-statement/_expected/iife.js @@ -0,0 +1,8 @@ +(function () { + 'use strict'; + + console.log( 1 ); + + console.log( 2 ); + +}()); diff --git a/test/form/empty-statement/_expected/umd.js b/test/form/empty-statement/_expected/umd.js new file mode 100644 index 0000000..e97ebff --- /dev/null +++ b/test/form/empty-statement/_expected/umd.js @@ -0,0 +1,11 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + console.log( 1 ); + + console.log( 2 ); + +}))); diff --git a/test/form/empty-statement/main.js b/test/form/empty-statement/main.js new file mode 100644 index 0000000..43f7037 --- /dev/null +++ b/test/form/empty-statement/main.js @@ -0,0 +1,4 @@ +; +console.log( 1 );; +; +console.log( 2 );; diff --git a/test/form/empty-switch-statement/_config.js b/test/form/empty-switch-statement/_config.js new file mode 100644 index 0000000..29c0517 --- /dev/null +++ b/test/form/empty-switch-statement/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'removes an empty switch statement' +}; diff --git a/test/form/empty-switch-statement/_expected/amd.js b/test/form/empty-switch-statement/_expected/amd.js new file mode 100644 index 0000000..e44e556 --- /dev/null +++ b/test/form/empty-switch-statement/_expected/amd.js @@ -0,0 +1,6 @@ +define(function () { 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}); \ No newline at end of file diff --git a/test/form/empty-switch-statement/_expected/cjs.js b/test/form/empty-switch-statement/_expected/cjs.js new file mode 100644 index 0000000..1afb143 --- /dev/null +++ b/test/form/empty-switch-statement/_expected/cjs.js @@ -0,0 +1,4 @@ +'use strict'; + +console.log( 1 ); +console.log( 2 ); \ No newline at end of file diff --git a/test/form/empty-switch-statement/_expected/es.js b/test/form/empty-switch-statement/_expected/es.js new file mode 100644 index 0000000..f97f3d0 --- /dev/null +++ b/test/form/empty-switch-statement/_expected/es.js @@ -0,0 +1,2 @@ +console.log( 1 ); +console.log( 2 ); \ No newline at end of file diff --git a/test/form/empty-switch-statement/_expected/iife.js b/test/form/empty-switch-statement/_expected/iife.js new file mode 100644 index 0000000..39aedbf --- /dev/null +++ b/test/form/empty-switch-statement/_expected/iife.js @@ -0,0 +1,7 @@ +(function () { + 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}()); \ No newline at end of file diff --git a/test/form/empty-switch-statement/_expected/umd.js b/test/form/empty-switch-statement/_expected/umd.js new file mode 100644 index 0000000..0445193 --- /dev/null +++ b/test/form/empty-switch-statement/_expected/umd.js @@ -0,0 +1,10 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}))); \ No newline at end of file diff --git a/test/form/empty-switch-statement/main.js b/test/form/empty-switch-statement/main.js new file mode 100644 index 0000000..58da4d4 --- /dev/null +++ b/test/form/empty-switch-statement/main.js @@ -0,0 +1,11 @@ +console.log( 1 ); +var result; +switch ( whatever ) { + case foo: + result = 'foo'; + break; + + default: + result = 'default'; +} +console.log( 2 ); diff --git a/test/form/empty-try-catch-statement/_config.js b/test/form/empty-try-catch-statement/_config.js new file mode 100644 index 0000000..70bf652 --- /dev/null +++ b/test/form/empty-try-catch-statement/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'removes an empty try-catch-finally statement' +}; diff --git a/test/form/empty-try-catch-statement/_expected/amd.js b/test/form/empty-try-catch-statement/_expected/amd.js new file mode 100644 index 0000000..e44e556 --- /dev/null +++ b/test/form/empty-try-catch-statement/_expected/amd.js @@ -0,0 +1,6 @@ +define(function () { 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}); \ No newline at end of file diff --git a/test/form/empty-try-catch-statement/_expected/cjs.js b/test/form/empty-try-catch-statement/_expected/cjs.js new file mode 100644 index 0000000..1afb143 --- /dev/null +++ b/test/form/empty-try-catch-statement/_expected/cjs.js @@ -0,0 +1,4 @@ +'use strict'; + +console.log( 1 ); +console.log( 2 ); \ No newline at end of file diff --git a/test/form/empty-try-catch-statement/_expected/es.js b/test/form/empty-try-catch-statement/_expected/es.js new file mode 100644 index 0000000..f97f3d0 --- /dev/null +++ b/test/form/empty-try-catch-statement/_expected/es.js @@ -0,0 +1,2 @@ +console.log( 1 ); +console.log( 2 ); \ No newline at end of file diff --git a/test/form/empty-try-catch-statement/_expected/iife.js b/test/form/empty-try-catch-statement/_expected/iife.js new file mode 100644 index 0000000..39aedbf --- /dev/null +++ b/test/form/empty-try-catch-statement/_expected/iife.js @@ -0,0 +1,7 @@ +(function () { + 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}()); \ No newline at end of file diff --git a/test/form/empty-try-catch-statement/_expected/umd.js b/test/form/empty-try-catch-statement/_expected/umd.js new file mode 100644 index 0000000..0445193 --- /dev/null +++ b/test/form/empty-try-catch-statement/_expected/umd.js @@ -0,0 +1,10 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}))); \ No newline at end of file diff --git a/test/form/empty-try-catch-statement/main.js b/test/form/empty-try-catch-statement/main.js new file mode 100644 index 0000000..52ed8c1 --- /dev/null +++ b/test/form/empty-try-catch-statement/main.js @@ -0,0 +1,9 @@ +console.log( 1 ); +try { + // do nothing +} catch ( err ) { + // do nothing +} finally { + // do nothing +} +console.log( 2 ); diff --git a/test/form/empty-while-statement/_config.js b/test/form/empty-while-statement/_config.js new file mode 100644 index 0000000..8db2e7f --- /dev/null +++ b/test/form/empty-while-statement/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'removes an empty while statement' +}; diff --git a/test/form/empty-while-statement/_expected/amd.js b/test/form/empty-while-statement/_expected/amd.js new file mode 100644 index 0000000..e44e556 --- /dev/null +++ b/test/form/empty-while-statement/_expected/amd.js @@ -0,0 +1,6 @@ +define(function () { 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}); \ No newline at end of file diff --git a/test/form/empty-while-statement/_expected/cjs.js b/test/form/empty-while-statement/_expected/cjs.js new file mode 100644 index 0000000..1afb143 --- /dev/null +++ b/test/form/empty-while-statement/_expected/cjs.js @@ -0,0 +1,4 @@ +'use strict'; + +console.log( 1 ); +console.log( 2 ); \ No newline at end of file diff --git a/test/form/empty-while-statement/_expected/es.js b/test/form/empty-while-statement/_expected/es.js new file mode 100644 index 0000000..f97f3d0 --- /dev/null +++ b/test/form/empty-while-statement/_expected/es.js @@ -0,0 +1,2 @@ +console.log( 1 ); +console.log( 2 ); \ No newline at end of file diff --git a/test/form/empty-while-statement/_expected/iife.js b/test/form/empty-while-statement/_expected/iife.js new file mode 100644 index 0000000..39aedbf --- /dev/null +++ b/test/form/empty-while-statement/_expected/iife.js @@ -0,0 +1,7 @@ +(function () { + 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}()); \ No newline at end of file diff --git a/test/form/empty-while-statement/_expected/umd.js b/test/form/empty-while-statement/_expected/umd.js new file mode 100644 index 0000000..0445193 --- /dev/null +++ b/test/form/empty-while-statement/_expected/umd.js @@ -0,0 +1,10 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + console.log( 1 ); + console.log( 2 ); + +}))); \ No newline at end of file diff --git a/test/form/empty-while-statement/main.js b/test/form/empty-while-statement/main.js new file mode 100644 index 0000000..c499a7f --- /dev/null +++ b/test/form/empty-while-statement/main.js @@ -0,0 +1,6 @@ +console.log( 1 ); +var condition = true; +while ( condition ) { + condition = false; +} +console.log( 2 ); diff --git a/test/form/erroneous-nested-member-expression/_expected/amd.js b/test/form/erroneous-nested-member-expression/_expected/amd.js index 6499b90..4b402c7 100644 --- a/test/form/erroneous-nested-member-expression/_expected/amd.js +++ b/test/form/erroneous-nested-member-expression/_expected/amd.js @@ -6,8 +6,8 @@ define(function () { 'use strict'; console.log('har?'); } }; - }; + } yar.har(); -}); \ No newline at end of file +}); diff --git a/test/form/erroneous-nested-member-expression/_expected/cjs.js b/test/form/erroneous-nested-member-expression/_expected/cjs.js index aa4b949..01c96f9 100644 --- a/test/form/erroneous-nested-member-expression/_expected/cjs.js +++ b/test/form/erroneous-nested-member-expression/_expected/cjs.js @@ -6,6 +6,6 @@ function yar() { console.log('har?'); } }; -}; +} -yar.har(); \ No newline at end of file +yar.har(); diff --git a/test/form/erroneous-nested-member-expression/_expected/es.js b/test/form/erroneous-nested-member-expression/_expected/es.js index 34c4e2e..ee4eace 100644 --- a/test/form/erroneous-nested-member-expression/_expected/es.js +++ b/test/form/erroneous-nested-member-expression/_expected/es.js @@ -4,6 +4,6 @@ function yar() { console.log('har?'); } }; -}; +} -yar.har(); \ No newline at end of file +yar.har(); diff --git a/test/form/erroneous-nested-member-expression/_expected/iife.js b/test/form/erroneous-nested-member-expression/_expected/iife.js index 4233cfe..8a8d65e 100644 --- a/test/form/erroneous-nested-member-expression/_expected/iife.js +++ b/test/form/erroneous-nested-member-expression/_expected/iife.js @@ -7,8 +7,8 @@ console.log('har?'); } }; - }; + } yar.har(); -}()); \ No newline at end of file +}()); diff --git a/test/form/erroneous-nested-member-expression/_expected/umd.js b/test/form/erroneous-nested-member-expression/_expected/umd.js index 2a8dba1..94dee16 100644 --- a/test/form/erroneous-nested-member-expression/_expected/umd.js +++ b/test/form/erroneous-nested-member-expression/_expected/umd.js @@ -10,8 +10,8 @@ console.log('har?'); } }; - }; + } yar.har(); -}))); \ No newline at end of file +}))); diff --git a/test/form/external-imports/_expected/es.js b/test/form/external-imports/_expected/es.js index 1c632ef..59d2b5e 100644 --- a/test/form/external-imports/_expected/es.js +++ b/test/form/external-imports/_expected/es.js @@ -1,11 +1,11 @@ import factory from 'factory'; import { bar, foo } from 'baz'; -import { port } from 'shipping-port'; +import { forEach, port } from 'shipping-port'; import * as containers from 'shipping-port'; import alphabet, { a } from 'alphabet'; factory( null ); foo( bar, port ); -containers.forEach( console.log, console ); +forEach( console.log, console ); console.log( a ); console.log( alphabet.length ); diff --git a/test/form/for-loop-with-empty-head/_config.js b/test/form/for-loop-with-empty-head/_config.js new file mode 100644 index 0000000..3fc4a67 --- /dev/null +++ b/test/form/for-loop-with-empty-head/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'handles for loop with empty head' +}; diff --git a/test/form/for-loop-with-empty-head/_expected/amd.js b/test/form/for-loop-with-empty-head/_expected/amd.js new file mode 100644 index 0000000..a07e3e0 --- /dev/null +++ b/test/form/for-loop-with-empty-head/_expected/amd.js @@ -0,0 +1,7 @@ +define(function () { 'use strict'; + + for ( ; ; ) { + console.log( 42 ); + } + +}); \ No newline at end of file diff --git a/test/form/for-loop-with-empty-head/_expected/cjs.js b/test/form/for-loop-with-empty-head/_expected/cjs.js new file mode 100644 index 0000000..f94dff7 --- /dev/null +++ b/test/form/for-loop-with-empty-head/_expected/cjs.js @@ -0,0 +1,5 @@ +'use strict'; + +for ( ; ; ) { + console.log( 42 ); +} \ No newline at end of file diff --git a/test/form/for-loop-with-empty-head/_expected/es.js b/test/form/for-loop-with-empty-head/_expected/es.js new file mode 100644 index 0000000..1690f25 --- /dev/null +++ b/test/form/for-loop-with-empty-head/_expected/es.js @@ -0,0 +1,3 @@ +for ( ; ; ) { + console.log( 42 ); +} \ No newline at end of file diff --git a/test/form/for-loop-with-empty-head/_expected/iife.js b/test/form/for-loop-with-empty-head/_expected/iife.js new file mode 100644 index 0000000..2217234 --- /dev/null +++ b/test/form/for-loop-with-empty-head/_expected/iife.js @@ -0,0 +1,8 @@ +(function () { + 'use strict'; + + for ( ; ; ) { + console.log( 42 ); + } + +}()); \ No newline at end of file diff --git a/test/form/for-loop-with-empty-head/_expected/umd.js b/test/form/for-loop-with-empty-head/_expected/umd.js new file mode 100644 index 0000000..3e90731 --- /dev/null +++ b/test/form/for-loop-with-empty-head/_expected/umd.js @@ -0,0 +1,11 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + for ( ; ; ) { + console.log( 42 ); + } + +}))); \ No newline at end of file diff --git a/test/form/for-loop-with-empty-head/main.js b/test/form/for-loop-with-empty-head/main.js new file mode 100644 index 0000000..5e492f8 --- /dev/null +++ b/test/form/for-loop-with-empty-head/main.js @@ -0,0 +1,3 @@ +for ( ; ; ) { + console.log( 42 ); +} diff --git a/test/form/import-external-namespace-and-default/_expected/es.js b/test/form/import-external-namespace-and-default/_expected/es.js index 5655595..07d61c1 100644 --- a/test/form/import-external-namespace-and-default/_expected/es.js +++ b/test/form/import-external-namespace-and-default/_expected/es.js @@ -1,6 +1,7 @@ -import * as foo from 'foo'; +import { bar } from 'foo'; import foo__default from 'foo'; +import * as foo from 'foo'; -console.log( foo.bar ); +console.log( bar ); console.log( foo__default ); diff --git a/test/form/includes-all-namespace-declarations/_config.js b/test/form/includes-all-namespace-declarations/_config.js new file mode 100644 index 0000000..30ffc5e --- /dev/null +++ b/test/form/includes-all-namespace-declarations/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'includes all declarations referenced by reified namespaces' +} diff --git a/test/form/includes-all-namespace-declarations/_expected/amd.js b/test/form/includes-all-namespace-declarations/_expected/amd.js new file mode 100644 index 0000000..ec759b1 --- /dev/null +++ b/test/form/includes-all-namespace-declarations/_expected/amd.js @@ -0,0 +1,5 @@ +define(function () { 'use strict'; + + + +}); \ No newline at end of file diff --git a/test/form/includes-all-namespace-declarations/_expected/cjs.js b/test/form/includes-all-namespace-declarations/_expected/cjs.js new file mode 100644 index 0000000..eb109ab --- /dev/null +++ b/test/form/includes-all-namespace-declarations/_expected/cjs.js @@ -0,0 +1,2 @@ +'use strict'; + diff --git a/test/form/includes-all-namespace-declarations/_expected/es.js b/test/form/includes-all-namespace-declarations/_expected/es.js new file mode 100644 index 0000000..e69de29 diff --git a/test/form/includes-all-namespace-declarations/_expected/iife.js b/test/form/includes-all-namespace-declarations/_expected/iife.js new file mode 100644 index 0000000..f3d1016 --- /dev/null +++ b/test/form/includes-all-namespace-declarations/_expected/iife.js @@ -0,0 +1,6 @@ +(function () { + 'use strict'; + + + +}()); \ No newline at end of file diff --git a/test/form/includes-all-namespace-declarations/_expected/umd.js b/test/form/includes-all-namespace-declarations/_expected/umd.js new file mode 100644 index 0000000..d561e69 --- /dev/null +++ b/test/form/includes-all-namespace-declarations/_expected/umd.js @@ -0,0 +1,9 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + + +}))); \ No newline at end of file diff --git a/test/form/includes-all-namespace-declarations/indirection.js b/test/form/includes-all-namespace-declarations/indirection.js new file mode 100644 index 0000000..d98c18c --- /dev/null +++ b/test/form/includes-all-namespace-declarations/indirection.js @@ -0,0 +1,7 @@ +import * as unused from './unused.js'; + +var indirection = { + unused: unused +}; + +export { indirection }; diff --git a/test/form/includes-all-namespace-declarations/main.js b/test/form/includes-all-namespace-declarations/main.js new file mode 100644 index 0000000..0b45d9e --- /dev/null +++ b/test/form/includes-all-namespace-declarations/main.js @@ -0,0 +1 @@ +import { indirection } from './indirection.js'; diff --git a/test/form/includes-all-namespace-declarations/unused.js b/test/form/includes-all-namespace-declarations/unused.js new file mode 100644 index 0000000..b0ff512 --- /dev/null +++ b/test/form/includes-all-namespace-declarations/unused.js @@ -0,0 +1,3 @@ +function foo () {} + +export { foo }; diff --git a/test/form/interop-false/_config.js b/test/form/interop-false/_config.js new file mode 100644 index 0000000..0076e3a --- /dev/null +++ b/test/form/interop-false/_config.js @@ -0,0 +1,7 @@ +module.exports = { + description: 'getInterop with interop: false', + options: { + moduleName: 'foo', + interop: false + } +}; diff --git a/test/form/interop-false/_expected/amd.js b/test/form/interop-false/_expected/amd.js new file mode 100644 index 0000000..9245883 --- /dev/null +++ b/test/form/interop-false/_expected/amd.js @@ -0,0 +1,7 @@ +define(['core/view'], function (View) { 'use strict'; + + var main = View.extend({}); + + return main; + +}); diff --git a/test/form/interop-false/_expected/cjs.js b/test/form/interop-false/_expected/cjs.js new file mode 100644 index 0000000..8396bc7 --- /dev/null +++ b/test/form/interop-false/_expected/cjs.js @@ -0,0 +1,7 @@ +'use strict'; + +var View = require('core/view'); + +var main = View.extend({}); + +module.exports = main; diff --git a/test/form/interop-false/_expected/es.js b/test/form/interop-false/_expected/es.js new file mode 100644 index 0000000..d5e775e --- /dev/null +++ b/test/form/interop-false/_expected/es.js @@ -0,0 +1,5 @@ +import View from 'core/view'; + +var main = View.extend({}); + +export default main; diff --git a/test/form/interop-false/_expected/iife.js b/test/form/interop-false/_expected/iife.js new file mode 100644 index 0000000..d94c432 --- /dev/null +++ b/test/form/interop-false/_expected/iife.js @@ -0,0 +1,8 @@ +var foo = (function (View) { + 'use strict'; + + var main = View.extend({}); + + return main; + +}(View)); diff --git a/test/form/interop-false/_expected/umd.js b/test/form/interop-false/_expected/umd.js new file mode 100644 index 0000000..5c9be13 --- /dev/null +++ b/test/form/interop-false/_expected/umd.js @@ -0,0 +1,11 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('core/view')) : + typeof define === 'function' && define.amd ? define(['core/view'], factory) : + (global.foo = factory(global.View)); +}(this, (function (View) { 'use strict'; + + var main = View.extend({}); + + return main; + +}))); diff --git a/test/form/interop-false/main.js b/test/form/interop-false/main.js new file mode 100644 index 0000000..a6b1040 --- /dev/null +++ b/test/form/interop-false/main.js @@ -0,0 +1,2 @@ +import View from 'core/view'; +export default View.extend({}); diff --git a/test/form/intro-and-outro/_config.js b/test/form/intro-and-outro/_config.js index 843e554..410d586 100644 --- a/test/form/intro-and-outro/_config.js +++ b/test/form/intro-and-outro/_config.js @@ -3,6 +3,7 @@ module.exports = { options: { intro: '/* this is an intro */', outro: '/* this is an outro */', - moduleName: 'foo' + moduleName: 'foo', + external: [ 'external' ] } }; diff --git a/test/form/intro-and-outro/_expected/amd.js b/test/form/intro-and-outro/_expected/amd.js index 3c16c97..ca940e4 100644 --- a/test/form/intro-and-outro/_expected/amd.js +++ b/test/form/intro-and-outro/_expected/amd.js @@ -1,7 +1,10 @@ -define(function () { 'use strict'; +define(['external'], function (a) { 'use strict'; /* this is an intro */ - console.log( 'hello world' ); + var a__default = 'default' in a ? a['default'] : a; + + console.log( a__default ); + console.log( a.b ); var main = 42; diff --git a/test/form/intro-and-outro/_expected/cjs.js b/test/form/intro-and-outro/_expected/cjs.js index dde8f6b..6c68e67 100644 --- a/test/form/intro-and-outro/_expected/cjs.js +++ b/test/form/intro-and-outro/_expected/cjs.js @@ -1,7 +1,13 @@ 'use strict'; /* this is an intro */ -console.log( 'hello world' ); +function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } + +var a = require('external'); +var a__default = _interopDefault(a); + +console.log( a__default ); +console.log( a.b ); var main = 42; diff --git a/test/form/intro-and-outro/_expected/es.js b/test/form/intro-and-outro/_expected/es.js index 7459f70..2da2a74 100644 --- a/test/form/intro-and-outro/_expected/es.js +++ b/test/form/intro-and-outro/_expected/es.js @@ -1,5 +1,8 @@ /* this is an intro */ -console.log( 'hello world' ); +import a, { b } from 'external'; + +console.log( a ); +console.log( b ); var main = 42; diff --git a/test/form/intro-and-outro/_expected/iife.js b/test/form/intro-and-outro/_expected/iife.js index ef591fc..2c716d3 100644 --- a/test/form/intro-and-outro/_expected/iife.js +++ b/test/form/intro-and-outro/_expected/iife.js @@ -1,12 +1,15 @@ -var foo = (function () { +var foo = (function (a) { 'use strict'; /* this is an intro */ - console.log( 'hello world' ); + var a__default = 'default' in a ? a['default'] : a; + + console.log( a__default ); + console.log( a.b ); var main = 42; return main; /* this is an outro */ -}()); +}(a)); diff --git a/test/form/intro-and-outro/_expected/umd.js b/test/form/intro-and-outro/_expected/umd.js index 97b8e3e..232db4e 100644 --- a/test/form/intro-and-outro/_expected/umd.js +++ b/test/form/intro-and-outro/_expected/umd.js @@ -1,11 +1,14 @@ (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global.foo = factory()); -}(this, (function () { 'use strict'; + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('external')) : + typeof define === 'function' && define.amd ? define(['external'], factory) : + (global.foo = factory(global.a)); +}(this, (function (a) { 'use strict'; /* this is an intro */ - console.log( 'hello world' ); + var a__default = 'default' in a ? a['default'] : a; + + console.log( a__default ); + console.log( a.b ); var main = 42; diff --git a/test/form/intro-and-outro/main.js b/test/form/intro-and-outro/main.js index 6054017..663ee99 100644 --- a/test/form/intro-and-outro/main.js +++ b/test/form/intro-and-outro/main.js @@ -1,3 +1,6 @@ -console.log( 'hello world' ); +import a, { b } from 'external' + +console.log( a ); +console.log( b ); export default 42; diff --git a/test/form/namespace-optimization-b/_expected/amd.js b/test/form/namespace-optimization-b/_expected/amd.js index 30c5406..e2582cc 100644 --- a/test/form/namespace-optimization-b/_expected/amd.js +++ b/test/form/namespace-optimization-b/_expected/amd.js @@ -16,4 +16,4 @@ define(function () { 'use strict'; a(); -}); \ No newline at end of file +}); diff --git a/test/form/namespace-optimization-b/_expected/cjs.js b/test/form/namespace-optimization-b/_expected/cjs.js index 3ee6869..a826df2 100644 --- a/test/form/namespace-optimization-b/_expected/cjs.js +++ b/test/form/namespace-optimization-b/_expected/cjs.js @@ -14,4 +14,4 @@ function a () { } } -a(); \ No newline at end of file +a(); diff --git a/test/form/namespace-optimization-b/_expected/es.js b/test/form/namespace-optimization-b/_expected/es.js index 8346d0d..85c15d8 100644 --- a/test/form/namespace-optimization-b/_expected/es.js +++ b/test/form/namespace-optimization-b/_expected/es.js @@ -12,4 +12,4 @@ function a () { } } -a(); \ No newline at end of file +a(); diff --git a/test/form/namespace-optimization-b/_expected/iife.js b/test/form/namespace-optimization-b/_expected/iife.js index 6aeb1cd..6c497f5 100644 --- a/test/form/namespace-optimization-b/_expected/iife.js +++ b/test/form/namespace-optimization-b/_expected/iife.js @@ -17,4 +17,4 @@ a(); -}()); \ No newline at end of file +}()); diff --git a/test/form/namespace-optimization-b/_expected/umd.js b/test/form/namespace-optimization-b/_expected/umd.js index f7fbbdf..7a2c4b0 100644 --- a/test/form/namespace-optimization-b/_expected/umd.js +++ b/test/form/namespace-optimization-b/_expected/umd.js @@ -20,4 +20,4 @@ a(); -}))); \ No newline at end of file +}))); diff --git a/test/form/namespace-optimization/_expected/amd.js b/test/form/namespace-optimization/_expected/amd.js index a244c47..95231f1 100644 --- a/test/form/namespace-optimization/_expected/amd.js +++ b/test/form/namespace-optimization/_expected/amd.js @@ -2,6 +2,6 @@ define(function () { 'use strict'; function a () {} - a(); + console.log( a() ); }); diff --git a/test/form/namespace-optimization/_expected/cjs.js b/test/form/namespace-optimization/_expected/cjs.js index b52a7e5..33a83e9 100644 --- a/test/form/namespace-optimization/_expected/cjs.js +++ b/test/form/namespace-optimization/_expected/cjs.js @@ -2,4 +2,4 @@ function a () {} -a(); +console.log( a() ); diff --git a/test/form/namespace-optimization/_expected/es.js b/test/form/namespace-optimization/_expected/es.js index 8bee044..297521e 100644 --- a/test/form/namespace-optimization/_expected/es.js +++ b/test/form/namespace-optimization/_expected/es.js @@ -1,3 +1,3 @@ function a () {} -a(); +console.log( a() ); diff --git a/test/form/namespace-optimization/_expected/iife.js b/test/form/namespace-optimization/_expected/iife.js index 206c237..64673c9 100644 --- a/test/form/namespace-optimization/_expected/iife.js +++ b/test/form/namespace-optimization/_expected/iife.js @@ -3,6 +3,6 @@ function a () {} - a(); + console.log( a() ); }()); diff --git a/test/form/namespace-optimization/_expected/umd.js b/test/form/namespace-optimization/_expected/umd.js index b2bc94f..bf379ef 100644 --- a/test/form/namespace-optimization/_expected/umd.js +++ b/test/form/namespace-optimization/_expected/umd.js @@ -6,6 +6,6 @@ function a () {} - a(); + console.log( a() ); -}))); \ No newline at end of file +}))); diff --git a/test/form/namespace-optimization/main.js b/test/form/namespace-optimization/main.js index e902244..6b955f7 100644 --- a/test/form/namespace-optimization/main.js +++ b/test/form/namespace-optimization/main.js @@ -1,3 +1,3 @@ import * as foo from './foo'; -foo.bar.quux.a(); +console.log( foo.bar.quux.a() ); diff --git a/test/form/no-treeshake/_expected/es.js b/test/form/no-treeshake/_expected/es.js index 54f2add..ddcec9b 100644 --- a/test/form/no-treeshake/_expected/es.js +++ b/test/form/no-treeshake/_expected/es.js @@ -1,3 +1,4 @@ +import { value } from 'external'; import * as external from 'external'; var foo = 'unused'; @@ -11,7 +12,7 @@ function bar () { } function baz () { - return 13 + external.value; + return 13 + value; } var create = Object.create; diff --git a/test/form/paths-function/_config.js b/test/form/paths-function/_config.js index d4f6a0d..2d1f055 100644 --- a/test/form/paths-function/_config.js +++ b/test/form/paths-function/_config.js @@ -1,6 +1,6 @@ module.exports = { description: 'external paths (#754)', options: { - paths: id => `https://npmcdn.com/${id}` + paths: id => `https://unpkg.com/${id}` } }; diff --git a/test/form/paths-function/_expected/amd.js b/test/form/paths-function/_expected/amd.js index fe2d243..0893293 100644 --- a/test/form/paths-function/_expected/amd.js +++ b/test/form/paths-function/_expected/amd.js @@ -1,4 +1,4 @@ -define(['https://npmcdn.com/foo'], function (foo) { 'use strict'; +define(['https://unpkg.com/foo'], function (foo) { 'use strict'; foo = 'default' in foo ? foo['default'] : foo; diff --git a/test/form/paths-function/_expected/cjs.js b/test/form/paths-function/_expected/cjs.js index 864ce43..4bb0804 100644 --- a/test/form/paths-function/_expected/cjs.js +++ b/test/form/paths-function/_expected/cjs.js @@ -2,6 +2,6 @@ function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } -var foo = _interopDefault(require('https://npmcdn.com/foo')); +var foo = _interopDefault(require('https://unpkg.com/foo')); assert.equal( foo, 42 ); diff --git a/test/form/paths-function/_expected/es.js b/test/form/paths-function/_expected/es.js index 1c4fe57..74bfea3 100644 --- a/test/form/paths-function/_expected/es.js +++ b/test/form/paths-function/_expected/es.js @@ -1,3 +1,3 @@ -import foo from 'https://npmcdn.com/foo'; +import foo from 'https://unpkg.com/foo'; assert.equal( foo, 42 ); diff --git a/test/form/paths-function/_expected/umd.js b/test/form/paths-function/_expected/umd.js index 8da109e..e86e892 100644 --- a/test/form/paths-function/_expected/umd.js +++ b/test/form/paths-function/_expected/umd.js @@ -1,6 +1,6 @@ (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('https://npmcdn.com/foo')) : - typeof define === 'function' && define.amd ? define(['https://npmcdn.com/foo'], factory) : + typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('https://unpkg.com/foo')) : + typeof define === 'function' && define.amd ? define(['https://unpkg.com/foo'], factory) : (factory(global.foo)); }(this, (function (foo) { 'use strict'; diff --git a/test/form/paths/_config.js b/test/form/paths/_config.js index c9b4ca3..1d6ed03 100644 --- a/test/form/paths/_config.js +++ b/test/form/paths/_config.js @@ -2,7 +2,7 @@ module.exports = { description: 'external paths (#754)', options: { paths: { - foo: 'https://npmcdn.com/foo' + foo: 'https://unpkg.com/foo' } } }; diff --git a/test/form/paths/_expected/amd.js b/test/form/paths/_expected/amd.js index fe2d243..0893293 100644 --- a/test/form/paths/_expected/amd.js +++ b/test/form/paths/_expected/amd.js @@ -1,4 +1,4 @@ -define(['https://npmcdn.com/foo'], function (foo) { 'use strict'; +define(['https://unpkg.com/foo'], function (foo) { 'use strict'; foo = 'default' in foo ? foo['default'] : foo; diff --git a/test/form/paths/_expected/cjs.js b/test/form/paths/_expected/cjs.js index 864ce43..4bb0804 100644 --- a/test/form/paths/_expected/cjs.js +++ b/test/form/paths/_expected/cjs.js @@ -2,6 +2,6 @@ function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } -var foo = _interopDefault(require('https://npmcdn.com/foo')); +var foo = _interopDefault(require('https://unpkg.com/foo')); assert.equal( foo, 42 ); diff --git a/test/form/paths/_expected/es.js b/test/form/paths/_expected/es.js index 1c4fe57..74bfea3 100644 --- a/test/form/paths/_expected/es.js +++ b/test/form/paths/_expected/es.js @@ -1,3 +1,3 @@ -import foo from 'https://npmcdn.com/foo'; +import foo from 'https://unpkg.com/foo'; assert.equal( foo, 42 ); diff --git a/test/form/paths/_expected/umd.js b/test/form/paths/_expected/umd.js index 8da109e..e86e892 100644 --- a/test/form/paths/_expected/umd.js +++ b/test/form/paths/_expected/umd.js @@ -1,6 +1,6 @@ (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('https://npmcdn.com/foo')) : - typeof define === 'function' && define.amd ? define(['https://npmcdn.com/foo'], factory) : + typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('https://unpkg.com/foo')) : + typeof define === 'function' && define.amd ? define(['https://unpkg.com/foo'], factory) : (factory(global.foo)); }(this, (function (foo) { 'use strict'; diff --git a/test/form/removes-existing-sourcemap-comments/_expected/amd.js b/test/form/removes-existing-sourcemap-comments/_expected/amd.js index e614344..99bdf35 100644 --- a/test/form/removes-existing-sourcemap-comments/_expected/amd.js +++ b/test/form/removes-existing-sourcemap-comments/_expected/amd.js @@ -1,6 +1,6 @@ define(function () { 'use strict'; - function foo () { + var foo = function () { return 42; } diff --git a/test/form/removes-existing-sourcemap-comments/_expected/cjs.js b/test/form/removes-existing-sourcemap-comments/_expected/cjs.js index d7a8df2..8735c93 100644 --- a/test/form/removes-existing-sourcemap-comments/_expected/cjs.js +++ b/test/form/removes-existing-sourcemap-comments/_expected/cjs.js @@ -1,6 +1,6 @@ 'use strict'; -function foo () { +var foo = function () { return 42; } diff --git a/test/form/removes-existing-sourcemap-comments/_expected/es.js b/test/form/removes-existing-sourcemap-comments/_expected/es.js index c458828..2a09cfb 100644 --- a/test/form/removes-existing-sourcemap-comments/_expected/es.js +++ b/test/form/removes-existing-sourcemap-comments/_expected/es.js @@ -1,4 +1,4 @@ -function foo () { +var foo = function () { return 42; } diff --git a/test/form/removes-existing-sourcemap-comments/_expected/iife.js b/test/form/removes-existing-sourcemap-comments/_expected/iife.js index 5867d0f..6381613 100644 --- a/test/form/removes-existing-sourcemap-comments/_expected/iife.js +++ b/test/form/removes-existing-sourcemap-comments/_expected/iife.js @@ -1,7 +1,7 @@ (function () { 'use strict'; - function foo () { + var foo = function () { return 42; } diff --git a/test/form/removes-existing-sourcemap-comments/_expected/umd.js b/test/form/removes-existing-sourcemap-comments/_expected/umd.js index bbfb959..b291566 100644 --- a/test/form/removes-existing-sourcemap-comments/_expected/umd.js +++ b/test/form/removes-existing-sourcemap-comments/_expected/umd.js @@ -4,10 +4,10 @@ (factory()); }(this, (function () { 'use strict'; - function foo () { + var foo = function () { return 42; } console.log( foo() ); -}))); \ No newline at end of file +}))); diff --git a/test/form/self-calling-function-with-effects/_config.js b/test/form/self-calling-function-with-effects/_config.js new file mode 100644 index 0000000..d20f89b --- /dev/null +++ b/test/form/self-calling-function-with-effects/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'discards a self-calling function with side-effects' +}; diff --git a/test/form/self-calling-function-with-effects/_expected/amd.js b/test/form/self-calling-function-with-effects/_expected/amd.js new file mode 100644 index 0000000..ad714bd --- /dev/null +++ b/test/form/self-calling-function-with-effects/_expected/amd.js @@ -0,0 +1,20 @@ +define(function () { 'use strict'; + + function foo ( x ) { + effect( x ); + if ( x > 0 ) foo( x - 1 ); + } + + function bar ( x ) { + effect( x ); + if ( x > 0 ) baz( x ); + } + + function baz ( x ) { + bar( x - 1 ); + } + + foo( 10 ); + bar( 10 ); + +}); \ No newline at end of file diff --git a/test/form/self-calling-function-with-effects/_expected/cjs.js b/test/form/self-calling-function-with-effects/_expected/cjs.js new file mode 100644 index 0000000..83a7a20 --- /dev/null +++ b/test/form/self-calling-function-with-effects/_expected/cjs.js @@ -0,0 +1,18 @@ +'use strict'; + +function foo ( x ) { + effect( x ); + if ( x > 0 ) foo( x - 1 ); +} + +function bar ( x ) { + effect( x ); + if ( x > 0 ) baz( x ); +} + +function baz ( x ) { + bar( x - 1 ); +} + +foo( 10 ); +bar( 10 ); \ No newline at end of file diff --git a/test/form/self-calling-function-with-effects/_expected/es.js b/test/form/self-calling-function-with-effects/_expected/es.js new file mode 100644 index 0000000..8e997b2 --- /dev/null +++ b/test/form/self-calling-function-with-effects/_expected/es.js @@ -0,0 +1,16 @@ +function foo ( x ) { + effect( x ); + if ( x > 0 ) foo( x - 1 ); +} + +function bar ( x ) { + effect( x ); + if ( x > 0 ) baz( x ); +} + +function baz ( x ) { + bar( x - 1 ); +} + +foo( 10 ); +bar( 10 ); \ No newline at end of file diff --git a/test/form/self-calling-function-with-effects/_expected/iife.js b/test/form/self-calling-function-with-effects/_expected/iife.js new file mode 100644 index 0000000..bb848ad --- /dev/null +++ b/test/form/self-calling-function-with-effects/_expected/iife.js @@ -0,0 +1,21 @@ +(function () { + 'use strict'; + + function foo ( x ) { + effect( x ); + if ( x > 0 ) foo( x - 1 ); + } + + function bar ( x ) { + effect( x ); + if ( x > 0 ) baz( x ); + } + + function baz ( x ) { + bar( x - 1 ); + } + + foo( 10 ); + bar( 10 ); + +}()); \ No newline at end of file diff --git a/test/form/self-calling-function-with-effects/_expected/umd.js b/test/form/self-calling-function-with-effects/_expected/umd.js new file mode 100644 index 0000000..a9fd5b2 --- /dev/null +++ b/test/form/self-calling-function-with-effects/_expected/umd.js @@ -0,0 +1,24 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + function foo ( x ) { + effect( x ); + if ( x > 0 ) foo( x - 1 ); + } + + function bar ( x ) { + effect( x ); + if ( x > 0 ) baz( x ); + } + + function baz ( x ) { + bar( x - 1 ); + } + + foo( 10 ); + bar( 10 ); + +}))); \ No newline at end of file diff --git a/test/form/self-calling-function-with-effects/main.js b/test/form/self-calling-function-with-effects/main.js new file mode 100644 index 0000000..7ec54d9 --- /dev/null +++ b/test/form/self-calling-function-with-effects/main.js @@ -0,0 +1,16 @@ +function foo ( x ) { + effect( x ); + if ( x > 0 ) foo( x - 1 ); +} + +function bar ( x ) { + effect( x ); + if ( x > 0 ) baz( x ); +} + +function baz ( x ) { + bar( x - 1 ); +} + +foo( 10 ); +bar( 10 ); diff --git a/test/form/self-calling-function/_config.js b/test/form/self-calling-function/_config.js new file mode 100644 index 0000000..3acb040 --- /dev/null +++ b/test/form/self-calling-function/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'discards a self-calling function without side-effects' +}; diff --git a/test/form/self-calling-function/_expected/amd.js b/test/form/self-calling-function/_expected/amd.js new file mode 100644 index 0000000..ec759b1 --- /dev/null +++ b/test/form/self-calling-function/_expected/amd.js @@ -0,0 +1,5 @@ +define(function () { 'use strict'; + + + +}); \ No newline at end of file diff --git a/test/form/self-calling-function/_expected/cjs.js b/test/form/self-calling-function/_expected/cjs.js new file mode 100644 index 0000000..eb109ab --- /dev/null +++ b/test/form/self-calling-function/_expected/cjs.js @@ -0,0 +1,2 @@ +'use strict'; + diff --git a/test/form/self-calling-function/_expected/es.js b/test/form/self-calling-function/_expected/es.js new file mode 100644 index 0000000..e69de29 diff --git a/test/form/self-calling-function/_expected/iife.js b/test/form/self-calling-function/_expected/iife.js new file mode 100644 index 0000000..f3d1016 --- /dev/null +++ b/test/form/self-calling-function/_expected/iife.js @@ -0,0 +1,6 @@ +(function () { + 'use strict'; + + + +}()); \ No newline at end of file diff --git a/test/form/self-calling-function/_expected/umd.js b/test/form/self-calling-function/_expected/umd.js new file mode 100644 index 0000000..d561e69 --- /dev/null +++ b/test/form/self-calling-function/_expected/umd.js @@ -0,0 +1,9 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + + +}))); \ No newline at end of file diff --git a/test/form/self-calling-function/main.js b/test/form/self-calling-function/main.js new file mode 100644 index 0000000..76ff633 --- /dev/null +++ b/test/form/self-calling-function/main.js @@ -0,0 +1,14 @@ +function foo ( x ) { + if ( x > 0 ) foo( x - 1 ); +} + +function bar ( x ) { + if ( x > 0 ) baz( x ); +} + +function baz ( x ) { + bar( x - 1 ); +} + +foo( 10 ); +bar( 10 ); diff --git a/test/form/side-effect-k/_expected/amd.js b/test/form/side-effect-k/_expected/amd.js index e903a01..abfcb98 100644 --- a/test/form/side-effect-k/_expected/amd.js +++ b/test/form/side-effect-k/_expected/amd.js @@ -25,4 +25,4 @@ define(function () { 'use strict'; return x; -}); \ No newline at end of file +}); diff --git a/test/form/side-effect-k/_expected/cjs.js b/test/form/side-effect-k/_expected/cjs.js index 7ee74b0..8219021 100644 --- a/test/form/side-effect-k/_expected/cjs.js +++ b/test/form/side-effect-k/_expected/cjs.js @@ -23,4 +23,4 @@ function augment ( x ) { function x () {} augment( x.prototype ); -module.exports = x; \ No newline at end of file +module.exports = x; diff --git a/test/form/side-effect-k/_expected/es.js b/test/form/side-effect-k/_expected/es.js index 0cbda65..8647986 100644 --- a/test/form/side-effect-k/_expected/es.js +++ b/test/form/side-effect-k/_expected/es.js @@ -21,4 +21,4 @@ function augment ( x ) { function x () {} augment( x.prototype ); -export default x; \ No newline at end of file +export default x; diff --git a/test/form/side-effect-k/_expected/iife.js b/test/form/side-effect-k/_expected/iife.js index bccee01..eeb57d4 100644 --- a/test/form/side-effect-k/_expected/iife.js +++ b/test/form/side-effect-k/_expected/iife.js @@ -26,4 +26,4 @@ var myBundle = (function () { return x; -}()); \ No newline at end of file +}()); diff --git a/test/form/side-effect-k/_expected/umd.js b/test/form/side-effect-k/_expected/umd.js index 3d6da0b..302275a 100644 --- a/test/form/side-effect-k/_expected/umd.js +++ b/test/form/side-effect-k/_expected/umd.js @@ -29,4 +29,4 @@ return x; -}))); \ No newline at end of file +}))); diff --git a/test/form/side-effect-p/_config.js b/test/form/side-effect-p/_config.js new file mode 100644 index 0000000..7a8d84b --- /dev/null +++ b/test/form/side-effect-p/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'detects mutation of globals' +}; diff --git a/test/form/side-effect-p/_expected/amd.js b/test/form/side-effect-p/_expected/amd.js new file mode 100644 index 0000000..35e8591 --- /dev/null +++ b/test/form/side-effect-p/_expected/amd.js @@ -0,0 +1,11 @@ +define(function () { 'use strict'; + + var bool = true; + + const hs = document.documentElement.style; + + if ( bool ) { + hs.color = "#222" + } + +}); \ No newline at end of file diff --git a/test/form/side-effect-p/_expected/cjs.js b/test/form/side-effect-p/_expected/cjs.js new file mode 100644 index 0000000..08dc383 --- /dev/null +++ b/test/form/side-effect-p/_expected/cjs.js @@ -0,0 +1,9 @@ +'use strict'; + +var bool = true; + +const hs = document.documentElement.style; + +if ( bool ) { + hs.color = "#222" +} \ No newline at end of file diff --git a/test/form/side-effect-p/_expected/es.js b/test/form/side-effect-p/_expected/es.js new file mode 100644 index 0000000..8e80199 --- /dev/null +++ b/test/form/side-effect-p/_expected/es.js @@ -0,0 +1,7 @@ +var bool = true; + +const hs = document.documentElement.style; + +if ( bool ) { + hs.color = "#222" +} \ No newline at end of file diff --git a/test/form/side-effect-p/_expected/iife.js b/test/form/side-effect-p/_expected/iife.js new file mode 100644 index 0000000..4ea2e35 --- /dev/null +++ b/test/form/side-effect-p/_expected/iife.js @@ -0,0 +1,12 @@ +(function () { + 'use strict'; + + var bool = true; + + const hs = document.documentElement.style; + + if ( bool ) { + hs.color = "#222" + } + +}()); \ No newline at end of file diff --git a/test/form/side-effect-p/_expected/umd.js b/test/form/side-effect-p/_expected/umd.js new file mode 100644 index 0000000..f99ce83 --- /dev/null +++ b/test/form/side-effect-p/_expected/umd.js @@ -0,0 +1,15 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + var bool = true; + + const hs = document.documentElement.style; + + if ( bool ) { + hs.color = "#222" + } + +}))); \ No newline at end of file diff --git a/test/form/side-effect-p/bool.js b/test/form/side-effect-p/bool.js new file mode 100644 index 0000000..ff3177b --- /dev/null +++ b/test/form/side-effect-p/bool.js @@ -0,0 +1 @@ +export default true; diff --git a/test/form/side-effect-p/main.js b/test/form/side-effect-p/main.js new file mode 100644 index 0000000..1ee7791 --- /dev/null +++ b/test/form/side-effect-p/main.js @@ -0,0 +1,7 @@ +import bool from './bool'; + +const hs = document.documentElement.style; + +if ( bool ) { + hs.color = "#222" +} diff --git a/test/form/side-effect-q/_config.js b/test/form/side-effect-q/_config.js new file mode 100644 index 0000000..944a58a --- /dev/null +++ b/test/form/side-effect-q/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'discards effects in conditional expressions with known test values' +}; diff --git a/test/form/side-effect-q/_expected/amd.js b/test/form/side-effect-q/_expected/amd.js new file mode 100644 index 0000000..ec759b1 --- /dev/null +++ b/test/form/side-effect-q/_expected/amd.js @@ -0,0 +1,5 @@ +define(function () { 'use strict'; + + + +}); \ No newline at end of file diff --git a/test/form/side-effect-q/_expected/cjs.js b/test/form/side-effect-q/_expected/cjs.js new file mode 100644 index 0000000..eb109ab --- /dev/null +++ b/test/form/side-effect-q/_expected/cjs.js @@ -0,0 +1,2 @@ +'use strict'; + diff --git a/test/form/side-effect-q/_expected/es.js b/test/form/side-effect-q/_expected/es.js new file mode 100644 index 0000000..e69de29 diff --git a/test/form/side-effect-q/_expected/iife.js b/test/form/side-effect-q/_expected/iife.js new file mode 100644 index 0000000..f3d1016 --- /dev/null +++ b/test/form/side-effect-q/_expected/iife.js @@ -0,0 +1,6 @@ +(function () { + 'use strict'; + + + +}()); \ No newline at end of file diff --git a/test/form/side-effect-q/_expected/umd.js b/test/form/side-effect-q/_expected/umd.js new file mode 100644 index 0000000..d561e69 --- /dev/null +++ b/test/form/side-effect-q/_expected/umd.js @@ -0,0 +1,9 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + + +}))); \ No newline at end of file diff --git a/test/form/side-effect-q/main.js b/test/form/side-effect-q/main.js new file mode 100644 index 0000000..2272edb --- /dev/null +++ b/test/form/side-effect-q/main.js @@ -0,0 +1,5 @@ +var x = true ? foo () : bar(); + +function foo () { + return 'should be removed, because x is unused'; +} diff --git a/test/form/side-effect-r/_config.js b/test/form/side-effect-r/_config.js new file mode 100644 index 0000000..2c3f470 --- /dev/null +++ b/test/form/side-effect-r/_config.js @@ -0,0 +1,4 @@ +module.exports = { + skip: true, + description: 'discards unused function expression assigned to a variable that calls itself and a global' +}; diff --git a/test/form/side-effect-r/main.js b/test/form/side-effect-r/main.js new file mode 100644 index 0000000..716ee31 --- /dev/null +++ b/test/form/side-effect-r/main.js @@ -0,0 +1,7 @@ +var foo = function foo() { + if ( whatever ) { + foo(); + } else { + bar(); + } +}; diff --git a/test/form/skips-dead-branches-b/_config.js b/test/form/skips-dead-branches-b/_config.js new file mode 100644 index 0000000..22c399c --- /dev/null +++ b/test/form/skips-dead-branches-b/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'skips a dead branch (b)' +}; diff --git a/test/form/skips-dead-branches-b/_expected/amd.js b/test/form/skips-dead-branches-b/_expected/amd.js new file mode 100644 index 0000000..c192900 --- /dev/null +++ b/test/form/skips-dead-branches-b/_expected/amd.js @@ -0,0 +1,9 @@ +define(function () { 'use strict'; + + function bar () { + console.log( 'this should be included' ); + } + + bar(); + +}); diff --git a/test/form/skips-dead-branches-b/_expected/cjs.js b/test/form/skips-dead-branches-b/_expected/cjs.js new file mode 100644 index 0000000..b3e974b --- /dev/null +++ b/test/form/skips-dead-branches-b/_expected/cjs.js @@ -0,0 +1,7 @@ +'use strict'; + +function bar () { + console.log( 'this should be included' ); +} + +bar(); diff --git a/test/form/skips-dead-branches-b/_expected/es.js b/test/form/skips-dead-branches-b/_expected/es.js new file mode 100644 index 0000000..ec9eb20 --- /dev/null +++ b/test/form/skips-dead-branches-b/_expected/es.js @@ -0,0 +1,5 @@ +function bar () { + console.log( 'this should be included' ); +} + +bar(); diff --git a/test/form/skips-dead-branches-b/_expected/iife.js b/test/form/skips-dead-branches-b/_expected/iife.js new file mode 100644 index 0000000..3b894d9 --- /dev/null +++ b/test/form/skips-dead-branches-b/_expected/iife.js @@ -0,0 +1,10 @@ +(function () { + 'use strict'; + + function bar () { + console.log( 'this should be included' ); + } + + bar(); + +}()); diff --git a/test/form/skips-dead-branches-b/_expected/umd.js b/test/form/skips-dead-branches-b/_expected/umd.js new file mode 100644 index 0000000..b3926b0 --- /dev/null +++ b/test/form/skips-dead-branches-b/_expected/umd.js @@ -0,0 +1,13 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + function bar () { + console.log( 'this should be included' ); + } + + bar(); + +}))); diff --git a/test/function/skips-dead-branches-b/main.js b/test/form/skips-dead-branches-b/main.js similarity index 100% rename from test/function/skips-dead-branches-b/main.js rename to test/form/skips-dead-branches-b/main.js diff --git a/test/form/skips-dead-branches-c/_config.js b/test/form/skips-dead-branches-c/_config.js new file mode 100644 index 0000000..8674142 --- /dev/null +++ b/test/form/skips-dead-branches-c/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'skips a dead branch (c)' +}; diff --git a/test/form/skips-dead-branches-c/_expected/amd.js b/test/form/skips-dead-branches-c/_expected/amd.js new file mode 100644 index 0000000..9dc50f5 --- /dev/null +++ b/test/form/skips-dead-branches-c/_expected/amd.js @@ -0,0 +1,9 @@ +define(function () { 'use strict'; + + function bar () { + console.log( 'this should be included' ); + } + + bar(); + +}); \ No newline at end of file diff --git a/test/form/skips-dead-branches-c/_expected/cjs.js b/test/form/skips-dead-branches-c/_expected/cjs.js new file mode 100644 index 0000000..d6aa952 --- /dev/null +++ b/test/form/skips-dead-branches-c/_expected/cjs.js @@ -0,0 +1,7 @@ +'use strict'; + +function bar () { + console.log( 'this should be included' ); +} + +bar(); \ No newline at end of file diff --git a/test/form/skips-dead-branches-c/_expected/es.js b/test/form/skips-dead-branches-c/_expected/es.js new file mode 100644 index 0000000..f39bfec --- /dev/null +++ b/test/form/skips-dead-branches-c/_expected/es.js @@ -0,0 +1,5 @@ +function bar () { + console.log( 'this should be included' ); +} + +bar(); \ No newline at end of file diff --git a/test/form/skips-dead-branches-c/_expected/iife.js b/test/form/skips-dead-branches-c/_expected/iife.js new file mode 100644 index 0000000..8d6dcb5 --- /dev/null +++ b/test/form/skips-dead-branches-c/_expected/iife.js @@ -0,0 +1,10 @@ +(function () { + 'use strict'; + + function bar () { + console.log( 'this should be included' ); + } + + bar(); + +}()); \ No newline at end of file diff --git a/test/form/skips-dead-branches-c/_expected/umd.js b/test/form/skips-dead-branches-c/_expected/umd.js new file mode 100644 index 0000000..67588d4 --- /dev/null +++ b/test/form/skips-dead-branches-c/_expected/umd.js @@ -0,0 +1,13 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + function bar () { + console.log( 'this should be included' ); + } + + bar(); + +}))); \ No newline at end of file diff --git a/test/function/skips-dead-branches-c/main.js b/test/form/skips-dead-branches-c/main.js similarity index 100% rename from test/function/skips-dead-branches-c/main.js rename to test/form/skips-dead-branches-c/main.js diff --git a/test/form/skips-dead-branches-d/_config.js b/test/form/skips-dead-branches-d/_config.js new file mode 100644 index 0000000..9779139 --- /dev/null +++ b/test/form/skips-dead-branches-d/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'skips a dead branch (d)' +}; diff --git a/test/form/skips-dead-branches-d/_expected/amd.js b/test/form/skips-dead-branches-d/_expected/amd.js new file mode 100644 index 0000000..9dc50f5 --- /dev/null +++ b/test/form/skips-dead-branches-d/_expected/amd.js @@ -0,0 +1,9 @@ +define(function () { 'use strict'; + + function bar () { + console.log( 'this should be included' ); + } + + bar(); + +}); \ No newline at end of file diff --git a/test/form/skips-dead-branches-d/_expected/cjs.js b/test/form/skips-dead-branches-d/_expected/cjs.js new file mode 100644 index 0000000..d6aa952 --- /dev/null +++ b/test/form/skips-dead-branches-d/_expected/cjs.js @@ -0,0 +1,7 @@ +'use strict'; + +function bar () { + console.log( 'this should be included' ); +} + +bar(); \ No newline at end of file diff --git a/test/form/skips-dead-branches-d/_expected/es.js b/test/form/skips-dead-branches-d/_expected/es.js new file mode 100644 index 0000000..f39bfec --- /dev/null +++ b/test/form/skips-dead-branches-d/_expected/es.js @@ -0,0 +1,5 @@ +function bar () { + console.log( 'this should be included' ); +} + +bar(); \ No newline at end of file diff --git a/test/form/skips-dead-branches-d/_expected/iife.js b/test/form/skips-dead-branches-d/_expected/iife.js new file mode 100644 index 0000000..8d6dcb5 --- /dev/null +++ b/test/form/skips-dead-branches-d/_expected/iife.js @@ -0,0 +1,10 @@ +(function () { + 'use strict'; + + function bar () { + console.log( 'this should be included' ); + } + + bar(); + +}()); \ No newline at end of file diff --git a/test/form/skips-dead-branches-d/_expected/umd.js b/test/form/skips-dead-branches-d/_expected/umd.js new file mode 100644 index 0000000..67588d4 --- /dev/null +++ b/test/form/skips-dead-branches-d/_expected/umd.js @@ -0,0 +1,13 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + function bar () { + console.log( 'this should be included' ); + } + + bar(); + +}))); \ No newline at end of file diff --git a/test/function/skips-dead-branches-d/main.js b/test/form/skips-dead-branches-d/main.js similarity index 100% rename from test/function/skips-dead-branches-d/main.js rename to test/form/skips-dead-branches-d/main.js diff --git a/test/form/skips-dead-branches-e/_config.js b/test/form/skips-dead-branches-e/_config.js new file mode 100644 index 0000000..d290c80 --- /dev/null +++ b/test/form/skips-dead-branches-e/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'skips a dead branch (e)' +}; diff --git a/test/form/skips-dead-branches-e/_expected/amd.js b/test/form/skips-dead-branches-e/_expected/amd.js new file mode 100644 index 0000000..9dc50f5 --- /dev/null +++ b/test/form/skips-dead-branches-e/_expected/amd.js @@ -0,0 +1,9 @@ +define(function () { 'use strict'; + + function bar () { + console.log( 'this should be included' ); + } + + bar(); + +}); \ No newline at end of file diff --git a/test/form/skips-dead-branches-e/_expected/cjs.js b/test/form/skips-dead-branches-e/_expected/cjs.js new file mode 100644 index 0000000..d6aa952 --- /dev/null +++ b/test/form/skips-dead-branches-e/_expected/cjs.js @@ -0,0 +1,7 @@ +'use strict'; + +function bar () { + console.log( 'this should be included' ); +} + +bar(); \ No newline at end of file diff --git a/test/form/skips-dead-branches-e/_expected/es.js b/test/form/skips-dead-branches-e/_expected/es.js new file mode 100644 index 0000000..f39bfec --- /dev/null +++ b/test/form/skips-dead-branches-e/_expected/es.js @@ -0,0 +1,5 @@ +function bar () { + console.log( 'this should be included' ); +} + +bar(); \ No newline at end of file diff --git a/test/form/skips-dead-branches-e/_expected/iife.js b/test/form/skips-dead-branches-e/_expected/iife.js new file mode 100644 index 0000000..8d6dcb5 --- /dev/null +++ b/test/form/skips-dead-branches-e/_expected/iife.js @@ -0,0 +1,10 @@ +(function () { + 'use strict'; + + function bar () { + console.log( 'this should be included' ); + } + + bar(); + +}()); \ No newline at end of file diff --git a/test/form/skips-dead-branches-e/_expected/umd.js b/test/form/skips-dead-branches-e/_expected/umd.js new file mode 100644 index 0000000..67588d4 --- /dev/null +++ b/test/form/skips-dead-branches-e/_expected/umd.js @@ -0,0 +1,13 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + function bar () { + console.log( 'this should be included' ); + } + + bar(); + +}))); \ No newline at end of file diff --git a/test/function/skips-dead-branches-e/main.js b/test/form/skips-dead-branches-e/main.js similarity index 100% rename from test/function/skips-dead-branches-e/main.js rename to test/form/skips-dead-branches-e/main.js diff --git a/test/form/skips-dead-branches-f/_config.js b/test/form/skips-dead-branches-f/_config.js new file mode 100644 index 0000000..9ac1d23 --- /dev/null +++ b/test/form/skips-dead-branches-f/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'skips a dead branch (f)' +}; diff --git a/test/form/skips-dead-branches-f/_expected/amd.js b/test/form/skips-dead-branches-f/_expected/amd.js new file mode 100644 index 0000000..9dc50f5 --- /dev/null +++ b/test/form/skips-dead-branches-f/_expected/amd.js @@ -0,0 +1,9 @@ +define(function () { 'use strict'; + + function bar () { + console.log( 'this should be included' ); + } + + bar(); + +}); \ No newline at end of file diff --git a/test/form/skips-dead-branches-f/_expected/cjs.js b/test/form/skips-dead-branches-f/_expected/cjs.js new file mode 100644 index 0000000..d6aa952 --- /dev/null +++ b/test/form/skips-dead-branches-f/_expected/cjs.js @@ -0,0 +1,7 @@ +'use strict'; + +function bar () { + console.log( 'this should be included' ); +} + +bar(); \ No newline at end of file diff --git a/test/form/skips-dead-branches-f/_expected/es.js b/test/form/skips-dead-branches-f/_expected/es.js new file mode 100644 index 0000000..f39bfec --- /dev/null +++ b/test/form/skips-dead-branches-f/_expected/es.js @@ -0,0 +1,5 @@ +function bar () { + console.log( 'this should be included' ); +} + +bar(); \ No newline at end of file diff --git a/test/form/skips-dead-branches-f/_expected/iife.js b/test/form/skips-dead-branches-f/_expected/iife.js new file mode 100644 index 0000000..8d6dcb5 --- /dev/null +++ b/test/form/skips-dead-branches-f/_expected/iife.js @@ -0,0 +1,10 @@ +(function () { + 'use strict'; + + function bar () { + console.log( 'this should be included' ); + } + + bar(); + +}()); \ No newline at end of file diff --git a/test/form/skips-dead-branches-f/_expected/umd.js b/test/form/skips-dead-branches-f/_expected/umd.js new file mode 100644 index 0000000..67588d4 --- /dev/null +++ b/test/form/skips-dead-branches-f/_expected/umd.js @@ -0,0 +1,13 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + function bar () { + console.log( 'this should be included' ); + } + + bar(); + +}))); \ No newline at end of file diff --git a/test/function/skips-dead-branches-f/main.js b/test/form/skips-dead-branches-f/main.js similarity index 100% rename from test/function/skips-dead-branches-f/main.js rename to test/form/skips-dead-branches-f/main.js diff --git a/test/form/skips-dead-branches-g/_config.js b/test/form/skips-dead-branches-g/_config.js new file mode 100644 index 0000000..f592e05 --- /dev/null +++ b/test/form/skips-dead-branches-g/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'skips a dead conditional expression branch (g)' +}; diff --git a/test/form/skips-dead-branches-g/_expected/amd.js b/test/form/skips-dead-branches-g/_expected/amd.js new file mode 100644 index 0000000..41371dd --- /dev/null +++ b/test/form/skips-dead-branches-g/_expected/amd.js @@ -0,0 +1,10 @@ +define(function () { 'use strict'; + + var a = 0; + var b = 1; + var x = a; + var y = b; + + console.log( x + y ); + +}); \ No newline at end of file diff --git a/test/form/skips-dead-branches-g/_expected/cjs.js b/test/form/skips-dead-branches-g/_expected/cjs.js new file mode 100644 index 0000000..44cffbf --- /dev/null +++ b/test/form/skips-dead-branches-g/_expected/cjs.js @@ -0,0 +1,8 @@ +'use strict'; + +var a = 0; +var b = 1; +var x = a; +var y = b; + +console.log( x + y ); \ No newline at end of file diff --git a/test/form/skips-dead-branches-g/_expected/es.js b/test/form/skips-dead-branches-g/_expected/es.js new file mode 100644 index 0000000..93d5198 --- /dev/null +++ b/test/form/skips-dead-branches-g/_expected/es.js @@ -0,0 +1,6 @@ +var a = 0; +var b = 1; +var x = a; +var y = b; + +console.log( x + y ); \ No newline at end of file diff --git a/test/form/skips-dead-branches-g/_expected/iife.js b/test/form/skips-dead-branches-g/_expected/iife.js new file mode 100644 index 0000000..ce654f4 --- /dev/null +++ b/test/form/skips-dead-branches-g/_expected/iife.js @@ -0,0 +1,11 @@ +(function () { + 'use strict'; + + var a = 0; + var b = 1; + var x = a; + var y = b; + + console.log( x + y ); + +}()); \ No newline at end of file diff --git a/test/form/skips-dead-branches-g/_expected/umd.js b/test/form/skips-dead-branches-g/_expected/umd.js new file mode 100644 index 0000000..40a0a54 --- /dev/null +++ b/test/form/skips-dead-branches-g/_expected/umd.js @@ -0,0 +1,14 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + var a = 0; + var b = 1; + var x = a; + var y = b; + + console.log( x + y ); + +}))); \ No newline at end of file diff --git a/test/form/skips-dead-branches-g/main.js b/test/form/skips-dead-branches-g/main.js new file mode 100644 index 0000000..352481e --- /dev/null +++ b/test/form/skips-dead-branches-g/main.js @@ -0,0 +1,8 @@ +var a = 0; +var b = 1; +var c = 2; + +var x = true ? a : b; +var y = false ? c : b; + +console.log( x + y ); diff --git a/test/form/skips-dead-branches/_config.js b/test/form/skips-dead-branches/_config.js new file mode 100644 index 0000000..078856d --- /dev/null +++ b/test/form/skips-dead-branches/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'skips a dead branch' +}; diff --git a/test/form/skips-dead-branches/_expected/amd.js b/test/form/skips-dead-branches/_expected/amd.js new file mode 100644 index 0000000..9dc50f5 --- /dev/null +++ b/test/form/skips-dead-branches/_expected/amd.js @@ -0,0 +1,9 @@ +define(function () { 'use strict'; + + function bar () { + console.log( 'this should be included' ); + } + + bar(); + +}); \ No newline at end of file diff --git a/test/form/skips-dead-branches/_expected/cjs.js b/test/form/skips-dead-branches/_expected/cjs.js new file mode 100644 index 0000000..d6aa952 --- /dev/null +++ b/test/form/skips-dead-branches/_expected/cjs.js @@ -0,0 +1,7 @@ +'use strict'; + +function bar () { + console.log( 'this should be included' ); +} + +bar(); \ No newline at end of file diff --git a/test/form/skips-dead-branches/_expected/es.js b/test/form/skips-dead-branches/_expected/es.js new file mode 100644 index 0000000..f39bfec --- /dev/null +++ b/test/form/skips-dead-branches/_expected/es.js @@ -0,0 +1,5 @@ +function bar () { + console.log( 'this should be included' ); +} + +bar(); \ No newline at end of file diff --git a/test/form/skips-dead-branches/_expected/iife.js b/test/form/skips-dead-branches/_expected/iife.js new file mode 100644 index 0000000..8d6dcb5 --- /dev/null +++ b/test/form/skips-dead-branches/_expected/iife.js @@ -0,0 +1,10 @@ +(function () { + 'use strict'; + + function bar () { + console.log( 'this should be included' ); + } + + bar(); + +}()); \ No newline at end of file diff --git a/test/form/skips-dead-branches/_expected/umd.js b/test/form/skips-dead-branches/_expected/umd.js new file mode 100644 index 0000000..67588d4 --- /dev/null +++ b/test/form/skips-dead-branches/_expected/umd.js @@ -0,0 +1,13 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + function bar () { + console.log( 'this should be included' ); + } + + bar(); + +}))); \ No newline at end of file diff --git a/test/function/skips-dead-branches/main.js b/test/form/skips-dead-branches/main.js similarity index 100% rename from test/function/skips-dead-branches/main.js rename to test/form/skips-dead-branches/main.js diff --git a/test/form/spacing-after-function-with-semicolon/_expected/amd.js b/test/form/spacing-after-function-with-semicolon/_expected/amd.js index 37f42f3..4db965c 100644 --- a/test/form/spacing-after-function-with-semicolon/_expected/amd.js +++ b/test/form/spacing-after-function-with-semicolon/_expected/amd.js @@ -1,6 +1,6 @@ define(function () { 'use strict'; - function x () { return 'x' }; + function x () { return 'x' } assert.equal( x(), 'x' ); diff --git a/test/form/spacing-after-function-with-semicolon/_expected/cjs.js b/test/form/spacing-after-function-with-semicolon/_expected/cjs.js index 503ea73..bac28c1 100644 --- a/test/form/spacing-after-function-with-semicolon/_expected/cjs.js +++ b/test/form/spacing-after-function-with-semicolon/_expected/cjs.js @@ -1,5 +1,5 @@ 'use strict'; -function x () { return 'x' }; +function x () { return 'x' } assert.equal( x(), 'x' ); diff --git a/test/form/spacing-after-function-with-semicolon/_expected/es.js b/test/form/spacing-after-function-with-semicolon/_expected/es.js index 3b3f5e7..f5b5029 100644 --- a/test/form/spacing-after-function-with-semicolon/_expected/es.js +++ b/test/form/spacing-after-function-with-semicolon/_expected/es.js @@ -1,3 +1,3 @@ -function x () { return 'x' }; +function x () { return 'x' } assert.equal( x(), 'x' ); diff --git a/test/form/spacing-after-function-with-semicolon/_expected/iife.js b/test/form/spacing-after-function-with-semicolon/_expected/iife.js index 7ff78ec..f8d6992 100644 --- a/test/form/spacing-after-function-with-semicolon/_expected/iife.js +++ b/test/form/spacing-after-function-with-semicolon/_expected/iife.js @@ -1,7 +1,7 @@ (function () { 'use strict'; - function x () { return 'x' }; + function x () { return 'x' } assert.equal( x(), 'x' ); diff --git a/test/form/spacing-after-function-with-semicolon/_expected/umd.js b/test/form/spacing-after-function-with-semicolon/_expected/umd.js index 8b7954a..75d31a1 100644 --- a/test/form/spacing-after-function-with-semicolon/_expected/umd.js +++ b/test/form/spacing-after-function-with-semicolon/_expected/umd.js @@ -4,8 +4,8 @@ (factory()); }(this, (function () { 'use strict'; - function x () { return 'x' }; + function x () { return 'x' } assert.equal( x(), 'x' ); -}))); \ No newline at end of file +}))); diff --git a/test/form/string-indentation-b/_expected/amd.js b/test/form/string-indentation-b/_expected/amd.js index f646cb2..2eb44c9 100644 --- a/test/form/string-indentation-b/_expected/amd.js +++ b/test/form/string-indentation-b/_expected/amd.js @@ -2,7 +2,8 @@ define(function () { 'use strict'; var a = 'a'; var b = 'b'; + assert.equal( a, 'a' ); assert.equal( b, 'b' ); -}); \ No newline at end of file +}); diff --git a/test/form/string-indentation-b/_expected/cjs.js b/test/form/string-indentation-b/_expected/cjs.js index 7c799d3..8432e6e 100644 --- a/test/form/string-indentation-b/_expected/cjs.js +++ b/test/form/string-indentation-b/_expected/cjs.js @@ -2,5 +2,6 @@ var a = 'a'; var b = 'b'; + assert.equal( a, 'a' ); -assert.equal( b, 'b' ); \ No newline at end of file +assert.equal( b, 'b' ); diff --git a/test/form/string-indentation-b/_expected/es.js b/test/form/string-indentation-b/_expected/es.js index 7ab432c..6cd3b5a 100644 --- a/test/form/string-indentation-b/_expected/es.js +++ b/test/form/string-indentation-b/_expected/es.js @@ -1,4 +1,5 @@ var a = 'a'; var b = 'b'; + assert.equal( a, 'a' ); -assert.equal( b, 'b' ); \ No newline at end of file +assert.equal( b, 'b' ); diff --git a/test/form/string-indentation-b/_expected/iife.js b/test/form/string-indentation-b/_expected/iife.js index ae8c969..6907cdb 100644 --- a/test/form/string-indentation-b/_expected/iife.js +++ b/test/form/string-indentation-b/_expected/iife.js @@ -3,7 +3,8 @@ var a = 'a'; var b = 'b'; + assert.equal( a, 'a' ); assert.equal( b, 'b' ); -}()); \ No newline at end of file +}()); diff --git a/test/form/string-indentation-b/_expected/umd.js b/test/form/string-indentation-b/_expected/umd.js index 9ebd756..47597be 100644 --- a/test/form/string-indentation-b/_expected/umd.js +++ b/test/form/string-indentation-b/_expected/umd.js @@ -6,7 +6,8 @@ var a = 'a'; var b = 'b'; + assert.equal( a, 'a' ); assert.equal( b, 'b' ); -}))); \ No newline at end of file +}))); diff --git a/test/form/transform-bundle-plugin-options/_config.js b/test/form/transform-bundle-plugin-options/_config.js new file mode 100644 index 0000000..417e38f --- /dev/null +++ b/test/form/transform-bundle-plugin-options/_config.js @@ -0,0 +1,12 @@ +module.exports = { + description: 'plugin .transformBundle gets passed options', + options: { + plugins: [ + { + transformBundle: function (code, options) { + return JSON.stringify(options); + } + } + ] + } +}; diff --git a/test/form/transform-bundle-plugin-options/_expected/amd.js b/test/form/transform-bundle-plugin-options/_expected/amd.js new file mode 100644 index 0000000..69904ec --- /dev/null +++ b/test/form/transform-bundle-plugin-options/_expected/amd.js @@ -0,0 +1 @@ +{"format":"amd"} diff --git a/test/form/transform-bundle-plugin-options/_expected/cjs.js b/test/form/transform-bundle-plugin-options/_expected/cjs.js new file mode 100644 index 0000000..72dd9eb --- /dev/null +++ b/test/form/transform-bundle-plugin-options/_expected/cjs.js @@ -0,0 +1 @@ +{"format":"cjs"} diff --git a/test/form/transform-bundle-plugin-options/_expected/es.js b/test/form/transform-bundle-plugin-options/_expected/es.js new file mode 100644 index 0000000..3a0913b --- /dev/null +++ b/test/form/transform-bundle-plugin-options/_expected/es.js @@ -0,0 +1 @@ +{"format":"es"} diff --git a/test/form/transform-bundle-plugin-options/_expected/iife.js b/test/form/transform-bundle-plugin-options/_expected/iife.js new file mode 100644 index 0000000..9bdbe7b --- /dev/null +++ b/test/form/transform-bundle-plugin-options/_expected/iife.js @@ -0,0 +1 @@ +{"format":"iife"} diff --git a/test/form/transform-bundle-plugin-options/_expected/umd.js b/test/form/transform-bundle-plugin-options/_expected/umd.js new file mode 100644 index 0000000..c7dad30 --- /dev/null +++ b/test/form/transform-bundle-plugin-options/_expected/umd.js @@ -0,0 +1 @@ +{"format":"umd"} diff --git a/test/form/transform-bundle-plugin-options/main.js b/test/form/transform-bundle-plugin-options/main.js new file mode 100644 index 0000000..934dee7 --- /dev/null +++ b/test/form/transform-bundle-plugin-options/main.js @@ -0,0 +1 @@ +console.log( 1 + 1 ); diff --git a/test/form/unused-var/_config.js b/test/form/unused-var/_config.js new file mode 100644 index 0000000..b89eb86 --- /dev/null +++ b/test/form/unused-var/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'omits unused var declaration' +}; diff --git a/test/form/unused-var/_expected/amd.js b/test/form/unused-var/_expected/amd.js new file mode 100644 index 0000000..babb23a --- /dev/null +++ b/test/form/unused-var/_expected/amd.js @@ -0,0 +1,7 @@ +define(function () { 'use strict'; + + var foo = 'lol'; + + console.log( foo ); + +}); \ No newline at end of file diff --git a/test/form/unused-var/_expected/cjs.js b/test/form/unused-var/_expected/cjs.js new file mode 100644 index 0000000..8628267 --- /dev/null +++ b/test/form/unused-var/_expected/cjs.js @@ -0,0 +1,5 @@ +'use strict'; + +var foo = 'lol'; + +console.log( foo ); \ No newline at end of file diff --git a/test/form/unused-var/_expected/es.js b/test/form/unused-var/_expected/es.js new file mode 100644 index 0000000..2e13c75 --- /dev/null +++ b/test/form/unused-var/_expected/es.js @@ -0,0 +1,3 @@ +var foo = 'lol'; + +console.log( foo ); \ No newline at end of file diff --git a/test/form/unused-var/_expected/iife.js b/test/form/unused-var/_expected/iife.js new file mode 100644 index 0000000..a9b45df --- /dev/null +++ b/test/form/unused-var/_expected/iife.js @@ -0,0 +1,8 @@ +(function () { + 'use strict'; + + var foo = 'lol'; + + console.log( foo ); + +}()); \ No newline at end of file diff --git a/test/form/unused-var/_expected/umd.js b/test/form/unused-var/_expected/umd.js new file mode 100644 index 0000000..71ba44f --- /dev/null +++ b/test/form/unused-var/_expected/umd.js @@ -0,0 +1,11 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + + var foo = 'lol'; + + console.log( foo ); + +}))); \ No newline at end of file diff --git a/test/form/unused-var/foo.js b/test/form/unused-var/foo.js new file mode 100644 index 0000000..3b60a87 --- /dev/null +++ b/test/form/unused-var/foo.js @@ -0,0 +1,6 @@ +var foo = 'lol'; +var bar = 'wut'; + +var baz = bar || foo; + +export { foo }; diff --git a/test/form/unused-var/main.js b/test/form/unused-var/main.js new file mode 100644 index 0000000..1b3e409 --- /dev/null +++ b/test/form/unused-var/main.js @@ -0,0 +1,2 @@ +import { foo } from './foo.js'; +console.log( foo ); diff --git a/test/function/consistent-renaming-b/_config.js b/test/function/consistent-renaming-b/_config.js index 1b8b173..f1b3ee6 100644 --- a/test/function/consistent-renaming-b/_config.js +++ b/test/function/consistent-renaming-b/_config.js @@ -1,3 +1,3 @@ module.exports = { description: 'consistent renaming test b' -}; \ No newline at end of file +}; diff --git a/test/function/consistent-renaming-b/altdir/two.js b/test/function/consistent-renaming-b/altdir/two.js index b97aab5..bd84240 100644 --- a/test/function/consistent-renaming-b/altdir/two.js +++ b/test/function/consistent-renaming-b/altdir/two.js @@ -1,5 +1,6 @@ function two () { + // imported as _two by subdir/two.js return 2; } -export { two }; \ No newline at end of file +export { two }; diff --git a/test/function/consistent-renaming-b/subdir/one.js b/test/function/consistent-renaming-b/subdir/one.js index 0d1985f..26a060c 100644 --- a/test/function/consistent-renaming-b/subdir/one.js +++ b/test/function/consistent-renaming-b/subdir/one.js @@ -1,5 +1,5 @@ import { two } from '../altdir/two'; export default function one () { - return two() - 1; + return two() - 1; } diff --git a/test/function/consistent-renaming-b/subdir/two.js b/test/function/consistent-renaming-b/subdir/two.js index b0004b7..c31c2ff 100644 --- a/test/function/consistent-renaming-b/subdir/two.js +++ b/test/function/consistent-renaming-b/subdir/two.js @@ -1,5 +1,6 @@ import { two as _two } from '../altdir/two'; export default function two () { - return _two(); + // imported as Two by main.js + return _two(); } diff --git a/test/function/cycles-pathological/_config.js b/test/function/cycles-pathological/_config.js index 8d44d9a..f6d8526 100644 --- a/test/function/cycles-pathological/_config.js +++ b/test/function/cycles-pathological/_config.js @@ -1,17 +1,11 @@ var assert = require( 'assert' ); -var warned; - module.exports = { + skip: true, description: 'resolves pathological cyclical dependencies gracefully', buble: true, - options: { - onwarn: function ( message ) { - assert.ok( /Module .+B\.js may be unable to evaluate without .+A\.js, but is included first due to a cyclical dependency. Consider swapping the import statements in .+main\.js to ensure correct ordering/.test( message ) ); - warned = true; - } - }, - runtimeError: function () { - assert.ok( warned ); + warnings: warnings => { + assert.equal( warnings.length, warnings ); + assert.ok( /Module .+B\.js may be unable to evaluate without .+A\.js, but is included first due to a cyclical dependency. Consider swapping the import statements in .+main\.js to ensure correct ordering/.test( warnings[0] ) ); } }; diff --git a/test/function/deconflicts-classes/_config.js b/test/function/deconflicts-classes/_config.js new file mode 100644 index 0000000..633aacc --- /dev/null +++ b/test/function/deconflicts-classes/_config.js @@ -0,0 +1,4 @@ +module.exports = { + description: 'deconflicts top-level classes', + buble: true +}; diff --git a/test/function/deconflicts-classes/a.js b/test/function/deconflicts-classes/a.js new file mode 100644 index 0000000..8f88528 --- /dev/null +++ b/test/function/deconflicts-classes/a.js @@ -0,0 +1,2 @@ +var Foo = function Foo () {}; +export { Foo }; diff --git a/test/function/deconflicts-classes/b.js b/test/function/deconflicts-classes/b.js new file mode 100644 index 0000000..6f6f410 --- /dev/null +++ b/test/function/deconflicts-classes/b.js @@ -0,0 +1,2 @@ +class Foo {}; +export { Foo }; diff --git a/test/function/deconflicts-classes/main.js b/test/function/deconflicts-classes/main.js new file mode 100644 index 0000000..89ff7de --- /dev/null +++ b/test/function/deconflicts-classes/main.js @@ -0,0 +1,4 @@ +import { Foo as A } from './a.js'; +import { Foo as B } from './b.js'; + +assert.notEqual( A, B ); diff --git a/test/function/double-default-export/_config.js b/test/function/double-default-export/_config.js index 26e86e7..d5f5254 100644 --- a/test/function/double-default-export/_config.js +++ b/test/function/double-default-export/_config.js @@ -1,8 +1,9 @@ +const path = require( 'path' ); const assert = require( 'assert' ); module.exports = { description: 'throws on double default exports', error: err => { - assert.equal( err.message, 'A module can only have one default export' ); + assert.equal( err.message, `Duplicate export 'default' (2:7) in ${path.resolve(__dirname, 'foo.js')}` ); } }; diff --git a/test/function/double-named-export/_config.js b/test/function/double-named-export/_config.js index 577b421..169ce76 100644 --- a/test/function/double-named-export/_config.js +++ b/test/function/double-named-export/_config.js @@ -1,8 +1,9 @@ +const path = require( 'path' ); const assert = require( 'assert' ); module.exports = { description: 'throws on duplicate named exports', error: err => { - assert.equal( err.message, `A module cannot have multiple exports with the same name ('foo')` ); + assert.equal( err.message, `Duplicate export 'foo' (3:9) in ${path.resolve(__dirname, 'foo.js')}` ); } }; diff --git a/test/function/double-named-reexport/_config.js b/test/function/double-named-reexport/_config.js index 577b421..169ce76 100644 --- a/test/function/double-named-reexport/_config.js +++ b/test/function/double-named-reexport/_config.js @@ -1,8 +1,9 @@ +const path = require( 'path' ); const assert = require( 'assert' ); module.exports = { description: 'throws on duplicate named exports', error: err => { - assert.equal( err.message, `A module cannot have multiple exports with the same name ('foo')` ); + assert.equal( err.message, `Duplicate export 'foo' (3:9) in ${path.resolve(__dirname, 'foo.js')}` ); } }; diff --git a/test/function/export-two-ways-default-b/_config.js b/test/function/export-two-ways-default-b/_config.js index 79fd012..4fa4888 100644 --- a/test/function/export-two-ways-default-b/_config.js +++ b/test/function/export-two-ways-default-b/_config.js @@ -1,4 +1,3 @@ module.exports = { - skip: true, description: 'side-effects are preserved if subject is exported in multiple ways, even if default export has no direct link to original (#733)' }; diff --git a/test/function/identifiers-in-template-literals/_config.js b/test/function/identifiers-in-template-literals/_config.js new file mode 100644 index 0000000..51b503d --- /dev/null +++ b/test/function/identifiers-in-template-literals/_config.js @@ -0,0 +1,4 @@ +module.exports = { + description: 'identifiers in template literals are rendered correctly', + buble: true +}; diff --git a/test/function/identifiers-in-template-literals/a.js b/test/function/identifiers-in-template-literals/a.js new file mode 100644 index 0000000..26a1db8 --- /dev/null +++ b/test/function/identifiers-in-template-literals/a.js @@ -0,0 +1,7 @@ +function x ( keypath ) { + return 'a'; +} + +export default function a () { + return x(); +} diff --git a/test/function/identifiers-in-template-literals/b.js b/test/function/identifiers-in-template-literals/b.js new file mode 100644 index 0000000..7511028 --- /dev/null +++ b/test/function/identifiers-in-template-literals/b.js @@ -0,0 +1,7 @@ +function x ( name ) { + return 'b'; +} + +export default function b () { + return `${x()}`; +} diff --git a/test/function/identifiers-in-template-literals/main.js b/test/function/identifiers-in-template-literals/main.js new file mode 100644 index 0000000..eedb4fb --- /dev/null +++ b/test/function/identifiers-in-template-literals/main.js @@ -0,0 +1,5 @@ +import a from './a.js'; +import b from './b.js'; + +assert.equal( a(), 'a' ); +assert.equal( b(), 'b' ); diff --git a/test/function/if-statement-with-assignment/_config.js b/test/function/if-statement-with-assignment/_config.js new file mode 100644 index 0000000..6c573d5 --- /dev/null +++ b/test/function/if-statement-with-assignment/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'update assignments to names are preserved (#930)' +}; diff --git a/test/function/if-statement-with-assignment/main.js b/test/function/if-statement-with-assignment/main.js new file mode 100644 index 0000000..e885c22 --- /dev/null +++ b/test/function/if-statement-with-assignment/main.js @@ -0,0 +1,6 @@ +var result = 0; +if ( Math.random() <= 1 ) { + if ( Math.random() <= 1 ) result += 1; +} + +assert.equal( result, 1 ); diff --git a/test/function/if-statement-with-update/_config.js b/test/function/if-statement-with-update/_config.js new file mode 100644 index 0000000..8601e12 --- /dev/null +++ b/test/function/if-statement-with-update/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'updates to names are preserved (#930)' +}; diff --git a/test/function/if-statement-with-update/main.js b/test/function/if-statement-with-update/main.js new file mode 100644 index 0000000..7ab3055 --- /dev/null +++ b/test/function/if-statement-with-update/main.js @@ -0,0 +1,6 @@ +var result = 0; +if ( Math.random() <= 1 ) { + if ( Math.random() <= 1 ) ++result; +} + +assert.equal( result, 1 ); diff --git a/test/function/iife-strong-dependencies/_config.js b/test/function/iife-strong-dependencies/_config.js index f1d7ee1..110fa77 100644 --- a/test/function/iife-strong-dependencies/_config.js +++ b/test/function/iife-strong-dependencies/_config.js @@ -1,16 +1,10 @@ var assert = require( 'assert' ); -var warned; - module.exports = { + skip: true, description: 'does not treat references inside IIFEs as weak dependencies', // edge case encountered in THREE.js codebase - options: { - onwarn: function ( message ) { - assert.ok( /Module .+D\.js may be unable to evaluate without .+C\.js, but is included first due to a cyclical dependency. Consider swapping the import statements in .+main\.js to ensure correct ordering/.test( message ) ); - warned = true; - } - }, - runtimeError: function () { - assert.ok( warned ); + warnings: warnings => { + assert.equal( warnings.length, 1 ); + assert.ok( /Module .+D\.js may be unable to evaluate without .+C\.js, but is included first due to a cyclical dependency. Consider swapping the import statements in .+main\.js to ensure correct ordering/.test( warnings[0] ) ); } }; diff --git a/test/function/import-dependency-in-same-module/_config.js b/test/function/import-dependency-in-same-module/_config.js index 622d7d9..a08ff3c 100644 --- a/test/function/import-dependency-in-same-module/_config.js +++ b/test/function/import-dependency-in-same-module/_config.js @@ -1,3 +1,3 @@ module.exports = { description: 'imports a dependency from the same module' -}; \ No newline at end of file +}; diff --git a/test/function/includes-superclass/_config.js b/test/function/includes-superclass/_config.js new file mode 100644 index 0000000..e226472 --- /dev/null +++ b/test/function/includes-superclass/_config.js @@ -0,0 +1,4 @@ +module.exports = { + description: 'includes superclass (#932)', + buble: true +}; diff --git a/test/function/includes-superclass/base.js b/test/function/includes-superclass/base.js new file mode 100644 index 0000000..c374794 --- /dev/null +++ b/test/function/includes-superclass/base.js @@ -0,0 +1,5 @@ +export class Base { + foo () { + return true; + } +} diff --git a/test/function/includes-superclass/main.js b/test/function/includes-superclass/main.js new file mode 100644 index 0000000..bfb78e8 --- /dev/null +++ b/test/function/includes-superclass/main.js @@ -0,0 +1,6 @@ +import { Thing } from './thing'; + +const thing = new Thing(); + +assert.ok( thing.foo() ); +assert.ok( thing.bar() ); diff --git a/test/function/includes-superclass/thing.js b/test/function/includes-superclass/thing.js new file mode 100644 index 0000000..97b3be8 --- /dev/null +++ b/test/function/includes-superclass/thing.js @@ -0,0 +1,7 @@ +import { Base } from './base.js'; + +export class Thing extends Base { + bar () { + return true; + } +} diff --git a/test/function/module-tree/_config.js b/test/function/module-tree/_config.js new file mode 100644 index 0000000..553ab2b --- /dev/null +++ b/test/function/module-tree/_config.js @@ -0,0 +1,37 @@ +const path = require( 'path' ); +const assert = require( 'assert' ); + +module.exports = { + description: 'bundle.modules includes dependencies (#903)', + bundle ( bundle ) { + const modules = bundle.modules.map( module => { + return { + id: path.relative( __dirname, module.id ), + dependencies: module.dependencies.map( id => path.relative( __dirname, id ) ) + }; + }); + + assert.deepEqual( modules, [ + { + id: path.normalize( 'nested/qux.js' ), + dependencies: [] + }, + { + id: path.normalize( 'nested/baz.js' ), + dependencies: [ path.normalize( 'nested/qux.js' ) ] + }, + { + id: 'bar.js', + dependencies: [ path.normalize( 'nested/baz.js' ) ] + }, + { + id: 'foo.js', + dependencies: [ 'bar.js' ] + }, + { + id: 'main.js', + dependencies: [ 'foo.js', 'bar.js' ] + } + ]); + } +}; diff --git a/test/function/module-tree/bar.js b/test/function/module-tree/bar.js new file mode 100644 index 0000000..76340f6 --- /dev/null +++ b/test/function/module-tree/bar.js @@ -0,0 +1 @@ +import './nested/baz.js'; diff --git a/test/function/module-tree/foo.js b/test/function/module-tree/foo.js new file mode 100644 index 0000000..1df02c2 --- /dev/null +++ b/test/function/module-tree/foo.js @@ -0,0 +1 @@ +import './bar.js'; diff --git a/test/function/module-tree/main.js b/test/function/module-tree/main.js new file mode 100644 index 0000000..f257022 --- /dev/null +++ b/test/function/module-tree/main.js @@ -0,0 +1,2 @@ +import './foo.js'; +import './bar.js'; diff --git a/test/function/module-tree/nested/baz.js b/test/function/module-tree/nested/baz.js new file mode 100644 index 0000000..0ff2bf9 --- /dev/null +++ b/test/function/module-tree/nested/baz.js @@ -0,0 +1 @@ +import qux from './qux.js'; diff --git a/test/function/module-tree/nested/qux.js b/test/function/module-tree/nested/qux.js new file mode 100644 index 0000000..6d70fec --- /dev/null +++ b/test/function/module-tree/nested/qux.js @@ -0,0 +1 @@ +export default 'whatever'; diff --git a/test/function/namespacing-collisions-2/Material.js b/test/function/namespacing-collisions-2/Material.js new file mode 100644 index 0000000..b6d1881 --- /dev/null +++ b/test/function/namespacing-collisions-2/Material.js @@ -0,0 +1,3 @@ +export function Material() { + return 'Material'; +} diff --git a/test/function/namespacing-collisions-2/MaterialAgain.js b/test/function/namespacing-collisions-2/MaterialAgain.js new file mode 100644 index 0000000..170cfff --- /dev/null +++ b/test/function/namespacing-collisions-2/MaterialAgain.js @@ -0,0 +1,3 @@ +export function MaterialAgain() { + return 'MaterialAgain'; +} diff --git a/test/function/namespacing-collisions-2/Something.js b/test/function/namespacing-collisions-2/Something.js new file mode 100644 index 0000000..64cbdca --- /dev/null +++ b/test/function/namespacing-collisions-2/Something.js @@ -0,0 +1,6 @@ +import * as Material from './Material'; + +export function Something() { + console.log(Material); + return 'Something'; +} \ No newline at end of file diff --git a/test/function/namespacing-collisions-2/SomethingAgain.js b/test/function/namespacing-collisions-2/SomethingAgain.js new file mode 100644 index 0000000..1efaca7 --- /dev/null +++ b/test/function/namespacing-collisions-2/SomethingAgain.js @@ -0,0 +1,6 @@ +import * as Material from './MaterialAgain'; + +export function SomethingAgain() { + console.log(Material); + return 'SomethingAgain'; +} \ No newline at end of file diff --git a/test/function/namespacing-collisions-2/_config.js b/test/function/namespacing-collisions-2/_config.js new file mode 100644 index 0000000..4684296 --- /dev/null +++ b/test/function/namespacing-collisions-2/_config.js @@ -0,0 +1,8 @@ +var assert = require( 'assert' ); + +module.exports = { + description: 'correctly namespaces when using * exports, take two (#910)', + exports: function ( exports ) { + assert.deepEqual( exports, ['Material', 'MaterialAgain', 'Something', 'SomethingAgain'] ); + } +}; diff --git a/test/function/namespacing-collisions-2/main.js b/test/function/namespacing-collisions-2/main.js new file mode 100644 index 0000000..c69a6e4 --- /dev/null +++ b/test/function/namespacing-collisions-2/main.js @@ -0,0 +1,7 @@ +import { Something } from './Something'; +import { SomethingAgain } from './SomethingAgain'; +import { Material } from './Material'; +import { MaterialAgain } from './MaterialAgain'; + +var result = [Material(), MaterialAgain(), Something(), SomethingAgain()] +export default result; diff --git a/test/function/namespacing-collisions/Material.js b/test/function/namespacing-collisions/Material.js new file mode 100644 index 0000000..b6d1881 --- /dev/null +++ b/test/function/namespacing-collisions/Material.js @@ -0,0 +1,3 @@ +export function Material() { + return 'Material'; +} diff --git a/test/function/namespacing-collisions/Something.js b/test/function/namespacing-collisions/Something.js new file mode 100644 index 0000000..64cbdca --- /dev/null +++ b/test/function/namespacing-collisions/Something.js @@ -0,0 +1,6 @@ +import * as Material from './Material'; + +export function Something() { + console.log(Material); + return 'Something'; +} \ No newline at end of file diff --git a/test/function/namespacing-collisions/_config.js b/test/function/namespacing-collisions/_config.js new file mode 100644 index 0000000..02c772f --- /dev/null +++ b/test/function/namespacing-collisions/_config.js @@ -0,0 +1,8 @@ +var assert = require( 'assert' ); + +module.exports = { + description: 'correctly namespaces when using * exports (#910)', + exports: function ( exports ) { + assert.deepEqual( exports, [ 'Material', 'Something' ] ); + } +}; diff --git a/test/function/namespacing-collisions/main.js b/test/function/namespacing-collisions/main.js new file mode 100644 index 0000000..8746123 --- /dev/null +++ b/test/function/namespacing-collisions/main.js @@ -0,0 +1,5 @@ +import { Something } from './Something'; +import { Material } from './Material'; + +var result = [Material(), Something()] +export default result; \ No newline at end of file diff --git a/test/function/namespacing-in-sub-functions/_config.js b/test/function/namespacing-in-sub-functions/_config.js new file mode 100644 index 0000000..025b117 --- /dev/null +++ b/test/function/namespacing-in-sub-functions/_config.js @@ -0,0 +1,8 @@ +var assert = require( 'assert' ); + +module.exports = { + description: 'correctly namespaces sub-functions (#910)', + exports: function ( exports ) { + assert.equal( exports, 'foobar' ); + } +}; diff --git a/test/function/namespacing-in-sub-functions/main.js b/test/function/namespacing-in-sub-functions/main.js new file mode 100644 index 0000000..ac9d9da --- /dev/null +++ b/test/function/namespacing-in-sub-functions/main.js @@ -0,0 +1,11 @@ +import { problematicFunc as otherFunc } from './problematicFunc'; +function innerFunc() { + function problematicFunc () { + return otherFunc(); + } + return problematicFunc(); +} + +var res = innerFunc(); + +export default res; \ No newline at end of file diff --git a/test/function/namespacing-in-sub-functions/problematicFunc.js b/test/function/namespacing-in-sub-functions/problematicFunc.js new file mode 100644 index 0000000..1fbecc0 --- /dev/null +++ b/test/function/namespacing-in-sub-functions/problematicFunc.js @@ -0,0 +1,5 @@ +function problematicFunc() { + return 'foobar'; +} + +export { problematicFunc }; \ No newline at end of file diff --git a/test/function/no-imports/_config.js b/test/function/no-imports/_config.js index 47ae3de..be6c448 100644 --- a/test/function/no-imports/_config.js +++ b/test/function/no-imports/_config.js @@ -1,3 +1,3 @@ module.exports = { description: 'creates a bundle from a module with no imports' -}; \ No newline at end of file +}; diff --git a/test/function/paths-are-case-sensitive/Foo.js b/test/function/paths-are-case-sensitive/Foo.js new file mode 100644 index 0000000..81baecb --- /dev/null +++ b/test/function/paths-are-case-sensitive/Foo.js @@ -0,0 +1,3 @@ +export default function () { + assert.ok( false ); +} diff --git a/test/function/paths-are-case-sensitive/_config.js b/test/function/paths-are-case-sensitive/_config.js new file mode 100644 index 0000000..3f81b80 --- /dev/null +++ b/test/function/paths-are-case-sensitive/_config.js @@ -0,0 +1,8 @@ +var assert = require( 'assert' ); + +module.exports = { + description: 'insists on correct casing for imports', + error: function ( err ) { + assert.ok( /Could not resolve/.test( err.message ) ); + } +}; diff --git a/test/function/paths-are-case-sensitive/main.js b/test/function/paths-are-case-sensitive/main.js new file mode 100644 index 0000000..e1fb5c4 --- /dev/null +++ b/test/function/paths-are-case-sensitive/main.js @@ -0,0 +1,3 @@ +import foo from './foo.js'; + +foo(); diff --git a/test/function/reassign-import-fails/_config.js b/test/function/reassign-import-fails/_config.js index e01d090..22591fe 100644 --- a/test/function/reassign-import-fails/_config.js +++ b/test/function/reassign-import-fails/_config.js @@ -4,9 +4,9 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows assignments to imported bindings', error: function ( err ) { + assert.ok( /Illegal reassignment/.test( err.message ) ); assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); assert.deepEqual( err.loc, { line: 8, column: 0 }); - assert.ok( /Illegal reassignment/.test( err.message ) ); } }; diff --git a/test/function/reassign-parameter/_config.js b/test/function/reassign-parameter/_config.js new file mode 100644 index 0000000..1701a6f --- /dev/null +++ b/test/function/reassign-parameter/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'parameters can be reassigned/updated' +}; diff --git a/test/function/reassign-parameter/main.js b/test/function/reassign-parameter/main.js new file mode 100644 index 0000000..6c68061 --- /dev/null +++ b/test/function/reassign-parameter/main.js @@ -0,0 +1,7 @@ +function numbers ( i ) { + var array = new Array( i ); + while ( i-- ) array[i] = i + 1; + return array; +} + +assert.deepEqual( numbers( 5 ), [ 1, 2, 3, 4, 5 ] ); diff --git a/test/function/rename-conditional-expression-children/_config.js b/test/function/rename-conditional-expression-children/_config.js new file mode 100644 index 0000000..d159c95 --- /dev/null +++ b/test/function/rename-conditional-expression-children/_config.js @@ -0,0 +1,4 @@ +module.exports = { + description: 'correctly renders children of ConditionalExpressions', + buble: true +}; diff --git a/test/function/rename-conditional-expression-children/foo.js b/test/function/rename-conditional-expression-children/foo.js new file mode 100644 index 0000000..7fe4a4d --- /dev/null +++ b/test/function/rename-conditional-expression-children/foo.js @@ -0,0 +1,2 @@ +export const bar = 42; +export const baz = 43; diff --git a/test/function/rename-conditional-expression-children/main.js b/test/function/rename-conditional-expression-children/main.js new file mode 100644 index 0000000..16bfa0f --- /dev/null +++ b/test/function/rename-conditional-expression-children/main.js @@ -0,0 +1,3 @@ +import * as foo from './foo.js'; + +assert.equal( true ? foo.bar : foo.baz, 42 ); diff --git a/test/function/skips-dead-branches-b/_config.js b/test/function/skips-dead-branches-b/_config.js deleted file mode 100644 index 9833915..0000000 --- a/test/function/skips-dead-branches-b/_config.js +++ /dev/null @@ -1,8 +0,0 @@ -var assert = require( 'assert' ); - -module.exports = { - description: 'skips a dead branch (b)', - code: function ( code ) { - assert.equal( code.indexOf( 'obj.foo = function' ), -1, code ); - } -}; diff --git a/test/function/skips-dead-branches-c/_config.js b/test/function/skips-dead-branches-c/_config.js deleted file mode 100644 index c925750..0000000 --- a/test/function/skips-dead-branches-c/_config.js +++ /dev/null @@ -1,8 +0,0 @@ -var assert = require( 'assert' ); - -module.exports = { - description: 'skips a dead branch (c)', - code: function ( code ) { - assert.equal( code.indexOf( 'obj.foo = function' ), -1, code ); - } -}; diff --git a/test/function/skips-dead-branches-d/_config.js b/test/function/skips-dead-branches-d/_config.js deleted file mode 100644 index 67c8046..0000000 --- a/test/function/skips-dead-branches-d/_config.js +++ /dev/null @@ -1,8 +0,0 @@ -var assert = require( 'assert' ); - -module.exports = { - description: 'skips a dead branch (d)', - code: function ( code ) { - assert.equal( code.indexOf( 'obj.foo = function' ), -1, code ); - } -}; diff --git a/test/function/skips-dead-branches-e/_config.js b/test/function/skips-dead-branches-e/_config.js deleted file mode 100644 index 5c6e6ab..0000000 --- a/test/function/skips-dead-branches-e/_config.js +++ /dev/null @@ -1,8 +0,0 @@ -var assert = require( 'assert' ); - -module.exports = { - description: 'skips a dead branch (e)', - code: function ( code ) { - assert.equal( code.indexOf( 'obj.foo = function' ), -1, code ); - } -}; diff --git a/test/function/skips-dead-branches-f/_config.js b/test/function/skips-dead-branches-f/_config.js deleted file mode 100644 index d829cef..0000000 --- a/test/function/skips-dead-branches-f/_config.js +++ /dev/null @@ -1,8 +0,0 @@ -var assert = require( 'assert' ); - -module.exports = { - description: 'skips a dead branch (f)', - code: function ( code ) { - assert.equal( code.indexOf( 'obj.foo = function' ), -1, code ); - } -}; diff --git a/test/function/skips-dead-branches-g/_config.js b/test/function/skips-dead-branches-g/_config.js deleted file mode 100644 index 90e7f11..0000000 --- a/test/function/skips-dead-branches-g/_config.js +++ /dev/null @@ -1,9 +0,0 @@ -var assert = require( 'assert' ); - -module.exports = { - description: 'skips a dead conditional expression branch (g)', - code: function ( code ) { - assert.ok( code.indexOf( 'var c = a;' ) >= 0, code ); - assert.ok( code.indexOf( 'var d = b;' ) >= 0, code ); - } -}; diff --git a/test/function/skips-dead-branches-g/main.js b/test/function/skips-dead-branches-g/main.js deleted file mode 100644 index 0c271f4..0000000 --- a/test/function/skips-dead-branches-g/main.js +++ /dev/null @@ -1,6 +0,0 @@ -var a = 0; -var b = 1; -var c = true ? a : b; -var d = false ? a : b; - -console.log( c + d ); diff --git a/test/function/skips-dead-branches/_config.js b/test/function/skips-dead-branches/_config.js deleted file mode 100644 index 9a0d225..0000000 --- a/test/function/skips-dead-branches/_config.js +++ /dev/null @@ -1,8 +0,0 @@ -var assert = require( 'assert' ); - -module.exports = { - description: 'skips a dead branch', - code: function ( code ) { - assert.equal( code.indexOf( 'obj.foo = function' ), -1, code ); - } -}; diff --git a/test/function/tracks-alias-mutations-b/_config.js b/test/function/tracks-alias-mutations-b/_config.js new file mode 100644 index 0000000..dde47e2 --- /dev/null +++ b/test/function/tracks-alias-mutations-b/_config.js @@ -0,0 +1,13 @@ +const assert = require( 'assert' ); + +const foo = {}; + +module.exports = { + description: 'tracks mutations of aliased objects', + context: { + foo + }, + exports () { + assert.equal( foo.x, 42 ); + } +}; diff --git a/test/function/tracks-alias-mutations-b/main.js b/test/function/tracks-alias-mutations-b/main.js new file mode 100644 index 0000000..81ef7f7 --- /dev/null +++ b/test/function/tracks-alias-mutations-b/main.js @@ -0,0 +1,2 @@ +var _foo = foo; +_foo.x = 42; diff --git a/test/function/tracks-alias-mutations/bar.js b/test/function/tracks-alias-mutations/bar.js index 4ec5140..88a92e5 100644 --- a/test/function/tracks-alias-mutations/bar.js +++ b/test/function/tracks-alias-mutations/bar.js @@ -1,6 +1,8 @@ import { foo } from './foo'; -var f = foo; -f.wasMutated = true; +var f = Math.random() <= 1 ? foo : {}; +var f2; +f2 = Math.random() <= 1 ? f : {}; +f2.wasMutated = true; export var bar = 'whatever'; diff --git a/test/function/vars-in-for-loop-head/_config.js b/test/function/vars-in-for-loop-head/_config.js new file mode 100644 index 0000000..166d6ee --- /dev/null +++ b/test/function/vars-in-for-loop-head/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'does not break apart vars in for loop head' +}; diff --git a/test/function/vars-in-for-loop-head/main.js b/test/function/vars-in-for-loop-head/main.js new file mode 100644 index 0000000..0b35f91 --- /dev/null +++ b/test/function/vars-in-for-loop-head/main.js @@ -0,0 +1,10 @@ +function clone ( things ) { + var result = []; + for ( var i = 0, list = things; i < list.length; i += 1 ) { + var thing = list[i]; + result.push( thing ); + } + return result; +} + +assert.deepEqual( clone([ 1, 2, 3 ]), [ 1, 2, 3 ] ); diff --git a/test/function/warn-on-unused-missing-imports/_config.js b/test/function/warn-on-unused-missing-imports/_config.js new file mode 100644 index 0000000..1fca512 --- /dev/null +++ b/test/function/warn-on-unused-missing-imports/_config.js @@ -0,0 +1,11 @@ +const path = require( 'path' ); +const assert = require( 'assert' ); + +module.exports = { + description: 'warns on missing (but unused) imports', + warnings: warnings => { + assert.deepEqual( warnings, [ + `Non-existent export 'b' is imported from ${path.resolve(__dirname, 'foo.js')} by ${path.resolve(__dirname, 'main.js')}` + ]); + } +}; diff --git a/test/function/warn-on-unused-missing-imports/foo.js b/test/function/warn-on-unused-missing-imports/foo.js new file mode 100644 index 0000000..18e60c8 --- /dev/null +++ b/test/function/warn-on-unused-missing-imports/foo.js @@ -0,0 +1 @@ +export var a = 42; diff --git a/test/function/warn-on-unused-missing-imports/main.js b/test/function/warn-on-unused-missing-imports/main.js new file mode 100644 index 0000000..7eaceb9 --- /dev/null +++ b/test/function/warn-on-unused-missing-imports/main.js @@ -0,0 +1,3 @@ +import { a, b } from './foo.js'; + +assert.equal( a, 42 ); diff --git a/test/test.js b/test/test.js index 4eb73a6..52cc00e 100644 --- a/test/test.js +++ b/test/test.js @@ -6,6 +6,7 @@ const sander = require( 'sander' ); const assert = require( 'assert' ); const { exec } = require( 'child_process' ); const buble = require( 'buble' ); +const acorn = require( 'acorn' ); const rollup = require( '../dist/rollup' ); const FUNCTION = path.resolve( __dirname, 'function' ); @@ -89,7 +90,26 @@ describe( 'rollup', function () { return rollup.rollup({ entry: 'x', plUgins: [] }).then( () => { throw new Error( 'Missing expected error' ); }, err => { - assert.equal( err.message, 'Unexpected key \'plUgins\' found, expected one of: acorn, banner, cache, context, dest, entry, exports, external, footer, format, globals, indent, intro, moduleId, moduleName, noConflict, onwarn, outro, paths, plugins, preferConst, sourceMap, sourceMapFile, targets, treeshake, useStrict' ); + assert.equal( err.message, 'Unexpected key \'plUgins\' found, expected one of: acorn, banner, cache, context, dest, entry, exports, external, footer, format, globals, indent, interop, intro, moduleId, moduleName, noConflict, onwarn, outro, paths, plugins, preferConst, sourceMap, sourceMapFile, targets, treeshake, useStrict' ); + }); + }); + + it( 'treats Literals as leaf nodes, even if first literal encountered is null', () => { + // this test has to be up here, otherwise the bug doesn't have + // an opportunity to present itself + return rollup.rollup({ + entry: 'x', + plugins: [ loader({ x: `var a = null; a = 'a string';` }) ] + }); + }); + + it( 'includes a newline at the end of the bundle', () => { + return rollup.rollup({ + entry: 'x', + plugins: [ loader({ x: `console.log( 42 );` }) ] + }).then( bundle => { + const { code } = bundle.generate({ format: 'iife' }); + assert.ok( code[ code.length - 1 ] === '\n' ); }); }); }); @@ -243,16 +263,16 @@ describe( 'rollup', function () { } } + if ( config.show || unintendedError ) { + console.log( result.code + '\n\n\n' ); + } + if ( config.warnings ) { config.warnings( warnings ); } else if ( warnings.length ) { throw new Error( `Got unexpected warnings:\n${warnings.join('\n')}` ); } - if ( config.show || unintendedError ) { - console.log( code + '\n\n\n' ); - } - if ( config.solo ) console.groupEnd(); if ( unintendedError ) throw unintendedError; @@ -290,10 +310,13 @@ describe( 'rollup', function () { } }, config.options ); - ( config.skip ? describe.skip : config.solo ? describe.only : describe)( dir, () => { + ( config.skip ? describe.skip : config.solo ? describe.only : describe )( dir, () => { + let promise; + const createBundle = () => ( promise || ( promise = rollup.rollup( options ) ) ); + PROFILES.forEach( profile => { it( 'generates ' + profile.format, () => { - return rollup.rollup( options ).then( bundle => { + return createBundle().then( bundle => { const options = extend( {}, config.options, { dest: FORM + '/' + dir + '/_actual/' + profile.format + '.js', format: profile.format @@ -584,6 +607,21 @@ describe( 'rollup', function () { assert.equal( executeBundle( bundle ), 21 ); }); }); + + it( 'keeps ASTs between runs', () => { + return rollup.rollup({ + entry: 'entry', + plugins: [ plugin ] + }).then( bundle => { + const asts = {}; + bundle.modules.forEach( module => { + asts[ module.id ] = module.ast; + }); + + assert.deepEqual( asts.entry, acorn.parse( modules.entry, { sourceType: 'module' }) ); + assert.deepEqual( asts.foo, acorn.parse( modules.foo, { sourceType: 'module' }) ); + }); + }); }); describe( 'hooks', () => {