diff --git a/src/Bundle.js b/src/Bundle.js index c4c5edf..8bc5826 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -73,14 +73,9 @@ export default class Bundle { declaration.use(); }); - let settled = false; - while ( !settled ) { - settled = true; - - this.modules.forEach( module => { - if ( module.markAllSideEffects() ) settled = false; - }); - } + this.modules.forEach( module => { + module.markAllSideEffects(); + }); this.orderedModules = this.sort(); this.deconflict(); diff --git a/src/Declaration.js b/src/Declaration.js index caefdca..7fb7c86 100644 --- a/src/Declaration.js +++ b/src/Declaration.js @@ -1,10 +1,5 @@ -import { walk } from 'estree-walker'; -import { keys } from './utils/object'; - -const modifierNodes = { - AssignmentExpression: 'left', - UpdateExpression: 'argument' -}; +import { blank, keys } from './utils/object.js'; +import testForSideEffects from './utils/testForSideEffects.js'; export default class Declaration { constructor ( node ) { @@ -22,6 +17,7 @@ export default class Declaration { this.name = null; this.isReassigned = false; + this.mutations = []; this.aliases = []; } @@ -34,54 +30,195 @@ export default class Declaration { this.name = reference.name; // TODO handle differences of opinion if ( reference.isReassignment ) this.isReassigned = true; + if ( reference.isMutation && !~this.mutations.indexOf( reference.statement ) ) { + this.mutations.push( reference.statement ); + } } - mutates () { - // returns a list of things this function mutates when it gets called - if ( !this._mutates ) { - let mutatedNames = {}; + hasSideEffect () { + return testForSideEffects( this.functionBody, this.statement.scope, this.statement ); + } - const statement = this.statement; - let scope = statement.scope; + render ( es6 ) { + if ( es6 ) return this.name; + if ( !this.isReassigned || !this.isExported ) return this.name; - const addNode = node => { - while ( node.type === 'MemberExpression' ) node = node.object; - if ( node.type === 'Identifier' ) mutatedNames[ node.name ] = true; - }; + return `exports.${this.name}`; + } - walk( this.functionBody, { - enter ( node ) { - if ( node._scope ) scope = node._scope; + use () { + this.isUsed = true; + if ( this.statement ) this.statement.mark(); - if ( node.type in modifierNodes ) { - addNode( node[ modifierNodes[ node.type ] ] ); - } else if ( node.type === 'CallExpression' ) { - addNode( node.callee ); - } - }, + this.aliases.forEach( alias => alias.use() ); + } +} - leave ( node ) { - if ( node._scope ) scope = scope.parent; - } - }); +export class SyntheticDefaultDeclaration { + constructor ( node, statement, name ) { + this.node = node; + this.statement = statement; + this.name = name; - this._mutates = keys( mutatedNames ); - } + this.original = null; + this.isExported = false; + this.aliases = []; + } - return this._mutates; + addAlias ( declaration ) { + this.aliases.push( declaration ); } - render ( es6 ) { - if ( es6 ) return this.name; - if ( !this.isReassigned || !this.isExported ) return this.name; + addReference ( reference ) { + // Don't change the name to `default`; it's not a valid identifier name. + if ( reference.name === 'default' ) return; - return `exports.${this.name}`; + reference.declaration = this; + this.name = reference.name; + } + + bind ( declaration ) { + this.original = declaration; + } + + hasSideEffect () { + if ( this.original ) { + return this.original.hasSideEffect(); + } + + if ( /FunctionExpression/.test( this.node.declaration.type ) ) { + return testForSideEffects( this.node.declaration.body, this.statement.scope, this.statement ); + } + } + + render () { + return !this.original || this.original.isReassigned ? + this.name : + this.original.render(); } use () { this.isUsed = true; - if ( this.statement ) this.statement.mark(); + this.statement.mark(); + + if ( this.original ) this.original.use(); this.aliases.forEach( alias => alias.use() ); } } + +export class SyntheticNamespaceDeclaration { + constructor ( module ) { + this.module = module; + this.name = null; + + this.needsNamespaceBlock = false; + this.aliases = []; + + this.originals = blank(); + module.getExports().forEach( name => { + this.originals[ name ] = module.traceExport( name ); + }); + } + + addAlias ( declaration ) { + this.aliases.push( declaration ); + } + + addReference ( reference ) { + // if we have e.g. `foo.bar`, we can optimise + // the reference by pointing directly to `bar` + if ( reference.parts.length ) { + reference.name = reference.parts.shift(); + + reference.end += reference.name.length + 1; // TODO this is brittle + + 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; + } + + original.addReference( reference ); + return; + } + + // 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 ); + } + + reference.declaration = this; + this.name = reference.name; + } + + renderBlock ( 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}${name}: ${original.render()}`; + }); + + return `var ${this.render()} = Object.freeze({\n${members.join( ',\n' )}\n});\n\n`; + } + + render () { + return this.name; + } + + use () { + keys( this.originals ).forEach( name => { + this.originals[ name ].use(); + }); + + this.aliases.forEach( alias => alias.use() ); + } +} + +export class ExternalDeclaration { + constructor ( module, name ) { + this.module = module; + this.name = name; + this.isExternal = true; + } + + addAlias () { + // noop + } + + addReference ( reference ) { + reference.declaration = this; + + if ( this.name === 'default' || this.name === '*' ) { + this.module.suggestName( reference.name ); + } + } + + render ( es6 ) { + if ( this.name === '*' ) { + return this.module.name; + } + + if ( this.name === 'default' ) { + return !es6 && this.module.exportsNames ? + `${this.module.name}__default` : + this.module.name; + } + + return es6 ? this.name : `${this.module.name}.${this.name}`; + } + + use () { + // noop? + } +} diff --git a/src/ExternalModule.js b/src/ExternalModule.js index 0e6a3ac..3113a5d 100644 --- a/src/ExternalModule.js +++ b/src/ExternalModule.js @@ -1,43 +1,6 @@ import { blank } from './utils/object.js'; import makeLegalIdentifier from './utils/makeLegalIdentifier.js'; - -class ExternalDeclaration { - constructor ( module, name ) { - this.module = module; - this.name = name; - this.isExternal = true; - } - - addAlias () { - // noop - } - - addReference ( reference ) { - reference.declaration = this; - - if ( this.name === 'default' || this.name === '*' ) { - this.module.suggestName( reference.name ); - } - } - - render ( es6 ) { - if ( this.name === '*' ) { - return this.module.name; - } - - if ( this.name === 'default' ) { - return !es6 && this.module.exportsNames ? - `${this.module.name}__default` : - this.module.name; - } - - return es6 ? this.name : `${this.module.name}.${this.name}`; - } - - use () { - // noop? - } -} +import { ExternalDeclaration } from './Declaration.js'; export default class ExternalModule { constructor ( id ) { diff --git a/src/Module.js b/src/Module.js index 863a246..1e5a97b 100644 --- a/src/Module.js +++ b/src/Module.js @@ -7,131 +7,7 @@ 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'; - -class SyntheticDefaultDeclaration { - constructor ( node, statement, name ) { - this.node = node; - this.statement = statement; - this.name = name; - - this.original = null; - this.isExported = false; - this.aliases = []; - } - - addAlias ( declaration ) { - this.aliases.push( declaration ); - } - - addReference ( reference ) { - // Don't change the name to `default`; it's not a valid identifier name. - if ( reference.name === 'default' ) return; - - reference.declaration = this; - this.name = reference.name; - } - - bind ( declaration ) { - this.original = declaration; - } - - mutates () { - return this.original.mutates(); - } - - render () { - return !this.original || this.original.isReassigned ? - this.name : - this.original.render(); - } - - use () { - this.isUsed = true; - this.statement.mark(); - - if ( this.original ) this.original.use(); - - this.aliases.forEach( alias => alias.use() ); - } -} - -class SyntheticNamespaceDeclaration { - constructor ( module ) { - this.module = module; - this.name = null; - - this.needsNamespaceBlock = false; - this.aliases = []; - - this.originals = blank(); - module.getExports().forEach( name => { - this.originals[ name ] = module.traceExport( name ); - }); - } - - addAlias ( declaration ) { - this.aliases.push( declaration ); - } - - addReference ( reference ) { - // if we have e.g. `foo.bar`, we can optimise - // the reference by pointing directly to `bar` - if ( reference.parts.length ) { - reference.name = reference.parts.shift(); - - reference.end += reference.name.length + 1; // TODO this is brittle - - 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; - } - - original.addReference( reference ); - return; - } - - // 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 ); - } - - reference.declaration = this; - this.name = reference.name; - } - - renderBlock ( 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}${name}: ${original.render()}`; - }); - - return `var ${this.render()} = Object.freeze({\n${members.join( ',\n' )}\n});\n\n`; - } - - render () { - return this.name; - } - - use () { - keys( this.originals ).forEach( name => { - this.originals[ name ].use(); - }); - - this.aliases.forEach( alias => alias.use() ); - } -} +import { SyntheticDefaultDeclaration, SyntheticNamespaceDeclaration } from './Declaration.js'; export default class Module { constructor ({ id, code, originalCode, ast, sourceMapChain, bundle }) { @@ -411,13 +287,9 @@ export default class Module { } markAllSideEffects () { - let hasSideEffect = false; - this.statements.forEach( statement => { - if ( statement.markSideEffect() ) hasSideEffect = true; + statement.markSideEffect(); }); - - return hasSideEffect; } namespace () { diff --git a/src/Statement.js b/src/Statement.js index e0a83b1..31b8bd7 100644 --- a/src/Statement.js +++ b/src/Statement.js @@ -1,16 +1,9 @@ import { walk } from 'estree-walker'; import Scope from './ast/Scope.js'; import attachScopes from './ast/attachScopes.js'; +import modifierNodes from './ast/modifierNodes.js'; import getLocation from './utils/getLocation.js'; - -const modifierNodes = { - AssignmentExpression: 'left', - UpdateExpression: 'argument' -}; - -function isIife ( node, parent ) { - return parent && parent.type === 'CallExpression' && node === parent.callee; -} +import testForSideEffects from './utils/testForSideEffects.js'; function isReference ( node, parent ) { if ( node.type === 'MemberExpression' ) { @@ -35,9 +28,10 @@ function isReference ( node, parent ) { } class Reference { - constructor ( node, scope ) { + constructor ( node, scope, statement ) { this.node = node; this.scope = scope; + this.statement = statement; this.declaration = null; // bound later @@ -90,6 +84,7 @@ export default class Statement { }); // find references + const statement = this; let { module, references, scope, stringLiteralRanges } = this; let readDepth = 0; @@ -106,7 +101,7 @@ export default class Statement { } if ( node._scope ) scope = node._scope; - if ( /Function/.test( node.type ) && !isIife( node, parent ) ) readDepth += 1; + if ( /Function/.test( node.type ) ) readDepth += 1; // special case – shorthand properties. because node.key === node.value, // we can't differentiate once we've descended into the node @@ -117,9 +112,10 @@ export default class Statement { return this.skip(); } + const isMutation = parent && parent.type in modifierNodes; let isReassignment; - if ( parent && parent.type in modifierNodes ) { + if ( isMutation ) { let subject = parent[ modifierNodes[ parent.type ] ]; let depth = 0; @@ -153,18 +149,19 @@ export default class Statement { scope.parent : scope; - const reference = new Reference( node, referenceScope ); + const reference = new Reference( node, referenceScope, statement ); references.push( reference ); reference.isImmediatelyUsed = !readDepth; reference.isReassignment = isReassignment; + reference.isMutation = !readDepth && isMutation; this.skip(); // don't descend from `foo.bar.baz` into `foo.bar` } }, - leave ( node, parent ) { + leave ( node ) { if ( node._scope ) scope = scope.parent; - if ( /Function/.test( node.type ) && !isIife( node, parent ) ) readDepth -= 1; + if ( /Function/.test( node.type ) ) readDepth -= 1; } }); } @@ -181,82 +178,10 @@ export default class Statement { markSideEffect () { if ( this.isIncluded ) return; - const statement = this; - let hasSideEffect = false; - - walk( this.node, { - enter ( node ) { - // Don't descend into (quasi) function declarations – we'll worry about those - // if they get called - if ( node.type === 'FunctionDeclaration' || node.type === 'VariableDeclarator' && node.init && /FunctionExpression/.test( node.init.type ) ) { - return this.skip(); - } - - // If this is a top-level call expression, or an assignment to a global, - // this statement will need to be marked - if ( node.type === 'NewExpression' ) { - hasSideEffect = true; - } - - else if ( node.type === 'CallExpression' ) { - if ( node.callee.type === 'Identifier' ) { - const declaration = statement.module.trace( node.callee.name ); - - if ( !declaration || !declaration.isFunctionDeclaration ) { - hasSideEffect = true; - } - - else { - const mutatedByFunction = declaration.mutates(); - let i = mutatedByFunction.length; - while ( i-- ) { - const mutatedDeclaration = statement.module.trace( mutatedByFunction[i] ); - - if ( !mutatedDeclaration || mutatedDeclaration.isUsed ) { - hasSideEffect = true; - break; - } - } - - // if ( declaration.hasSideEffect() ) { - // // if calling this function creates side-effects... - // hasSideEffect = true; - // } - // - // else { - // // ...or mutates inputs that are included... - // hasSideEffect = true; - // } - - // TODO does function mutate inputs that are needed? - } - } - - else if ( node.callee.type === 'MemberExpression' ) { - // if we're calling e.g. Object.keys(thing), there are no side-effects - // TODO - - hasSideEffect = true; - } - } - - else if ( node.type in modifierNodes ) { - let subject = node[ modifierNodes[ node.type ] ]; - while ( subject.type === 'MemberExpression' ) subject = subject.object; - - const declaration = statement.module.trace( subject.name ); - - if ( !declaration || declaration.isExternal || declaration.statement.isIncluded ) { - hasSideEffect = true; - } - } - - if ( hasSideEffect ) this.skip(); - } - }); - - if ( hasSideEffect ) statement.mark(); - return hasSideEffect; + if ( testForSideEffects( this.node, this.scope, this ) ) { + this.mark(); + return true; + } } source () { diff --git a/src/ast/flatten.js b/src/ast/flatten.js new file mode 100644 index 0000000..0cecbde --- /dev/null +++ b/src/ast/flatten.js @@ -0,0 +1,16 @@ +export default function flatten ( node ) { + let parts = []; + while ( node.type === 'MemberExpression' ) { + if ( node.computed ) return null; + parts.unshift( node.property.name ); + + node = node.object; + } + + if ( node.type !== 'Identifier' ) return null; + + const name = node.name; + parts.unshift( name ); + + return { name, keypath: parts.join( '.' ) }; +} diff --git a/src/ast/modifierNodes.js b/src/ast/modifierNodes.js new file mode 100644 index 0000000..2ea3f5d --- /dev/null +++ b/src/ast/modifierNodes.js @@ -0,0 +1,4 @@ +export default { + AssignmentExpression: 'left', + UpdateExpression: 'argument' +}; diff --git a/src/utils/testForSideEffects.js b/src/utils/testForSideEffects.js new file mode 100644 index 0000000..1aa4f96 --- /dev/null +++ b/src/utils/testForSideEffects.js @@ -0,0 +1,92 @@ +import { walk } from 'estree-walker'; +import modifierNodes from '../ast/modifierNodes.js'; +import flatten from '../ast/flatten'; + +let pureFunctions = {}; +[ + // TODO add others to this list + 'Object.keys' +].forEach( name => pureFunctions[ name ] = true ); + +export default function testForSideEffects ( node, scope, statement ) { + let hasSideEffect = false; + + walk( node, { + enter ( node ) { + if ( hasSideEffect ) return this.skip(); + if ( /Function/.test( node.type ) ) return this.skip(); + + if ( node._scope ) scope = node._scope; + + // If this is a top-level call expression, or an assignment to a global, + // this statement will need to be marked + if ( node.type === 'NewExpression' ) { + hasSideEffect = true; + return this.skip(); + } + + if ( node.type === 'CallExpression' ) { + if ( node.callee.type === 'Identifier' && !scope.contains( node.callee.name ) ) { + const declaration = statement.module.trace( node.callee.name ); + + if ( !declaration || declaration.isExternal ) { + // we're calling a global or an external function. Assume side-effects + hasSideEffect = true; + return this.skip(); + } + + // we're calling a function defined in this bundle + if ( declaration.hasSideEffect() ) { + hasSideEffect = true; + return this.skip(); + } + + // TODO does function mutate inputs that are needed? + return; + } + + if ( node.callee.type === 'MemberExpression' ) { + const flattened = flatten( node.callee ); + + if ( !flattened ) { + // is not a keypath like `foo.bar.baz` – could be e.g. + // `(a || b).foo()`. Err on the side of caution + hasSideEffect = true; + return; + } + + // if we're calling e.g. Object.keys(thing), there are no side-effects + // TODO make pureFunctions configurable + const declaration = statement.module.trace( flattened.name ); + if ( !declaration && pureFunctions[ flattened.keypath ] ) return; + + hasSideEffect = true; + return this.skip(); + } + + // otherwise we're probably dealing with a function expression + if ( testForSideEffects( node.callee, scope, statement ) ) { + hasSideEffect = true; + return this.skip(); + } + } + + if ( node.type in modifierNodes ) { + let subject = node[ modifierNodes[ node.type ] ]; + while ( subject.type === 'MemberExpression' ) subject = subject.object; + + const declaration = statement.module.trace( subject.name ); + + if ( !declaration || declaration.isExternal || declaration.statement.isIncluded ) { + hasSideEffect = true; + return this.skip(); + } + } + }, + leave ( node ) { + if ( node._scope ) scope = scope.parent; + } + }); + + return hasSideEffect; +} diff --git a/test/form/namespace-optimization-b/_expected/amd.js b/test/form/namespace-optimization-b/_expected/amd.js index 44fba59..30c5406 100644 --- a/test/form/namespace-optimization-b/_expected/amd.js +++ b/test/form/namespace-optimization-b/_expected/amd.js @@ -1,15 +1,19 @@ define(function () { 'use strict'; - function foo() { - }; - - function a() { - foo(); - foo(); - var a; - if (a.b) { - } - } - a(); - -}); + function foo () { + console.log( 'foo' ); + } + + function a () { + foo(); + foo(); + + var a; + if ( a.b ) { + // empty + } + } + + 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 34f819f..3ee6869 100644 --- a/test/form/namespace-optimization-b/_expected/cjs.js +++ b/test/form/namespace-optimization-b/_expected/cjs.js @@ -1,13 +1,17 @@ 'use strict'; -function foo() { -}; - -function a() { - foo(); - foo(); - var a; - if (a.b) { - } +function foo () { + console.log( 'foo' ); } -a(); + +function a () { + foo(); + foo(); + + var a; + if ( a.b ) { + // empty + } +} + +a(); \ No newline at end of file diff --git a/test/form/namespace-optimization-b/_expected/es6.js b/test/form/namespace-optimization-b/_expected/es6.js index c1a6d5d..8346d0d 100644 --- a/test/form/namespace-optimization-b/_expected/es6.js +++ b/test/form/namespace-optimization-b/_expected/es6.js @@ -1,11 +1,15 @@ -function foo() { -}; +function foo () { + console.log( 'foo' ); +} + +function a () { + foo(); + foo(); -function a() { - foo(); - foo(); - var a; - if (a.b) { - } + var a; + if ( a.b ) { + // empty + } } -a(); + +a(); \ No newline at end of file diff --git a/test/form/namespace-optimization-b/_expected/iife.js b/test/form/namespace-optimization-b/_expected/iife.js index 6ea6190..59b2ce7 100644 --- a/test/form/namespace-optimization-b/_expected/iife.js +++ b/test/form/namespace-optimization-b/_expected/iife.js @@ -1,15 +1,19 @@ (function () { 'use strict'; - function foo() { - }; - - function a() { - foo(); - foo(); - var a; - if (a.b) { - } - } - a(); - -})(); + function foo () { + console.log( 'foo' ); + } + + function a () { + foo(); + foo(); + + var a; + if ( a.b ) { + // empty + } + } + + 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 108f27b..c2ad84b 100644 --- a/test/form/namespace-optimization-b/_expected/umd.js +++ b/test/form/namespace-optimization-b/_expected/umd.js @@ -1,19 +1,23 @@ (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory() : - typeof define === 'function' && define.amd ? define(factory) : - factory(); + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + factory(); }(this, function () { 'use strict'; - function foo() { - }; + function foo () { + console.log( 'foo' ); + } - function a() { - foo(); - foo(); - var a; - if (a.b) { - } - } - a(); + function a () { + foo(); + foo(); -})); + var a; + if ( a.b ) { + // empty + } + } + + a(); + +})); \ No newline at end of file diff --git a/test/form/self-contained-bundle/_expected/amd.js b/test/form/self-contained-bundle/_expected/amd.js index ed04c7c..a29d399 100644 --- a/test/form/self-contained-bundle/_expected/amd.js +++ b/test/form/self-contained-bundle/_expected/amd.js @@ -1,7 +1,7 @@ define(function () { 'use strict'; function foo () { - return bar(); + console.log( bar() ); } function bar () { diff --git a/test/form/self-contained-bundle/_expected/cjs.js b/test/form/self-contained-bundle/_expected/cjs.js index 8a71fe7..499be1d 100644 --- a/test/form/self-contained-bundle/_expected/cjs.js +++ b/test/form/self-contained-bundle/_expected/cjs.js @@ -1,7 +1,7 @@ 'use strict'; function foo () { - return bar(); + console.log( bar() ); } function bar () { diff --git a/test/form/self-contained-bundle/_expected/es6.js b/test/form/self-contained-bundle/_expected/es6.js index 7d5460c..5588fc7 100644 --- a/test/form/self-contained-bundle/_expected/es6.js +++ b/test/form/self-contained-bundle/_expected/es6.js @@ -1,5 +1,5 @@ function foo () { - return bar(); + console.log( bar() ); } function bar () { diff --git a/test/form/self-contained-bundle/_expected/iife.js b/test/form/self-contained-bundle/_expected/iife.js index 46f6561..1017054 100644 --- a/test/form/self-contained-bundle/_expected/iife.js +++ b/test/form/self-contained-bundle/_expected/iife.js @@ -1,7 +1,7 @@ (function () { 'use strict'; function foo () { - return bar(); + console.log( bar() ); } function bar () { diff --git a/test/form/self-contained-bundle/_expected/umd.js b/test/form/self-contained-bundle/_expected/umd.js index 51b72b0..d29ef88 100644 --- a/test/form/self-contained-bundle/_expected/umd.js +++ b/test/form/self-contained-bundle/_expected/umd.js @@ -5,7 +5,7 @@ }(this, function () { 'use strict'; function foo () { - return bar(); + console.log( bar() ); } function bar () { diff --git a/test/form/self-contained-bundle/foo.js b/test/form/self-contained-bundle/foo.js index 1e911f3..46a9052 100644 --- a/test/form/self-contained-bundle/foo.js +++ b/test/form/self-contained-bundle/foo.js @@ -1,5 +1,5 @@ export default function foo () { - return bar(); + console.log( bar() ); } function bar () { diff --git a/test/form/side-effect-b/_config.js b/test/form/side-effect-b/_config.js new file mode 100644 index 0000000..9b0c852 --- /dev/null +++ b/test/form/side-effect-b/_config.js @@ -0,0 +1,6 @@ +module.exports = { + description: 'discards IIFE with no side-effects', + options: { + moduleName: 'myBundle' + } +}; diff --git a/test/form/side-effect-b/_expected/amd.js b/test/form/side-effect-b/_expected/amd.js new file mode 100644 index 0000000..70cc3c4 --- /dev/null +++ b/test/form/side-effect-b/_expected/amd.js @@ -0,0 +1,7 @@ +define(function () { 'use strict'; + + var main = 42; + + return main; + +}); \ No newline at end of file diff --git a/test/form/side-effect-b/_expected/cjs.js b/test/form/side-effect-b/_expected/cjs.js new file mode 100644 index 0000000..cc51dcb --- /dev/null +++ b/test/form/side-effect-b/_expected/cjs.js @@ -0,0 +1,5 @@ +'use strict'; + +var main = 42; + +module.exports = main; \ No newline at end of file diff --git a/test/form/side-effect-b/_expected/es6.js b/test/form/side-effect-b/_expected/es6.js new file mode 100644 index 0000000..b953a15 --- /dev/null +++ b/test/form/side-effect-b/_expected/es6.js @@ -0,0 +1,3 @@ +var main = 42; + +export default main; \ No newline at end of file diff --git a/test/form/side-effect-b/_expected/iife.js b/test/form/side-effect-b/_expected/iife.js new file mode 100644 index 0000000..0880329 --- /dev/null +++ b/test/form/side-effect-b/_expected/iife.js @@ -0,0 +1,7 @@ +var myBundle = (function () { 'use strict'; + + var main = 42; + + return main; + +})(); \ No newline at end of file diff --git a/test/form/side-effect-b/_expected/umd.js b/test/form/side-effect-b/_expected/umd.js new file mode 100644 index 0000000..7f83fb9 --- /dev/null +++ b/test/form/side-effect-b/_expected/umd.js @@ -0,0 +1,11 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + global.myBundle = factory(); +}(this, function () { 'use strict'; + + var main = 42; + + return main; + +})); \ No newline at end of file diff --git a/test/form/side-effect-b/main.js b/test/form/side-effect-b/main.js new file mode 100644 index 0000000..c5e65eb --- /dev/null +++ b/test/form/side-effect-b/main.js @@ -0,0 +1,8 @@ +var Unused = (function () { + function Unused () {} + Unused.prototype = {}; + + return Unused; +}()); + +export default 42; diff --git a/test/form/side-effect-c/_config.js b/test/form/side-effect-c/_config.js new file mode 100644 index 0000000..0d692d4 --- /dev/null +++ b/test/form/side-effect-c/_config.js @@ -0,0 +1,6 @@ +module.exports = { + description: 'discards function with no side-effects', + options: { + moduleName: 'myBundle' + } +}; diff --git a/test/form/side-effect-c/_expected/amd.js b/test/form/side-effect-c/_expected/amd.js new file mode 100644 index 0000000..70cc3c4 --- /dev/null +++ b/test/form/side-effect-c/_expected/amd.js @@ -0,0 +1,7 @@ +define(function () { 'use strict'; + + var main = 42; + + return main; + +}); \ No newline at end of file diff --git a/test/form/side-effect-c/_expected/cjs.js b/test/form/side-effect-c/_expected/cjs.js new file mode 100644 index 0000000..cc51dcb --- /dev/null +++ b/test/form/side-effect-c/_expected/cjs.js @@ -0,0 +1,5 @@ +'use strict'; + +var main = 42; + +module.exports = main; \ No newline at end of file diff --git a/test/form/side-effect-c/_expected/es6.js b/test/form/side-effect-c/_expected/es6.js new file mode 100644 index 0000000..b953a15 --- /dev/null +++ b/test/form/side-effect-c/_expected/es6.js @@ -0,0 +1,3 @@ +var main = 42; + +export default main; \ No newline at end of file diff --git a/test/form/side-effect-c/_expected/iife.js b/test/form/side-effect-c/_expected/iife.js new file mode 100644 index 0000000..0880329 --- /dev/null +++ b/test/form/side-effect-c/_expected/iife.js @@ -0,0 +1,7 @@ +var myBundle = (function () { 'use strict'; + + var main = 42; + + return main; + +})(); \ No newline at end of file diff --git a/test/form/side-effect-c/_expected/umd.js b/test/form/side-effect-c/_expected/umd.js new file mode 100644 index 0000000..7f83fb9 --- /dev/null +++ b/test/form/side-effect-c/_expected/umd.js @@ -0,0 +1,11 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + global.myBundle = factory(); +}(this, function () { 'use strict'; + + var main = 42; + + return main; + +})); \ No newline at end of file diff --git a/test/form/side-effect-c/main.js b/test/form/side-effect-c/main.js new file mode 100644 index 0000000..87ad109 --- /dev/null +++ b/test/form/side-effect-c/main.js @@ -0,0 +1,10 @@ +var factory = function () { + function Unused () {} + Unused.prototype = {}; + + return Unused; +}; + +var Unused = factory(); + +export default 42; diff --git a/test/form/side-effect-d/_config.js b/test/form/side-effect-d/_config.js new file mode 100644 index 0000000..a8d5bf9 --- /dev/null +++ b/test/form/side-effect-d/_config.js @@ -0,0 +1,6 @@ +module.exports = { + description: 'excludes functions that are known to be pure', + options: { + moduleName: 'myBundle' + } +}; diff --git a/test/form/side-effect-d/_expected/amd.js b/test/form/side-effect-d/_expected/amd.js new file mode 100644 index 0000000..37d2571 --- /dev/null +++ b/test/form/side-effect-d/_expected/amd.js @@ -0,0 +1,7 @@ +define(function () { 'use strict'; + + var main = 42; + + return main; + +}); diff --git a/test/form/side-effect-d/_expected/cjs.js b/test/form/side-effect-d/_expected/cjs.js new file mode 100644 index 0000000..5a370cd --- /dev/null +++ b/test/form/side-effect-d/_expected/cjs.js @@ -0,0 +1,5 @@ +'use strict'; + +var main = 42; + +module.exports = main; diff --git a/test/form/side-effect-d/_expected/es6.js b/test/form/side-effect-d/_expected/es6.js new file mode 100644 index 0000000..d862de8 --- /dev/null +++ b/test/form/side-effect-d/_expected/es6.js @@ -0,0 +1,3 @@ +var main = 42; + +export default main; diff --git a/test/form/side-effect-d/_expected/iife.js b/test/form/side-effect-d/_expected/iife.js new file mode 100644 index 0000000..b7b15ee --- /dev/null +++ b/test/form/side-effect-d/_expected/iife.js @@ -0,0 +1,7 @@ +var myBundle = (function () { 'use strict'; + + var main = 42; + + return main; + +})(); diff --git a/test/form/side-effect-d/_expected/umd.js b/test/form/side-effect-d/_expected/umd.js new file mode 100644 index 0000000..b5aa08a --- /dev/null +++ b/test/form/side-effect-d/_expected/umd.js @@ -0,0 +1,11 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + global.myBundle = factory(); +}(this, function () { 'use strict'; + + var main = 42; + + return main; + +})); diff --git a/test/form/side-effect-d/main.js b/test/form/side-effect-d/main.js new file mode 100644 index 0000000..c53646d --- /dev/null +++ b/test/form/side-effect-d/main.js @@ -0,0 +1,4 @@ +var obj = { foo: 1, bar: 2 }; +var keys = Object.keys( obj ); + +export default 42; diff --git a/test/form/unused-side-effect/_config.js b/test/form/side-effect/_config.js similarity index 100% rename from test/form/unused-side-effect/_config.js rename to test/form/side-effect/_config.js diff --git a/test/form/unused-side-effect/_expected/amd.js b/test/form/side-effect/_expected/amd.js similarity index 100% rename from test/form/unused-side-effect/_expected/amd.js rename to test/form/side-effect/_expected/amd.js diff --git a/test/form/unused-side-effect/_expected/cjs.js b/test/form/side-effect/_expected/cjs.js similarity index 100% rename from test/form/unused-side-effect/_expected/cjs.js rename to test/form/side-effect/_expected/cjs.js diff --git a/test/form/unused-side-effect/_expected/es6.js b/test/form/side-effect/_expected/es6.js similarity index 100% rename from test/form/unused-side-effect/_expected/es6.js rename to test/form/side-effect/_expected/es6.js diff --git a/test/form/unused-side-effect/_expected/iife.js b/test/form/side-effect/_expected/iife.js similarity index 100% rename from test/form/unused-side-effect/_expected/iife.js rename to test/form/side-effect/_expected/iife.js diff --git a/test/form/unused-side-effect/_expected/umd.js b/test/form/side-effect/_expected/umd.js similarity index 100% rename from test/form/unused-side-effect/_expected/umd.js rename to test/form/side-effect/_expected/umd.js diff --git a/test/form/unused-side-effect/foo.js b/test/form/side-effect/foo.js similarity index 100% rename from test/form/unused-side-effect/foo.js rename to test/form/side-effect/foo.js diff --git a/test/form/unused-side-effect/main.js b/test/form/side-effect/main.js similarity index 100% rename from test/form/unused-side-effect/main.js rename to test/form/side-effect/main.js diff --git a/test/form/unmodified-default-exports-function-argument/_expected/amd.js b/test/form/unmodified-default-exports-function-argument/_expected/amd.js index c1053c2..be41871 100644 --- a/test/form/unmodified-default-exports-function-argument/_expected/amd.js +++ b/test/form/unmodified-default-exports-function-argument/_expected/amd.js @@ -11,4 +11,6 @@ define(function () { 'use strict'; var answer = foo(); var somethingElse = bar(); + console.log( answer ); + }); diff --git a/test/form/unmodified-default-exports-function-argument/_expected/cjs.js b/test/form/unmodified-default-exports-function-argument/_expected/cjs.js index 18131b8..362f677 100644 --- a/test/form/unmodified-default-exports-function-argument/_expected/cjs.js +++ b/test/form/unmodified-default-exports-function-argument/_expected/cjs.js @@ -10,3 +10,5 @@ function bar () { var answer = foo(); var somethingElse = bar(); + +console.log( answer ); diff --git a/test/form/unmodified-default-exports-function-argument/_expected/es6.js b/test/form/unmodified-default-exports-function-argument/_expected/es6.js index 6bde701..76d6f0c 100644 --- a/test/form/unmodified-default-exports-function-argument/_expected/es6.js +++ b/test/form/unmodified-default-exports-function-argument/_expected/es6.js @@ -8,3 +8,5 @@ function bar () { var answer = foo(); var somethingElse = bar(); + +console.log( answer ); diff --git a/test/form/unmodified-default-exports-function-argument/_expected/iife.js b/test/form/unmodified-default-exports-function-argument/_expected/iife.js index 8d5d37f..9fb882d 100644 --- a/test/form/unmodified-default-exports-function-argument/_expected/iife.js +++ b/test/form/unmodified-default-exports-function-argument/_expected/iife.js @@ -11,4 +11,6 @@ var answer = foo(); var somethingElse = bar(); + console.log( answer ); + })(); diff --git a/test/form/unmodified-default-exports-function-argument/_expected/umd.js b/test/form/unmodified-default-exports-function-argument/_expected/umd.js index 5ae4301..2a4c64c 100644 --- a/test/form/unmodified-default-exports-function-argument/_expected/umd.js +++ b/test/form/unmodified-default-exports-function-argument/_expected/umd.js @@ -15,4 +15,6 @@ var answer = foo(); var somethingElse = bar(); + console.log( answer ); + })); diff --git a/test/form/unmodified-default-exports-function-argument/main.js b/test/form/unmodified-default-exports-function-argument/main.js index 1cc9361..efe5d3f 100644 --- a/test/form/unmodified-default-exports-function-argument/main.js +++ b/test/form/unmodified-default-exports-function-argument/main.js @@ -2,3 +2,5 @@ import foo, { bar } from './foo'; var answer = foo(); var somethingElse = bar(); + +console.log( answer );