From 8fdb8d931890fb92d52285b49d5d256a6caeaabb Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Tue, 22 Sep 2015 22:49:19 -0400 Subject: [PATCH] Add recent tests and fixes on top of 0.15.0 preserve updates and fixes include readdirSync assignment pattern fix handle side-effects update tests fix double removal bug reinstate sourcemaps update tests add missing break statement handle export * from internal add back tests update all tests skip namespace optimisation tests for now reinstate various fixes reinstate fast/leaner deconflicting, update tests --- .babelrc | 1 + .eslintrc | 6 +- .gitignore | 1 + .travis.yml | 3 +- CHANGELOG.md | 27 ++++ README.md | 3 + appveyor.yml | 33 +++++ browser/sander.js | 4 + gobblefile.js | 3 +- package.json | 23 ++- src/Bundle.js | 35 +++-- src/Module.js | 59 +++++++- src/Statement.js | 67 +++++++-- src/ast/Scope.js | 60 +++++--- src/rollup.js | 7 +- src/utils/getExportMode.js | 4 +- src/utils/makeLegalIdentifier.js | 5 +- src/utils/path.js | 6 +- src/utils/resolveId.js | 42 ++++-- src/utils/sourceMappingURL.js | 6 + test/cli/external-modules/main.js | 4 +- test/form/export-all-from-internal/_config.js | 6 + .../export-all-from-internal/_expected/amd.js | 9 ++ .../export-all-from-internal/_expected/cjs.js | 7 + .../export-all-from-internal/_expected/es6.js | 4 + .../_expected/iife.js | 9 ++ .../export-all-from-internal/_expected/umd.js | 13 ++ .../form/export-all-from-internal/internal.js | 3 + test/form/export-all-from-internal/main.js | 1 + .../_expected/amd.js | 4 +- .../_expected/cjs.js | 4 +- .../_expected/es6.js | 4 +- .../_expected/iife.js | 4 +- .../_expected/umd.js | 4 +- test/form/namespace-optimization/_config.js | 4 + .../namespace-optimization/_expected/amd.js | 7 + .../namespace-optimization/_expected/cjs.js | 5 + .../namespace-optimization/_expected/es6.js | 3 + .../namespace-optimization/_expected/iife.js | 7 + .../namespace-optimization/_expected/umd.js | 11 ++ test/form/namespace-optimization/bar.js | 3 + test/form/namespace-optimization/foo.js | 3 + test/form/namespace-optimization/main.js | 3 + test/form/namespace-optimization/quux.js | 1 + test/form/unused-side-effect/_config.js | 3 + test/form/unused-side-effect/_expected/amd.js | 7 + test/form/unused-side-effect/_expected/cjs.js | 5 + test/form/unused-side-effect/_expected/es6.js | 3 + .../form/unused-side-effect/_expected/iife.js | 7 + test/form/unused-side-effect/_expected/umd.js | 11 ++ test/form/unused-side-effect/foo.js | 13 ++ test/form/unused-side-effect/main.js | 2 + .../main.js | 6 +- test/function/assignment-patterns/_config.js | 4 + test/function/assignment-patterns/main.js | 21 +++ test/function/assignment-patterns/other.js | 8 + test/function/cannot-import-self/_config.js | 8 + test/function/cannot-import-self/main.js | 1 + .../custom-path-resolver-async/_config.js | 2 +- .../custom-path-resolver-sync/_config.js | 2 +- .../duplicate-import-fails/_config.js | 2 +- .../_config.js | 2 +- .../dynamic-namespace-lookup/_config.js | 3 + test/function/dynamic-namespace-lookup/foo.js | 2 + .../function/dynamic-namespace-lookup/main.js | 8 + .../export-not-at-top-level-fails/_config.js | 2 +- .../export-type-mismatch-b/_config.js | 11 ++ test/function/export-type-mismatch-b/main.js | 1 + .../export-type-mismatch-c/_config.js | 11 ++ test/function/export-type-mismatch-c/main.js | 1 + test/function/export-type-mismatch/_config.js | 11 ++ test/function/export-type-mismatch/main.js | 1 + .../_config.js | 3 + .../main.js | 3 + .../module.js | 17 +++ .../handles-multiple-declarations/_config.js | 3 + .../handles-multiple-declarations/main.js | 2 + test/function/has-modules-array/_config.js | 12 ++ test/function/has-modules-array/foo.js | 1 + test/function/has-modules-array/main.js | 2 + .../import-default-from-external/main.js | 2 +- .../_config.js | 4 + .../import-from-external-subdirectory/main.js | 5 + .../import-named-from-external/main.js | 4 +- .../main.js | 2 +- .../main.js | 2 +- .../import-not-at-top-level-fails/_config.js | 2 +- .../import-of-unexported-fails/_config.js | 8 + .../import-of-unexported-fails/empty.js | 0 .../import-of-unexported-fails/main.js | 3 + .../imports-are-deconflicted-b/main.js | 3 +- .../function/imports-are-deconflicted/main.js | 3 +- .../function/modify-assumed-global/_config.js | 14 ++ test/function/modify-assumed-global/main.js | 3 + test/function/modify-assumed-global/math.js | 15 ++ test/function/module-sort-order/_config.js | 5 + test/function/module-sort-order/a.js | 20 +++ test/function/module-sort-order/b.js | 1 + test/function/module-sort-order/c.js | 3 + test/function/module-sort-order/main.js | 4 + test/function/module-sort-order/z.js | 5 + .../_config.js | 2 +- .../namespace-update-import-fails/_config.js | 2 +- .../property-keys-not-renamed/_config.js | 3 + .../property-keys-not-renamed/main.js | 7 + .../function/property-keys-not-renamed/one.js | 11 ++ .../property-keys-not-renamed/three.js | 11 ++ .../function/property-keys-not-renamed/two.js | 11 ++ .../function/reassign-import-fails/_config.js | 2 +- .../_config.js | 2 +- .../function/shadowed-external-export/main.js | 4 +- test/function/shorthand-properties/baz.js | 3 + test/function/shorthand-properties/foo.js | 5 +- test/function/shorthand-properties/main.js | 5 +- .../_config.js | 2 +- test/sourcemaps/basic-support/_config.js | 6 +- test/sourcemaps/names/_config.js | 24 ++- test/test.js | 140 +++++++++++------- 118 files changed, 882 insertions(+), 185 deletions(-) create mode 100644 appveyor.yml create mode 100644 src/utils/sourceMappingURL.js create mode 100644 test/form/export-all-from-internal/_config.js create mode 100644 test/form/export-all-from-internal/_expected/amd.js create mode 100644 test/form/export-all-from-internal/_expected/cjs.js create mode 100644 test/form/export-all-from-internal/_expected/es6.js create mode 100644 test/form/export-all-from-internal/_expected/iife.js create mode 100644 test/form/export-all-from-internal/_expected/umd.js create mode 100644 test/form/export-all-from-internal/internal.js create mode 100644 test/form/export-all-from-internal/main.js create mode 100644 test/form/namespace-optimization/_config.js create mode 100644 test/form/namespace-optimization/_expected/amd.js create mode 100644 test/form/namespace-optimization/_expected/cjs.js create mode 100644 test/form/namespace-optimization/_expected/es6.js create mode 100644 test/form/namespace-optimization/_expected/iife.js create mode 100644 test/form/namespace-optimization/_expected/umd.js create mode 100644 test/form/namespace-optimization/bar.js create mode 100644 test/form/namespace-optimization/foo.js create mode 100644 test/form/namespace-optimization/main.js create mode 100644 test/form/namespace-optimization/quux.js create mode 100644 test/form/unused-side-effect/_config.js create mode 100644 test/form/unused-side-effect/_expected/amd.js create mode 100644 test/form/unused-side-effect/_expected/cjs.js create mode 100644 test/form/unused-side-effect/_expected/es6.js create mode 100644 test/form/unused-side-effect/_expected/iife.js create mode 100644 test/form/unused-side-effect/_expected/umd.js create mode 100644 test/form/unused-side-effect/foo.js create mode 100644 test/form/unused-side-effect/main.js create mode 100644 test/function/assignment-patterns/_config.js create mode 100644 test/function/assignment-patterns/main.js create mode 100644 test/function/assignment-patterns/other.js create mode 100644 test/function/cannot-import-self/_config.js create mode 100644 test/function/cannot-import-self/main.js create mode 100644 test/function/dynamic-namespace-lookup/_config.js create mode 100644 test/function/dynamic-namespace-lookup/foo.js create mode 100644 test/function/dynamic-namespace-lookup/main.js create mode 100644 test/function/export-type-mismatch-b/_config.js create mode 100644 test/function/export-type-mismatch-b/main.js create mode 100644 test/function/export-type-mismatch-c/_config.js create mode 100644 test/function/export-type-mismatch-c/main.js create mode 100644 test/function/export-type-mismatch/_config.js create mode 100644 test/function/export-type-mismatch/main.js create mode 100644 test/function/globally-called-modifying-function/_config.js create mode 100644 test/function/globally-called-modifying-function/main.js create mode 100644 test/function/globally-called-modifying-function/module.js create mode 100644 test/function/handles-multiple-declarations/_config.js create mode 100644 test/function/handles-multiple-declarations/main.js create mode 100644 test/function/has-modules-array/_config.js create mode 100644 test/function/has-modules-array/foo.js create mode 100644 test/function/has-modules-array/main.js create mode 100644 test/function/import-from-external-subdirectory/_config.js create mode 100644 test/function/import-from-external-subdirectory/main.js create mode 100644 test/function/import-of-unexported-fails/_config.js create mode 100644 test/function/import-of-unexported-fails/empty.js create mode 100644 test/function/import-of-unexported-fails/main.js create mode 100644 test/function/modify-assumed-global/_config.js create mode 100644 test/function/modify-assumed-global/main.js create mode 100644 test/function/modify-assumed-global/math.js create mode 100644 test/function/module-sort-order/_config.js create mode 100644 test/function/module-sort-order/a.js create mode 100644 test/function/module-sort-order/b.js create mode 100644 test/function/module-sort-order/c.js create mode 100644 test/function/module-sort-order/main.js create mode 100644 test/function/module-sort-order/z.js create mode 100644 test/function/property-keys-not-renamed/_config.js create mode 100644 test/function/property-keys-not-renamed/main.js create mode 100644 test/function/property-keys-not-renamed/one.js create mode 100644 test/function/property-keys-not-renamed/three.js create mode 100644 test/function/property-keys-not-renamed/two.js create mode 100644 test/function/shorthand-properties/baz.js diff --git a/.babelrc b/.babelrc index 6ce65b2..ed25dc0 100644 --- a/.babelrc +++ b/.babelrc @@ -5,6 +5,7 @@ "es6.classes", "es6.constants", "es6.destructuring", + "es6.modules", "es6.parameters", "es6.properties.shorthand", "es6.spread", diff --git a/.eslintrc b/.eslintrc index 11f6d54..6e5fb1a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,15 +1,19 @@ { "rules": { - "indent": [ 2, "tab", { "SwitchCase": 1}], + "indent": [ 2, "tab", { "SwitchCase": 1 } ], "quotes": [ 2, "single" ], "linebreak-style": [ 2, "unix" ], "semi": [ 2, "always" ], + "space-after-keywords": [ 2, "always" ], + "space-before-blocks": [ 2, "always" ], + "space-before-function-paren": [ 2, "always" ], "no-mixed-spaces-and-tabs": [ 2, "smart-tabs" ], "no-cond-assign": [ 0 ] }, "env": { "es6": true, "browser": true, + "mocha": true, "node": true }, "extends": "eslint:recommended", diff --git a/.gitignore b/.gitignore index 5000af5..41653e5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules .gobble* dist _actual +coverage diff --git a/.travis.yml b/.travis.yml index 7fdfdb4..9c889c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,8 @@ sudo: false language: node_js node_js: - - "0.10" - "0.12" - - "iojs" + - "4" env: global: - BUILD_TIMEOUT=10000 diff --git a/CHANGELOG.md b/CHANGELOG.md index d58f37b..cde4345 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # rollup changelog +## 0.16.4 + +* Fix import paths with `.` ([#133](https://github.com/rollup/rollup/issues/133)) +* Prevent sourceMappingURL confusion leading to broken sourcemap +* Add code coverage reporting [#130](https://github.com/rollup/rollup/pull/130)) +* Add `modules` property to user-facing `bundle` – an array with `{id}` objects ([#128](https://github.com/rollup/rollup/issues/128)) + +## 0.16.3 + +* Prevent adjacent blocks of multiple var declarations causing magic-string failure ([#105](https://github.com/rollup/rollup/issues/105)) + +## 0.16.2 + +* Top-level function calls and assignments to globals are treated as side-effects, and always included +* Import files from subdirectories of external packages, e.g. `import mod from 'foo/sub/mod'` ([#126](https://github.com/rollup/rollup/issues/126)) + +## 0.16.1 + +* Handle assignment patterns, and destructured/rest parameters, when analysing scopes +* Fix bug preventing project from self-building (make base `Identifier` class markable) + +## 0.16.0 + +* Internal refactoring ([#99](https://github.com/rollup/rollup/pull/99)) +* Optimisation for statically-analysable namespace imports ([#99](https://github.com/rollup/rollup/pull/99)) +* Windows support (theoretically!) ([#117](https://github.com/rollup/rollup/pull/117) / [#119](https://github.com/rollup/rollup/pull/119)) + ## 0.15.0 * Load all modules specified by `import` statements, and do tree-shaking synchronously once loading is complete. This results in simpler and faster code, and enables future improvements ([#97](https://github.com/rollup/rollup/pull/97)) diff --git a/README.md b/README.md index 978fded..265a504 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,9 @@ dependency status + + Coverage via Codecov +

> *I roll up, I roll up, I roll up, Shawty I roll up* diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..cd3fab4 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,33 @@ +# http://www.appveyor.com/docs/appveyor-yml + +version: "{build}" + +clone_depth: 10 + +init: + - git config --global core.autocrlf false + +environment: + matrix: + # node.js + - nodejs_version: 0.10 + - nodejs_version: 0.12 + # io.js + - nodejs_version: 1 + +install: + - ps: Install-Product node $env:nodejs_version + - npm install + +build: off + +test_script: + - node --version && npm --version + - npm test + +matrix: + fast_finish: false + +# cache: +# - C:\Users\appveyor\AppData\Roaming\npm-cache -> package.json # npm cache +# - node_modules -> package.json # local npm modules diff --git a/browser/sander.js b/browser/sander.js index 6436076..5dbe4fc 100644 --- a/browser/sander.js +++ b/browser/sander.js @@ -1,3 +1,7 @@ +export function readdirSync () { + throw new Error( 'Cannot use sander.readdirSync inside browser' ); +} + export function readFile () { throw new Error( 'Cannot use sander.readFile inside browser' ); } diff --git a/gobblefile.js b/gobblefile.js index 6036237..4042dd7 100644 --- a/gobblefile.js +++ b/gobblefile.js @@ -8,7 +8,8 @@ var node = src entry: 'rollup.js', dest: 'rollup.js', format: 'cjs', - external: [ 'sander', 'acorn' ] + external: [ 'sander', 'acorn' ], + sourceMap: true }) .transform( 'babel' ); diff --git a/package.json b/package.json index 1e18cfa..b95ed8b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rollup", - "version": "0.15.0", + "version": "0.17.0", "description": "Next-generation ES6 module bundler", "main": "dist/rollup.js", "jsnext:main": "src/rollup.js", @@ -8,15 +8,19 @@ "rollup": "./bin/rollup" }, "scripts": { - "test": "mocha", "pretest": "npm run build", + "test": "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": "gobble build -f dist", "prepublish": "npm test", "lint": "eslint src" }, "repository": { "type": "git", - "url": "https://github.com/rich-harris/rollup" + "url": "https://github.com/rollup/rollup" }, "keywords": [ "modules", @@ -26,13 +30,18 @@ "optimizer" ], "author": "Rich Harris", + "contributors": [ + "Oskar Segersvärd " + ], "license": "MIT", "bugs": { - "url": "https://github.com/rich-harris/rollup/issues" + "url": "https://github.com/rollup/rollup/issues" }, - "homepage": "https://github.com/rich-harris/rollup", + "homepage": "https://github.com/rollup/rollup", "devDependencies": { + "babel": "^5.8.21", "babel-core": "^5.5.8", + "codecov.io": "^0.1.6", "console-group": "^0.1.2", "eslint": "^1.1.0", "gobble": "^0.10.1", @@ -40,9 +49,11 @@ "gobble-browserify": "^0.6.1", "gobble-cli": "^0.4.2", "gobble-esperanto-bundle": "^0.2.0", - "gobble-rollup": "^0.7.0", + "gobble-rollup": "^0.8.0", "gobble-rollup-babel": "^0.1.0", + "istanbul": "^0.3.20", "mocha": "^2.2.4", + "remap-istanbul": "^0.2.0", "source-map": "^0.4.4" }, "dependencies": { diff --git a/src/Bundle.js b/src/Bundle.js index 25cff8a..5a6199d 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -1,4 +1,3 @@ -import { basename, extname } from './utils/path'; import { Promise } from 'sander'; import MagicString from 'magic-string'; import { blank, keys } from './utils/object'; @@ -66,7 +65,7 @@ export default class Bundle { // `export default a + b` - generate an export name // based on the id of the entry module else { - let defaultExportName = makeLegalIdentifier( basename( this.entryModule.id ).slice( 0, -extname( this.entryModule.id ).length ) ); + let defaultExportName = this.entryModule.basename(); // deconflict let topLevelNames = []; @@ -84,16 +83,23 @@ export default class Bundle { entryModule.markAllStatements( true ); this.markAllModifierStatements(); + + // Include all side-effects + // TODO does this obviate the need for markAllStatements throughout? + this.modules.forEach( module => { + module.markAllSideEffects(); + }); + this.orderedModules = this.sort(); }); } // TODO would be better to deconflict once, rather than per-render deconflict ( es6 ) { - let usedNames = blank(); + let nameCount = blank(); // ensure no conflicts with globals - keys( this.assumedGlobals ).forEach( name => usedNames[ name ] = true ); + keys( this.assumedGlobals ).forEach( name => nameCount[ name ] = 0 ); let allReplacements = blank(); @@ -158,11 +164,15 @@ export default class Bundle { }); function getSafeName ( name ) { - while ( usedNames[ name ] ) { - name = `_${name}`; + if ( name in nameCount ) { + nameCount[ name ] += 1; + name = `${name}$${nameCount[ name ]}`; + + while ( name in nameCount ) name = `_${name}`; // just to avoid any crazy edge cases + return name; } - usedNames[ name ] = true; + nameCount[ name ] = 0; return name; } @@ -322,8 +332,7 @@ export default class Bundle { // since we're rewriting variable exports, we want to // ensure we don't try and export them again at the bottom - this.toExport = keys( this.entryModule.exports ) - .concat( keys( this.entryModule.reexports ) ) + this.toExport = this.entryModule.getExports() .filter( key => !varExports[ key ] ); let magicString = new MagicString.Bundle({ separator: '\n\n' }); @@ -501,8 +510,12 @@ export default class Bundle { const exportDeclaration = module.exports[ name ]; if ( exportDeclaration ) return this.trace( module, exportDeclaration.localName ); - const exportDelegate = module.exportDelegates[ name ]; - if ( exportDelegate ) return this.traceExport( exportDelegate.module, name, es6 ); + for ( let i = 0; i < module.exportAlls.length; i += 1 ) { + const declaration = module.exportAlls[i]; + if ( declaration.module.exports[ name ] ) { + return this.traceExport( declaration.module, name, es6 ); + } + } throw new Error( `Could not trace binding '${name}' from ${module.id}` ); } diff --git a/src/Module.js b/src/Module.js index 384fa7d..135eb13 100644 --- a/src/Module.js +++ b/src/Module.js @@ -3,8 +3,10 @@ import MagicString from 'magic-string'; import Statement from './Statement'; import walk from './ast/walk'; import { blank, keys } from './utils/object'; +import { basename, extname } from './utils/path'; import getLocation from './utils/getLocation'; import makeLegalIdentifier from './utils/makeLegalIdentifier'; +import SOURCEMAPPING_URL from './utils/sourceMappingURL'; function deconflict ( name, names ) { while ( name in names ) { @@ -37,7 +39,7 @@ export default class Module { }); // remove existing sourceMappingURL comments - const pattern = /\/\/#\s+sourceMappingURL=.+\n?/g; + const pattern = new RegExp( `\\/\\/#\\s+${SOURCEMAPPING_URL}=.+\\n?`, 'g' ); let match; while ( match = pattern.exec( source ) ) { this.magicString.remove( match.index, match.index + match[0].length ); @@ -243,6 +245,10 @@ export default class Module { }); } + basename () { + return makeLegalIdentifier( basename( this.id ).slice( 0, -extname( this.id ).length ) ); + } + bindImportSpecifiers () { if ( this.boundImportSpecifiers ) return; this.boundImportSpecifiers = true; @@ -329,6 +335,16 @@ export default class Module { }); }); + // special case – `export { ... } from './other'` in entry module + if ( this.exportAlls.length ) { + this.exportAlls.forEach( ({ source }) => { + const resolved = this.resolvedIds[ source ]; + const otherModule = this.bundle.moduleById[ resolved ]; + + strongDependencies[ otherModule.id ] = otherModule; + }); + } + return { strongDependencies, weakDependencies }; } @@ -355,6 +371,26 @@ export default class Module { return importDeclaration.module.findDefiningStatement( name ); } + getExports () { + let exports = blank(); + + keys( this.exports ).forEach( name => { + exports[ name ] = true; + }); + + keys( this.reexports ).forEach( name => { + exports[ name ] = true; + }); + + this.exportAlls.forEach( ({ module }) => { + module.getExports().forEach( name => { + if ( name !== 'default' ) exports[ name ] = true; + }); + }); + + return keys( exports ); + } + mark ( name ) { // shortcut cycles if ( this.marked[ name ] ) return; @@ -418,6 +454,12 @@ export default class Module { } } + markAllSideEffects () { + this.statements.forEach( statement => { + statement.markSideEffect(); + }); + } + markAllStatements ( isEntryModule ) { this.statements.forEach( statement => { if ( statement.isIncluded ) return; // TODO can this happen? probably not... @@ -530,8 +572,12 @@ export default class Module { // 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 - this.magicString.remove( node.start, node.declarations[0].start ); + // 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; @@ -642,7 +688,12 @@ export default class Module { statement.replaceIdentifiers( magicString, replacements, bundleExports ); // modify exports as necessary - if ( statement.isExportDeclaration ) { + if ( statement.isReexportDeclaration ) { + // remove `export { foo } from './other'` and `export * from './other'` + magicString.remove( statement.start, statement.next ); + } + + else if ( statement.isExportDeclaration ) { // remove `export` from `export var foo = 42` if ( statement.node.type === 'ExportNamedDeclaration' && statement.node.declaration.type === 'VariableDeclaration' ) { magicString.remove( statement.node.start, statement.node.declaration.start ); diff --git a/src/Statement.js b/src/Statement.js index ae4638d..5cc62cf 100644 --- a/src/Statement.js +++ b/src/Statement.js @@ -3,6 +3,16 @@ import getLocation from './utils/getLocation'; import walk from './ast/walk'; import Scope from './ast/Scope'; +const blockDeclarations = { + 'const': true, + 'let': true +}; + +const modifierNodes = { + AssignmentExpression: 'left', + UpdateExpression: 'argument' +}; + function isIife ( node, parent ) { return parent && parent.type === 'CallExpression' && node === parent.callee; } @@ -31,6 +41,7 @@ export default class Statement { this.reassigns = blank(); + this.hasSideEffects = false; this.isIncluded = false; this.isImportDeclaration = node.type === 'ImportDeclaration'; @@ -49,7 +60,8 @@ export default class Statement { switch ( node.type ) { case 'FunctionDeclaration': - scope.addDeclaration( node.id.name, node, false ); + scope.addDeclaration( node, false, false ); + break; case 'BlockStatement': if ( parent && /Function/.test( parent.type ) ) { @@ -62,7 +74,7 @@ export default class Statement { // named function expressions - the name is considered // part of the function's scope if ( parent.type === 'FunctionExpression' && parent.id ) { - newScope.addDeclaration( parent.id.name, parent, false ); + newScope.addDeclaration( parent, false, false ); } } else { newScope = new Scope({ @@ -84,12 +96,13 @@ export default class Statement { case 'VariableDeclaration': node.declarations.forEach( declarator => { - scope.addDeclaration( declarator.id.name, node, true ); + const isBlockDeclaration = node.type === 'VariableDeclaration' && blockDeclarations[ node.kind ]; + scope.addDeclaration( declarator, isBlockDeclaration, true ); }); break; case 'ClassDeclaration': - scope.addDeclaration( node.id.name, node, false ); + scope.addDeclaration( node, false, false ); break; } @@ -265,14 +278,22 @@ export default class Statement { const id = this.module.resolvedIds[ this.node.source.value ]; const otherModule = this.module.bundle.moduleById[ id ]; - this.node.specifiers.forEach( specifier => { - const reexport = this.module.reexports[ specifier.exported.name ]; + if ( this.node.specifiers ) { + this.node.specifiers.forEach( specifier => { + const reexport = this.module.reexports[ specifier.exported.name ]; - reexport.isUsed = true; - reexport.module = otherModule; // TODO still necessary? + reexport.isUsed = true; + reexport.module = otherModule; // TODO still necessary? - if ( !otherModule.isExternal ) otherModule.markExport( specifier.local.name, specifier.exported.name, this.module ); - }); + if ( !otherModule.isExternal ) otherModule.markExport( specifier.local.name, specifier.exported.name, this.module ); + }); + } else { + otherModule.needsAll = true; + + otherModule.getExports().forEach( name => { + if ( name !== 'default' ) otherModule.markExport( name, name, this.module ); + }); + } return; } @@ -283,6 +304,32 @@ export default class Statement { }); } + markSideEffect () { + const statement = this; + + walk( this.node, { + enter ( node, parent ) { + if ( /Function/.test( node.type ) && !isIife( node, parent ) ) 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 === 'CallExpression' ) { + statement.mark(); + } + + else if ( node.type in modifierNodes ) { + let subject = node[ modifierNodes[ node.type ] ]; + while ( subject.type === 'MemberExpression' ) subject = subject.object; + + const bundle = statement.module.bundle; + const canonicalName = bundle.trace( statement.module, subject.name ); + + if ( bundle.assumedGlobals[ canonicalName ] ) statement.mark(); + } + } + }); + } + replaceIdentifiers ( magicString, names, bundleExports ) { const replacementStack = [ names ]; const nameList = keys( names ); diff --git a/src/ast/Scope.js b/src/ast/Scope.js index aeb9da7..7bd79bf 100644 --- a/src/ast/Scope.js +++ b/src/ast/Scope.js @@ -1,10 +1,38 @@ import { blank } from '../utils/object'; -const blockDeclarations = { - 'const': true, - 'let': true +const extractors = { + Identifier ( names, param ) { + names.push( param.name ); + }, + + ObjectPattern ( names, param ) { + param.properties.forEach( prop => { + extractors[ prop.key.type ]( names, prop.key ); + }); + }, + + ArrayPattern ( names, param ) { + param.elements.forEach( element => { + if ( element ) extractors[ element.type ]( names, element ); + }); + }, + + RestElement ( names, param ) { + extractors[ param.argument.type ]( names, param.argument ); + }, + + AssignmentPattern ( names, param ) { + return extractors[ param.left.type ]( names, param.left ); + } }; +function extractNames ( param ) { + let names = []; + + extractors[ param.type ]( names, param ); + return names; +} + export default class Scope { constructor ( options ) { options = options || {}; @@ -18,26 +46,29 @@ export default class Scope { if ( options.params ) { options.params.forEach( param => { - this.declarations[ param.name ] = param; + extractNames( param ).forEach( name => { + this.declarations[ name ] = true; + }); }); } } - addDeclaration ( name, declaration, isVar ) { - const isBlockDeclaration = declaration.type === 'VariableDeclaration' && blockDeclarations[ declaration.kind ]; - + addDeclaration ( declaration, isBlockDeclaration, isVar ) { if ( !isBlockDeclaration && this.isBlockScope ) { - // it's a `var` or function declaration, and this + // it's a `var` or function node, and this // is a block scope, so we need to go up - this.parent.addDeclaration( name, declaration, isVar ); + this.parent.addDeclaration( declaration, isBlockDeclaration, isVar ); } else { - this.declarations[ name ] = declaration; - if ( isVar ) this.varDeclarations.push( name ); + extractNames( declaration.id ).forEach( name => { + this.declarations[ name ] = true; + if ( isVar ) this.varDeclarations.push( name ); + }); } } contains ( name ) { - return !!this.getDeclaration( name ); + return this.declarations[ name ] || + ( this.parent ? this.parent.contains( name ) : false ); } findDefiningScope ( name ) { @@ -51,9 +82,4 @@ export default class Scope { return null; } - - getDeclaration ( name ) { - return this.declarations[ name ] || - this.parent && this.parent.getDeclaration( name ); - } } diff --git a/src/rollup.js b/src/rollup.js index ee2b3b0..94722e7 100644 --- a/src/rollup.js +++ b/src/rollup.js @@ -1,11 +1,9 @@ import { basename } from './utils/path'; import { writeFile } from 'sander'; import { keys } from './utils/object'; +import SOURCEMAPPING_URL from './utils/sourceMappingURL'; import Bundle from './Bundle'; -let SOURCEMAPPING_URL = 'sourceMa'; -SOURCEMAPPING_URL += 'ppingURL'; - export function rollup ( options ) { if ( !options || !options.entry ) { throw new Error( 'You must supply options.entry to rollup' ); @@ -17,6 +15,9 @@ export function rollup ( options ) { return { imports: bundle.externalModules.map( module => module.id ), exports: keys( bundle.entryModule.exports ), + modules: bundle.orderedModules.map( module => { + return { id: module.id }; + }), generate: options => bundle.render( options ), write: options => { diff --git a/src/utils/getExportMode.js b/src/utils/getExportMode.js index 86ed4d4..3c41629 100644 --- a/src/utils/getExportMode.js +++ b/src/utils/getExportMode.js @@ -5,7 +5,9 @@ function badExports ( option, keys ) { } export default function getExportMode ( bundle, exportMode ) { - const exportKeys = keys( bundle.entryModule.exports ).concat( keys( bundle.entryModule.reexports ) ); + const exportKeys = keys( bundle.entryModule.exports ) + .concat( keys( bundle.entryModule.reexports ) ) + .concat( bundle.entryModule.exportAlls ); // not keys, but makes our job easier this way if ( exportMode === 'default' ) { if ( exportKeys.length !== 1 || exportKeys[0] !== 'default' ) { diff --git a/src/utils/makeLegalIdentifier.js b/src/utils/makeLegalIdentifier.js index 7e0a846..1dd4454 100644 --- a/src/utils/makeLegalIdentifier.js +++ b/src/utils/makeLegalIdentifier.js @@ -8,7 +8,10 @@ reservedWords.concat( builtins ).forEach( word => blacklisted[ word ] = true ); export default function makeLegalIdentifier ( str ) { - str = str.replace( /[^$_a-zA-Z0-9]/g, '_' ); + str = str + .replace( /-(\w)/g, ( _, letter ) => letter.toUpperCase() ) + .replace( /[^$_a-zA-Z0-9]/g, '_' ); + if ( /\d/.test( str[0] ) || blacklisted[ str ] ) str = `_${str}`; return str; diff --git a/src/utils/path.js b/src/utils/path.js index b5db7c1..1ea179f 100644 --- a/src/utils/path.js +++ b/src/utils/path.js @@ -1,6 +1,6 @@ // TODO does this all work on windows? -export const absolutePath = /^(?:\/|(?:[A-Za-z]:)?\\)/; +export const absolutePath = /^(?:\/|(?:[A-Za-z]:)?[\\|\/])/; export function isAbsolute ( path ) { return absolutePath.test( path ); @@ -35,12 +35,10 @@ export function relative ( from, to ) { toParts.shift(); } - while ( toParts[0] && toParts[0][0] === '.' ) { + while ( toParts[0] === '.' || toParts[0] === '..' ) { const toPart = toParts.shift(); if ( toPart === '..' ) { fromParts.pop(); - } else if ( toPart !== '.' ) { - throw new Error( `Unexpected path part (${toPart})` ); } } diff --git a/src/utils/resolveId.js b/src/utils/resolveId.js index 6b82f0d..3090001 100644 --- a/src/utils/resolveId.js +++ b/src/utils/resolveId.js @@ -1,5 +1,14 @@ import { absolutePath, dirname, isAbsolute, resolve } from './path'; -import { readFileSync } from 'sander'; +import { readdirSync, readFileSync } from 'sander'; + +function dirExists ( dir ) { + try { + readdirSync( dir ); + return true; + } catch ( err ) { + return false; + } +} export function defaultResolver ( importee, importer, options ) { // absolute paths are left untouched @@ -10,8 +19,10 @@ export function defaultResolver ( importee, importer, options ) { // we try to resolve external modules if ( importee[0] !== '.' ) { + const [ id ] = importee.split( /[\/\\]/ ); + // unless we want to keep it external, that is - if ( ~options.external.indexOf( importee ) ) return null; + if ( ~options.external.indexOf( id ) ) return null; return options.resolveExternal( importee, importer, options ); } @@ -24,29 +35,32 @@ export function defaultExternalResolver ( id, importer ) { const root = absolutePath.exec( importer )[0]; let dir = dirname( importer ); - while ( dir !== root ) { - const pkgPath = resolve( dir, 'node_modules', id, 'package.json' ); - let pkgJson; + // `foo` should use jsnext:main, but `foo/src/bar` shouldn't + const parts = id.split( /[\/\\]/ ); - try { - pkgJson = readFileSync( pkgPath ).toString(); - } catch ( err ) { - // noop - } + while ( dir !== root && dir !== '.' ) { + const modulePath = resolve( dir, 'node_modules', parts[0] ); + + if ( dirExists( modulePath ) ) { + // `foo/src/bar` + if ( parts.length > 1 ) { + return resolve( modulePath, ...parts.slice( 1 ) ).replace( /\.js$/, '' ) + '.js'; + } - if ( pkgJson ) { + // `foo` + const pkgPath = resolve( modulePath, 'package.json' ); let pkg; try { - pkg = JSON.parse( pkgJson ); + pkg = JSON.parse( readFileSync( pkgPath ).toString() ); } catch ( err ) { - throw new Error( `Malformed JSON: ${pkgPath}` ); + throw new Error( `Missing or malformed package.json: ${modulePath}` ); } const main = pkg[ 'jsnext:main' ]; if ( !main ) { - throw new Error( `Package ${id} does not have a jsnext:main field, and so cannot be included in your rollup. Try adding it as an external module instead (e.g. options.external = ['${id}']). See https://github.com/rollup/rollup/wiki/jsnext:main for more info` ); + throw new Error( `Package ${id} (imported by ${importer}) does not have a jsnext:main field, and so cannot be included in your rollup. Try adding it as an external module instead (e.g. options.external = ['${id}']). See https://github.com/rollup/rollup/wiki/jsnext:main for more info` ); } return resolve( dirname( pkgPath ), main ).replace( /\.js$/, '' ) + '.js'; diff --git a/src/utils/sourceMappingURL.js b/src/utils/sourceMappingURL.js new file mode 100644 index 0000000..616c24d --- /dev/null +++ b/src/utils/sourceMappingURL.js @@ -0,0 +1,6 @@ +// this looks ridiculous, but it prevents sourcemap tooling from mistaking +// this for an actual sourceMappingURL +let SOURCEMAPPING_URL = 'sourceMa'; +SOURCEMAPPING_URL += 'ppingURL'; + +export default SOURCEMAPPING_URL; diff --git a/test/cli/external-modules/main.js b/test/cli/external-modules/main.js index 03f1b74..abaa454 100644 --- a/test/cli/external-modules/main.js +++ b/test/cli/external-modules/main.js @@ -1,5 +1,5 @@ -import { relative } from 'path'; +import { relative, normalize } from 'path'; import { format } from 'util'; assert.equal( format( 'it %s', 'works' ), 'it works' ); -assert.equal( relative( 'a/b/c', 'a/c/b' ), '../../c/b' ); +assert.equal( relative( 'a/b/c', 'a/c/b' ), normalize('../../c/b') ); diff --git a/test/form/export-all-from-internal/_config.js b/test/form/export-all-from-internal/_config.js new file mode 100644 index 0000000..83871ca --- /dev/null +++ b/test/form/export-all-from-internal/_config.js @@ -0,0 +1,6 @@ +module.exports = { + description: 'should be able to export * from the bundle', + options: { + moduleName: 'exposedInternals' + } +}; diff --git a/test/form/export-all-from-internal/_expected/amd.js b/test/form/export-all-from-internal/_expected/amd.js new file mode 100644 index 0000000..6b0c47e --- /dev/null +++ b/test/form/export-all-from-internal/_expected/amd.js @@ -0,0 +1,9 @@ +define(['exports'], function (exports) { 'use strict'; + + const a = 1; + const b = 2; + + exports.a = a; + exports.b = b; + +}); diff --git a/test/form/export-all-from-internal/_expected/cjs.js b/test/form/export-all-from-internal/_expected/cjs.js new file mode 100644 index 0000000..869bba0 --- /dev/null +++ b/test/form/export-all-from-internal/_expected/cjs.js @@ -0,0 +1,7 @@ +'use strict'; + +const a = 1; +const b = 2; + +exports.a = a; +exports.b = b; diff --git a/test/form/export-all-from-internal/_expected/es6.js b/test/form/export-all-from-internal/_expected/es6.js new file mode 100644 index 0000000..aafcac6 --- /dev/null +++ b/test/form/export-all-from-internal/_expected/es6.js @@ -0,0 +1,4 @@ +const a = 1; +const b = 2; + +export { a, b }; diff --git a/test/form/export-all-from-internal/_expected/iife.js b/test/form/export-all-from-internal/_expected/iife.js new file mode 100644 index 0000000..238e208 --- /dev/null +++ b/test/form/export-all-from-internal/_expected/iife.js @@ -0,0 +1,9 @@ +(function (exports) { 'use strict'; + + const a = 1; + const b = 2; + + exports.a = a; + exports.b = b; + +})((this.exposedInternals = {})); diff --git a/test/form/export-all-from-internal/_expected/umd.js b/test/form/export-all-from-internal/_expected/umd.js new file mode 100644 index 0000000..50c5cc4 --- /dev/null +++ b/test/form/export-all-from-internal/_expected/umd.js @@ -0,0 +1,13 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + factory((global.exposedInternals = {})); +}(this, function (exports) { 'use strict'; + + const a = 1; + const b = 2; + + exports.a = a; + exports.b = b; + +})); diff --git a/test/form/export-all-from-internal/internal.js b/test/form/export-all-from-internal/internal.js new file mode 100644 index 0000000..8f791b6 --- /dev/null +++ b/test/form/export-all-from-internal/internal.js @@ -0,0 +1,3 @@ +export const a = 1; +export const b = 2; +export default 42; diff --git a/test/form/export-all-from-internal/main.js b/test/form/export-all-from-internal/main.js new file mode 100644 index 0000000..676c1ba --- /dev/null +++ b/test/form/export-all-from-internal/main.js @@ -0,0 +1 @@ +export * from './internal.js'; diff --git a/test/form/internal-conflict-resolution/_expected/amd.js b/test/form/internal-conflict-resolution/_expected/amd.js index f2b3c4a..24d63fa 100644 --- a/test/form/internal-conflict-resolution/_expected/amd.js +++ b/test/form/internal-conflict-resolution/_expected/amd.js @@ -1,9 +1,9 @@ define(function () { 'use strict'; - var _bar = 42; + var bar$1 = 42; function foo () { - return _bar; + return bar$1; } function bar () { diff --git a/test/form/internal-conflict-resolution/_expected/cjs.js b/test/form/internal-conflict-resolution/_expected/cjs.js index 74ae2f8..76c44a7 100644 --- a/test/form/internal-conflict-resolution/_expected/cjs.js +++ b/test/form/internal-conflict-resolution/_expected/cjs.js @@ -1,9 +1,9 @@ 'use strict'; -var _bar = 42; +var bar$1 = 42; function foo () { - return _bar; + return bar$1; } function bar () { diff --git a/test/form/internal-conflict-resolution/_expected/es6.js b/test/form/internal-conflict-resolution/_expected/es6.js index e1fc1f7..086f425 100644 --- a/test/form/internal-conflict-resolution/_expected/es6.js +++ b/test/form/internal-conflict-resolution/_expected/es6.js @@ -1,7 +1,7 @@ -var _bar = 42; +var bar$1 = 42; function foo () { - return _bar; + return bar$1; } function bar () { diff --git a/test/form/internal-conflict-resolution/_expected/iife.js b/test/form/internal-conflict-resolution/_expected/iife.js index efe296f..b32c809 100644 --- a/test/form/internal-conflict-resolution/_expected/iife.js +++ b/test/form/internal-conflict-resolution/_expected/iife.js @@ -1,9 +1,9 @@ (function () { 'use strict'; - var _bar = 42; + var bar$1 = 42; function foo () { - return _bar; + return bar$1; } function bar () { diff --git a/test/form/internal-conflict-resolution/_expected/umd.js b/test/form/internal-conflict-resolution/_expected/umd.js index 979f229..c879634 100644 --- a/test/form/internal-conflict-resolution/_expected/umd.js +++ b/test/form/internal-conflict-resolution/_expected/umd.js @@ -4,10 +4,10 @@ factory(); }(this, function () { 'use strict'; - var _bar = 42; + var bar$1 = 42; function foo () { - return _bar; + return bar$1; } function bar () { diff --git a/test/form/namespace-optimization/_config.js b/test/form/namespace-optimization/_config.js new file mode 100644 index 0000000..e6eccae --- /dev/null +++ b/test/form/namespace-optimization/_config.js @@ -0,0 +1,4 @@ +module.exports = { + skip: true, + description: 'it does static lookup optimization of internal namespaces' +}; diff --git a/test/form/namespace-optimization/_expected/amd.js b/test/form/namespace-optimization/_expected/amd.js new file mode 100644 index 0000000..a244c47 --- /dev/null +++ b/test/form/namespace-optimization/_expected/amd.js @@ -0,0 +1,7 @@ +define(function () { 'use strict'; + + function a () {} + + a(); + +}); diff --git a/test/form/namespace-optimization/_expected/cjs.js b/test/form/namespace-optimization/_expected/cjs.js new file mode 100644 index 0000000..b52a7e5 --- /dev/null +++ b/test/form/namespace-optimization/_expected/cjs.js @@ -0,0 +1,5 @@ +'use strict'; + +function a () {} + +a(); diff --git a/test/form/namespace-optimization/_expected/es6.js b/test/form/namespace-optimization/_expected/es6.js new file mode 100644 index 0000000..8bee044 --- /dev/null +++ b/test/form/namespace-optimization/_expected/es6.js @@ -0,0 +1,3 @@ +function a () {} + +a(); diff --git a/test/form/namespace-optimization/_expected/iife.js b/test/form/namespace-optimization/_expected/iife.js new file mode 100644 index 0000000..aac8ff9 --- /dev/null +++ b/test/form/namespace-optimization/_expected/iife.js @@ -0,0 +1,7 @@ +(function () { 'use strict'; + + function a () {} + + a(); + +})(); diff --git a/test/form/namespace-optimization/_expected/umd.js b/test/form/namespace-optimization/_expected/umd.js new file mode 100644 index 0000000..38f7835 --- /dev/null +++ b/test/form/namespace-optimization/_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'; + + function a () {} + + a(); + +})); diff --git a/test/form/namespace-optimization/bar.js b/test/form/namespace-optimization/bar.js new file mode 100644 index 0000000..aa96676 --- /dev/null +++ b/test/form/namespace-optimization/bar.js @@ -0,0 +1,3 @@ +import * as quux from './quux'; + +export { quux }; diff --git a/test/form/namespace-optimization/foo.js b/test/form/namespace-optimization/foo.js new file mode 100644 index 0000000..42a93ae --- /dev/null +++ b/test/form/namespace-optimization/foo.js @@ -0,0 +1,3 @@ +import * as bar from './bar'; + +export { bar }; diff --git a/test/form/namespace-optimization/main.js b/test/form/namespace-optimization/main.js new file mode 100644 index 0000000..e902244 --- /dev/null +++ b/test/form/namespace-optimization/main.js @@ -0,0 +1,3 @@ +import * as foo from './foo'; + +foo.bar.quux.a(); diff --git a/test/form/namespace-optimization/quux.js b/test/form/namespace-optimization/quux.js new file mode 100644 index 0000000..103a9f0 --- /dev/null +++ b/test/form/namespace-optimization/quux.js @@ -0,0 +1 @@ +export function a () {} diff --git a/test/form/unused-side-effect/_config.js b/test/form/unused-side-effect/_config.js new file mode 100644 index 0000000..0431077 --- /dev/null +++ b/test/form/unused-side-effect/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'side-effects to non-globals are not blindly included' +}; diff --git a/test/form/unused-side-effect/_expected/amd.js b/test/form/unused-side-effect/_expected/amd.js new file mode 100644 index 0000000..c82bf4f --- /dev/null +++ b/test/form/unused-side-effect/_expected/amd.js @@ -0,0 +1,7 @@ +define(function () { 'use strict'; + + var foo = 42; + + assert.equal( foo, 42 ); + +}); diff --git a/test/form/unused-side-effect/_expected/cjs.js b/test/form/unused-side-effect/_expected/cjs.js new file mode 100644 index 0000000..0d2573f --- /dev/null +++ b/test/form/unused-side-effect/_expected/cjs.js @@ -0,0 +1,5 @@ +'use strict'; + +var foo = 42; + +assert.equal( foo, 42 ); diff --git a/test/form/unused-side-effect/_expected/es6.js b/test/form/unused-side-effect/_expected/es6.js new file mode 100644 index 0000000..e50ecda --- /dev/null +++ b/test/form/unused-side-effect/_expected/es6.js @@ -0,0 +1,3 @@ +var foo = 42; + +assert.equal( foo, 42 ); diff --git a/test/form/unused-side-effect/_expected/iife.js b/test/form/unused-side-effect/_expected/iife.js new file mode 100644 index 0000000..a3f7fc8 --- /dev/null +++ b/test/form/unused-side-effect/_expected/iife.js @@ -0,0 +1,7 @@ +(function () { 'use strict'; + + var foo = 42; + + assert.equal( foo, 42 ); + +})(); diff --git a/test/form/unused-side-effect/_expected/umd.js b/test/form/unused-side-effect/_expected/umd.js new file mode 100644 index 0000000..f96fd30 --- /dev/null +++ b/test/form/unused-side-effect/_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 = 42; + + assert.equal( foo, 42 ); + +})); diff --git a/test/form/unused-side-effect/foo.js b/test/form/unused-side-effect/foo.js new file mode 100644 index 0000000..7953f6e --- /dev/null +++ b/test/form/unused-side-effect/foo.js @@ -0,0 +1,13 @@ +var uid = 0; +uid = 1; +uid += 1; +uid++; + +// ensure identifiers aren't treated as globals just because +// var declaration hasn't been encountered yet... +uid2 = 1; +uid2 += 1; +uid2++; +var uid2; + +export var foo = 42; diff --git a/test/form/unused-side-effect/main.js b/test/form/unused-side-effect/main.js new file mode 100644 index 0000000..ef12cae --- /dev/null +++ b/test/form/unused-side-effect/main.js @@ -0,0 +1,2 @@ +import { foo } from './foo'; +assert.equal( foo, 42 ); diff --git a/test/function/allows-external-modules-from-nested-module/main.js b/test/function/allows-external-modules-from-nested-module/main.js index 8bacdc9..bdd4f85 100644 --- a/test/function/allows-external-modules-from-nested-module/main.js +++ b/test/function/allows-external-modules-from-nested-module/main.js @@ -1,8 +1,8 @@ -import { relative } from 'path'; +import { relative, normalize } from 'path'; import foo from './foo'; var path = 'foo/bar/baz'; var path2 = 'foo/baz/bar'; -assert.equal( relative( path, path2 ), '../../baz/bar' ); -assert.equal( foo, '../../c/b' ); \ No newline at end of file +assert.equal( relative( path, path2 ), normalize('../../baz/bar') ); +assert.equal( foo, normalize('../../c/b') ); \ No newline at end of file diff --git a/test/function/assignment-patterns/_config.js b/test/function/assignment-patterns/_config.js new file mode 100644 index 0000000..d4afdc3 --- /dev/null +++ b/test/function/assignment-patterns/_config.js @@ -0,0 +1,4 @@ +module.exports = { + description: 'allows reassigments to default parameters that shadow imports', + babel: true +}; diff --git a/test/function/assignment-patterns/main.js b/test/function/assignment-patterns/main.js new file mode 100644 index 0000000..844c908 --- /dev/null +++ b/test/function/assignment-patterns/main.js @@ -0,0 +1,21 @@ +import { bar, baz, x, items, p, q, r, s } from './other'; + +function foo ( bar = 1, { baz } = { baz: 2 }, [[[,x = 3] = []] = []] = [], ...items ) { + bar += 1; + baz += 1; + x += 1; + + let { p, q } = { p: 4, q: 5 }; + let [ r, s ] = [ 6, 7 ]; + + p++; + q += 1; + r = 7; + s = 6; + + return bar + baz + x + items.length + p + q + r + s; +} + +assert.equal( foo(), 33 ); +assert.equal( foo( 2 ), 34 ); +assert.equal( foo( 2, { baz: 3 }, [[[99,10]]], 'a', 'b', 'c' ), 45 ); diff --git a/test/function/assignment-patterns/other.js b/test/function/assignment-patterns/other.js new file mode 100644 index 0000000..840584c --- /dev/null +++ b/test/function/assignment-patterns/other.js @@ -0,0 +1,8 @@ +export const bar = 'bar'; +export const baz = 'baz'; +export const x = 'x'; +export const items = 'items'; +export const p = 'p'; +export const q = 'q'; +export const r = 'r'; +export const s = 's'; diff --git a/test/function/cannot-import-self/_config.js b/test/function/cannot-import-self/_config.js new file mode 100644 index 0000000..2413d03 --- /dev/null +++ b/test/function/cannot-import-self/_config.js @@ -0,0 +1,8 @@ +var assert = require( 'assert' ); + +module.exports = { + description: 'prevents a module importing itself', + error: function ( err ) { + assert.ok( /A module cannot import itself/.test( err.message ) ); + } +}; diff --git a/test/function/cannot-import-self/main.js b/test/function/cannot-import-self/main.js new file mode 100644 index 0000000..48e08e6 --- /dev/null +++ b/test/function/cannot-import-self/main.js @@ -0,0 +1 @@ +import me from './main'; diff --git a/test/function/custom-path-resolver-async/_config.js b/test/function/custom-path-resolver-async/_config.js index 287f536..c336d67 100644 --- a/test/function/custom-path-resolver-async/_config.js +++ b/test/function/custom-path-resolver-async/_config.js @@ -8,7 +8,7 @@ module.exports = { var Promise = require( 'sander' ).Promise; var resolved; - if ( importee === path.resolve( __dirname, 'main.js' ) ) return importee; + if ( path.normalize(importee) === path.resolve( __dirname, 'main.js' ) ) return importee; if ( importee === 'foo' ) { resolved = path.resolve( __dirname, 'bar.js' ); diff --git a/test/function/custom-path-resolver-sync/_config.js b/test/function/custom-path-resolver-sync/_config.js index 9a755e5..ee993e1 100644 --- a/test/function/custom-path-resolver-sync/_config.js +++ b/test/function/custom-path-resolver-sync/_config.js @@ -5,7 +5,7 @@ module.exports = { description: 'uses a custom path resolver (synchronous)', options: { resolveId: function ( importee, importer ) { - if ( importee === path.resolve( __dirname, 'main.js' ) ) return importee; + if ( path.normalize(importee) === path.resolve( __dirname, 'main.js' ) ) return importee; if ( importee === 'foo' ) return path.resolve( __dirname, 'bar.js' ); return false; diff --git a/test/function/duplicate-import-fails/_config.js b/test/function/duplicate-import-fails/_config.js index 4280c53..4083690 100644 --- a/test/function/duplicate-import-fails/_config.js +++ b/test/function/duplicate-import-fails/_config.js @@ -4,7 +4,7 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows duplicate imports', error: function ( err ) { - assert.equal( err.file, path.resolve( __dirname, 'main.js' ) ); + assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); assert.deepEqual( err.loc, { line: 2, column: 9 }); assert.ok( /Duplicated import/.test( err.message ) ); } diff --git a/test/function/duplicate-import-specifier-fails/_config.js b/test/function/duplicate-import-specifier-fails/_config.js index b935a69..e3957b5 100644 --- a/test/function/duplicate-import-specifier-fails/_config.js +++ b/test/function/duplicate-import-specifier-fails/_config.js @@ -4,7 +4,7 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows duplicate import specifiers', error: function ( err ) { - assert.equal( err.file, path.resolve( __dirname, 'main.js' ) ); + assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); assert.deepEqual( err.loc, { line: 1, column: 12 }); assert.ok( /Duplicated import/.test( err.message ) ); } diff --git a/test/function/dynamic-namespace-lookup/_config.js b/test/function/dynamic-namespace-lookup/_config.js new file mode 100644 index 0000000..3c2fe54 --- /dev/null +++ b/test/function/dynamic-namespace-lookup/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'does namespace optimization when possible, but not for dynamic lookups' +}; diff --git a/test/function/dynamic-namespace-lookup/foo.js b/test/function/dynamic-namespace-lookup/foo.js new file mode 100644 index 0000000..a727aee --- /dev/null +++ b/test/function/dynamic-namespace-lookup/foo.js @@ -0,0 +1,2 @@ +export var bar = 'bar'; +export var baz = 'baz'; diff --git a/test/function/dynamic-namespace-lookup/main.js b/test/function/dynamic-namespace-lookup/main.js new file mode 100644 index 0000000..ee1ce10 --- /dev/null +++ b/test/function/dynamic-namespace-lookup/main.js @@ -0,0 +1,8 @@ +import * as foo from './foo'; + +var bar = 'baz'; + +assert.equal( foo.bar, 'bar' ); +assert.equal( foo.baz, 'baz' ); + +assert.equal( foo[ bar ], 'baz' ); diff --git a/test/function/export-not-at-top-level-fails/_config.js b/test/function/export-not-at-top-level-fails/_config.js index c68ca42..2dadeab 100644 --- a/test/function/export-not-at-top-level-fails/_config.js +++ b/test/function/export-not-at-top-level-fails/_config.js @@ -4,7 +4,7 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows non-top-level exports', error: function ( err ) { - assert.equal( err.file, path.resolve( __dirname, 'main.js' ) ); + assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); assert.deepEqual( err.loc, { line: 2, column: 2 }); assert.ok( /may only appear at the top level/.test( err.message ) ); } diff --git a/test/function/export-type-mismatch-b/_config.js b/test/function/export-type-mismatch-b/_config.js new file mode 100644 index 0000000..25a7220 --- /dev/null +++ b/test/function/export-type-mismatch-b/_config.js @@ -0,0 +1,11 @@ +var assert = require( 'assert' ); + +module.exports = { + description: 'export type must be auto, default, named or none', + bundleOptions: { + exports: 'blah' + }, + generateError: function ( err ) { + assert.ok( /options\.exports must be 'default', 'named', 'none', 'auto', or left unspecified/.test( err.message ) ); + } +}; diff --git a/test/function/export-type-mismatch-b/main.js b/test/function/export-type-mismatch-b/main.js new file mode 100644 index 0000000..7a4e8a7 --- /dev/null +++ b/test/function/export-type-mismatch-b/main.js @@ -0,0 +1 @@ +export default 42; diff --git a/test/function/export-type-mismatch-c/_config.js b/test/function/export-type-mismatch-c/_config.js new file mode 100644 index 0000000..8df0d58 --- /dev/null +++ b/test/function/export-type-mismatch-c/_config.js @@ -0,0 +1,11 @@ +var assert = require( 'assert' ); + +module.exports = { + description: 'cannot have named exports if explicit export type is default', + bundleOptions: { + exports: 'none' + }, + generateError: function ( err ) { + assert.ok( /'none' was specified for options\.exports/.test( err.message ) ); + } +}; diff --git a/test/function/export-type-mismatch-c/main.js b/test/function/export-type-mismatch-c/main.js new file mode 100644 index 0000000..7a4e8a7 --- /dev/null +++ b/test/function/export-type-mismatch-c/main.js @@ -0,0 +1 @@ +export default 42; diff --git a/test/function/export-type-mismatch/_config.js b/test/function/export-type-mismatch/_config.js new file mode 100644 index 0000000..5b321d6 --- /dev/null +++ b/test/function/export-type-mismatch/_config.js @@ -0,0 +1,11 @@ +var assert = require( 'assert' ); + +module.exports = { + description: 'cannot have named exports if explicit export type is default', + bundleOptions: { + exports: 'default' + }, + generateError: function ( err ) { + assert.ok( /'default' was specified for options\.exports/.test( err.message ) ); + } +}; diff --git a/test/function/export-type-mismatch/main.js b/test/function/export-type-mismatch/main.js new file mode 100644 index 0000000..3b8dc9f --- /dev/null +++ b/test/function/export-type-mismatch/main.js @@ -0,0 +1 @@ +export var foo = 42; diff --git a/test/function/globally-called-modifying-function/_config.js b/test/function/globally-called-modifying-function/_config.js new file mode 100644 index 0000000..86b2f62 --- /dev/null +++ b/test/function/globally-called-modifying-function/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'globally called function should be included if it modifies an exported value (#112)' +}; diff --git a/test/function/globally-called-modifying-function/main.js b/test/function/globally-called-modifying-function/main.js new file mode 100644 index 0000000..3b2061e --- /dev/null +++ b/test/function/globally-called-modifying-function/main.js @@ -0,0 +1,3 @@ +import value from './module.js'; + +assert.equal( value, 3 ); diff --git a/test/function/globally-called-modifying-function/module.js b/test/function/globally-called-modifying-function/module.js new file mode 100644 index 0000000..fc50ee5 --- /dev/null +++ b/test/function/globally-called-modifying-function/module.js @@ -0,0 +1,17 @@ +var value = 1; + +function change () { + value = 2; +} + +function changeAgain () { + value += 1; +} + +change(); + +if ( true ) { + changeAgain(); +} + +export default value; diff --git a/test/function/handles-multiple-declarations/_config.js b/test/function/handles-multiple-declarations/_config.js new file mode 100644 index 0000000..94022ee --- /dev/null +++ b/test/function/handles-multiple-declarations/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'handles multiple declaration blocks with multiple declarations (#105)' +}; diff --git a/test/function/handles-multiple-declarations/main.js b/test/function/handles-multiple-declarations/main.js new file mode 100644 index 0000000..a8af4cd --- /dev/null +++ b/test/function/handles-multiple-declarations/main.js @@ -0,0 +1,2 @@ +var a, b; +var c, d; diff --git a/test/function/has-modules-array/_config.js b/test/function/has-modules-array/_config.js new file mode 100644 index 0000000..a2a12c3 --- /dev/null +++ b/test/function/has-modules-array/_config.js @@ -0,0 +1,12 @@ +var path = require( 'path' ); +var assert = require( 'assert' ); + +module.exports = { + description: 'user-facing bundle has modules array', + bundle: function ( bundle ) { + assert.ok( bundle.modules ); + assert.equal( bundle.modules.length, 2 ); + assert.equal( path.relative(bundle.modules[0].id, path.resolve(__dirname, 'foo.js')), '' ); + assert.equal( path.relative(bundle.modules[1].id, path.resolve(__dirname, 'main.js')), '' ); + } +}; diff --git a/test/function/has-modules-array/foo.js b/test/function/has-modules-array/foo.js new file mode 100644 index 0000000..7a4e8a7 --- /dev/null +++ b/test/function/has-modules-array/foo.js @@ -0,0 +1 @@ +export default 42; diff --git a/test/function/has-modules-array/main.js b/test/function/has-modules-array/main.js new file mode 100644 index 0000000..3206f53 --- /dev/null +++ b/test/function/has-modules-array/main.js @@ -0,0 +1,2 @@ +import foo from './foo'; +assert.equal( foo, 42 ); diff --git a/test/function/import-default-from-external/main.js b/test/function/import-default-from-external/main.js index 5d5cd6d..aacce04 100644 --- a/test/function/import-default-from-external/main.js +++ b/test/function/import-default-from-external/main.js @@ -4,4 +4,4 @@ import path from 'path'; var path1 = 'foo/bar/baz'; var path2 = 'foo/baz/bar'; -assert.equal( path.relative( path1, path2 ), '../../baz/bar' ); \ No newline at end of file +assert.equal( path.relative( path1, path2 ), path.normalize('../../baz/bar') ); \ No newline at end of file diff --git a/test/function/import-from-external-subdirectory/_config.js b/test/function/import-from-external-subdirectory/_config.js new file mode 100644 index 0000000..57a682c --- /dev/null +++ b/test/function/import-from-external-subdirectory/_config.js @@ -0,0 +1,4 @@ +module.exports = { + description: 'default resolver imports from a subdirectory of an external module', + babel: true +}; diff --git a/test/function/import-from-external-subdirectory/main.js b/test/function/import-from-external-subdirectory/main.js new file mode 100644 index 0000000..966ed65 --- /dev/null +++ b/test/function/import-from-external-subdirectory/main.js @@ -0,0 +1,5 @@ +// this test is brittle, it relies on this dependency continuing +// to be structured in a certain way +import btoa from 'magic-string/src/utils/btoa'; + +assert.equal( btoa( 'it works' ), new Buffer( 'it works' ).toString( 'base64' ) ); diff --git a/test/function/import-named-from-external/main.js b/test/function/import-named-from-external/main.js index 49027f6..1030a82 100644 --- a/test/function/import-named-from-external/main.js +++ b/test/function/import-named-from-external/main.js @@ -1,6 +1,6 @@ -import { relative } from 'path'; +import { relative, normalize } from 'path'; var path = 'foo/bar/baz'; var path2 = 'foo/baz/bar'; -assert.equal( relative( path, path2 ), '../../baz/bar' ); \ No newline at end of file +assert.equal( relative( path, path2 ), normalize('../../baz/bar') ); \ No newline at end of file diff --git a/test/function/import-namespace-from-external-module-renamed/main.js b/test/function/import-namespace-from-external-module-renamed/main.js index 2208cd4..947231e 100644 --- a/test/function/import-namespace-from-external-module-renamed/main.js +++ b/test/function/import-namespace-from-external-module-renamed/main.js @@ -3,4 +3,4 @@ import * as node_path from 'path'; var path1 = 'foo/bar/baz'; var path2 = 'foo/baz/bar'; -assert.equal( node_path.relative( path1, path2 ), '../../baz/bar' ); +assert.equal( node_path.relative( path1, path2 ), node_path.normalize('../../baz/bar') ); diff --git a/test/function/import-namespace-from-external-module/main.js b/test/function/import-namespace-from-external-module/main.js index 000a219..10f9d36 100644 --- a/test/function/import-namespace-from-external-module/main.js +++ b/test/function/import-namespace-from-external-module/main.js @@ -3,4 +3,4 @@ import * as path from 'path'; var path1 = 'foo/bar/baz'; var path2 = 'foo/baz/bar'; -assert.equal( path.relative( path1, path2 ), '../../baz/bar' ); \ No newline at end of file +assert.equal( path.relative( path1, path2 ), path.normalize('../../baz/bar') ); \ No newline at end of file diff --git a/test/function/import-not-at-top-level-fails/_config.js b/test/function/import-not-at-top-level-fails/_config.js index c75e986..4f873aa 100644 --- a/test/function/import-not-at-top-level-fails/_config.js +++ b/test/function/import-not-at-top-level-fails/_config.js @@ -4,7 +4,7 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows non-top-level imports', error: function ( err ) { - assert.equal( err.file, path.resolve( __dirname, 'main.js' ) ); + assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); assert.deepEqual( err.loc, { line: 2, column: 2 }); assert.ok( /may only appear at the top level/.test( err.message ) ); } diff --git a/test/function/import-of-unexported-fails/_config.js b/test/function/import-of-unexported-fails/_config.js new file mode 100644 index 0000000..3027ba9 --- /dev/null +++ b/test/function/import-of-unexported-fails/_config.js @@ -0,0 +1,8 @@ +var assert = require( 'assert' ); + +module.exports = { + description: 'marking an imported, but unexported, identifier should throw', + error: function ( err ) { + assert.ok( /Module .+empty\.js does not export default \(imported by .+main\.js\)/.test( err.message ) ); + } +}; diff --git a/test/function/import-of-unexported-fails/empty.js b/test/function/import-of-unexported-fails/empty.js new file mode 100644 index 0000000..e69de29 diff --git a/test/function/import-of-unexported-fails/main.js b/test/function/import-of-unexported-fails/main.js new file mode 100644 index 0000000..37cf334 --- /dev/null +++ b/test/function/import-of-unexported-fails/main.js @@ -0,0 +1,3 @@ +import a from './empty.js'; + +a(); diff --git a/test/function/imports-are-deconflicted-b/main.js b/test/function/imports-are-deconflicted-b/main.js index fa81bd0..cf917fc 100644 --- a/test/function/imports-are-deconflicted-b/main.js +++ b/test/function/imports-are-deconflicted-b/main.js @@ -1,5 +1,6 @@ import foo from './foo'; import bar from './bar'; +import { normalize } from 'path'; assert.equal( foo, 'foo' ); -assert.equal( bar(), '../../baz/bar' ); +assert.equal( bar(), normalize('../../baz/bar') ); diff --git a/test/function/imports-are-deconflicted/main.js b/test/function/imports-are-deconflicted/main.js index fa81bd0..cf917fc 100644 --- a/test/function/imports-are-deconflicted/main.js +++ b/test/function/imports-are-deconflicted/main.js @@ -1,5 +1,6 @@ import foo from './foo'; import bar from './bar'; +import { normalize } from 'path'; assert.equal( foo, 'foo' ); -assert.equal( bar(), '../../baz/bar' ); +assert.equal( bar(), normalize('../../baz/bar') ); diff --git a/test/function/modify-assumed-global/_config.js b/test/function/modify-assumed-global/_config.js new file mode 100644 index 0000000..ee89a52 --- /dev/null +++ b/test/function/modify-assumed-global/_config.js @@ -0,0 +1,14 @@ +var assert = require( 'assert' ); + +var Math = {}; + +module.exports = { + description: 'side-effects to assumed globals are included', + context: { + Math: Math + }, + exports: function ( exports ) { + assert.equal( Math.square( 3 ), 9 ); + assert.equal( Math.cube( 3 ), 27 ); + } +}; diff --git a/test/function/modify-assumed-global/main.js b/test/function/modify-assumed-global/main.js new file mode 100644 index 0000000..cc4f217 --- /dev/null +++ b/test/function/modify-assumed-global/main.js @@ -0,0 +1,3 @@ +import { square } from './math'; + +assert.equal( square( 2 ), 4 ); diff --git a/test/function/modify-assumed-global/math.js b/test/function/modify-assumed-global/math.js new file mode 100644 index 0000000..469df59 --- /dev/null +++ b/test/function/modify-assumed-global/math.js @@ -0,0 +1,15 @@ +function square ( x ) { + return x * x; +} + +function cube ( x ) { + return x * x * x; +} + +Math.square = square; + +if ( true ) { + Math.cube = cube; +} + +export { square }; diff --git a/test/function/module-sort-order/_config.js b/test/function/module-sort-order/_config.js new file mode 100644 index 0000000..a84538d --- /dev/null +++ b/test/function/module-sort-order/_config.js @@ -0,0 +1,5 @@ +module.exports = { + // solo: true, + // show: true, + description: 'module sorter is not confused by top-level call expressions' +}; diff --git a/test/function/module-sort-order/a.js b/test/function/module-sort-order/a.js new file mode 100644 index 0000000..cf43071 --- /dev/null +++ b/test/function/module-sort-order/a.js @@ -0,0 +1,20 @@ +import { b } from './b'; +import z from './z'; + +z(); + +const p = { + q: function () { + b.nope(); + } +}; + +(function () { + const p = { + q: function () { + b.nope(); + } + }; +})(); + +export default 42; diff --git a/test/function/module-sort-order/b.js b/test/function/module-sort-order/b.js new file mode 100644 index 0000000..3a7e6b7 --- /dev/null +++ b/test/function/module-sort-order/b.js @@ -0,0 +1 @@ +export const b = function () {}; diff --git a/test/function/module-sort-order/c.js b/test/function/module-sort-order/c.js new file mode 100644 index 0000000..4bbf7c1 --- /dev/null +++ b/test/function/module-sort-order/c.js @@ -0,0 +1,3 @@ +import { b } from './b'; + +export const c = function () {}; diff --git a/test/function/module-sort-order/main.js b/test/function/module-sort-order/main.js new file mode 100644 index 0000000..dcd42cb --- /dev/null +++ b/test/function/module-sort-order/main.js @@ -0,0 +1,4 @@ +import a from './a'; +import z from './z'; + +z(); diff --git a/test/function/module-sort-order/z.js b/test/function/module-sort-order/z.js new file mode 100644 index 0000000..8b67172 --- /dev/null +++ b/test/function/module-sort-order/z.js @@ -0,0 +1,5 @@ +import { c } from './c'; + +export default function () { + c(); +} diff --git a/test/function/namespace-reassign-import-fails/_config.js b/test/function/namespace-reassign-import-fails/_config.js index 7e2d7a8..2d606f4 100644 --- a/test/function/namespace-reassign-import-fails/_config.js +++ b/test/function/namespace-reassign-import-fails/_config.js @@ -4,7 +4,7 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows reassignments to namespace exports', error: function ( err ) { - assert.equal( err.file, path.resolve( __dirname, 'main.js' ) ); + assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); assert.deepEqual( err.loc, { line: 3, column: 0 }); assert.ok( /Illegal reassignment/.test( err.message ) ); } diff --git a/test/function/namespace-update-import-fails/_config.js b/test/function/namespace-update-import-fails/_config.js index 116f4d0..4cff5c0 100644 --- a/test/function/namespace-update-import-fails/_config.js +++ b/test/function/namespace-update-import-fails/_config.js @@ -4,7 +4,7 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows updates to namespace exports', error: function ( err ) { - assert.equal( err.file, path.resolve( __dirname, 'main.js' ) ); + assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); assert.deepEqual( err.loc, { line: 3, column: 0 }); assert.ok( /Illegal reassignment/.test( err.message ) ); } diff --git a/test/function/property-keys-not-renamed/_config.js b/test/function/property-keys-not-renamed/_config.js new file mode 100644 index 0000000..d4e1f82 --- /dev/null +++ b/test/function/property-keys-not-renamed/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'does not rename property keys' +}; diff --git a/test/function/property-keys-not-renamed/main.js b/test/function/property-keys-not-renamed/main.js new file mode 100644 index 0000000..9134b8e --- /dev/null +++ b/test/function/property-keys-not-renamed/main.js @@ -0,0 +1,7 @@ +import one from './one'; +import two from './two'; +import three from './three'; + +assert.equal( one(), 'one' ); +assert.equal( two(), 'two' ); +assert.equal( three(), 'three' ); diff --git a/test/function/property-keys-not-renamed/one.js b/test/function/property-keys-not-renamed/one.js new file mode 100644 index 0000000..ab4a0f9 --- /dev/null +++ b/test/function/property-keys-not-renamed/one.js @@ -0,0 +1,11 @@ +var obj = { + foo: foo +}; + +function foo () { + return 'one'; +} + +export default function () { + return obj.foo(); +} diff --git a/test/function/property-keys-not-renamed/three.js b/test/function/property-keys-not-renamed/three.js new file mode 100644 index 0000000..a78e2e5 --- /dev/null +++ b/test/function/property-keys-not-renamed/three.js @@ -0,0 +1,11 @@ +var obj = { + foo: foo +}; + +function foo () { + return 'three'; +} + +export default function () { + return obj.foo(); +} diff --git a/test/function/property-keys-not-renamed/two.js b/test/function/property-keys-not-renamed/two.js new file mode 100644 index 0000000..70e142b --- /dev/null +++ b/test/function/property-keys-not-renamed/two.js @@ -0,0 +1,11 @@ +var obj = { + foo: foo +}; + +function foo () { + return 'two'; +} + +export default function () { + return obj.foo(); +} diff --git a/test/function/reassign-import-fails/_config.js b/test/function/reassign-import-fails/_config.js index c73c36d..e01d090 100644 --- a/test/function/reassign-import-fails/_config.js +++ b/test/function/reassign-import-fails/_config.js @@ -4,7 +4,7 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows assignments to imported bindings', error: function ( err ) { - assert.equal( err.file, path.resolve( __dirname, 'main.js' ) ); + 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-import-not-at-top-level-fails/_config.js b/test/function/reassign-import-not-at-top-level-fails/_config.js index d17af29..6e00786 100644 --- a/test/function/reassign-import-not-at-top-level-fails/_config.js +++ b/test/function/reassign-import-not-at-top-level-fails/_config.js @@ -4,7 +4,7 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows assignments to imported bindings not at the top level', error: function ( err ) { - assert.equal( err.file, path.resolve( __dirname, 'main.js' ) ); + assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); assert.deepEqual( err.loc, { line: 7, column: 2 }); assert.ok( /Illegal reassignment/.test( err.message ) ); } diff --git a/test/function/shadowed-external-export/main.js b/test/function/shadowed-external-export/main.js index d8a080a..37ce4be 100644 --- a/test/function/shadowed-external-export/main.js +++ b/test/function/shadowed-external-export/main.js @@ -1,4 +1,4 @@ -import { relative } from 'path'; +import { relative, normalize } from 'path'; var paths = {}; function getRelativePath ( path, path2 ) { @@ -6,5 +6,5 @@ function getRelativePath ( path, path2 ) { return relative( path, path2 ); } -assert.equal( getRelativePath( 'foo/bar/baz', 'foo/baz/bar' ), '../../baz/bar' ); +assert.equal( getRelativePath( 'foo/bar/baz', 'foo/baz/bar' ), normalize('../../baz/bar') ); assert.deepEqual( paths, { 'foo/bar/baz': true }); diff --git a/test/function/shorthand-properties/baz.js b/test/function/shorthand-properties/baz.js new file mode 100644 index 0000000..d826bfa --- /dev/null +++ b/test/function/shorthand-properties/baz.js @@ -0,0 +1,3 @@ +export default function bar () { + return 'main-bar'; +} diff --git a/test/function/shorthand-properties/foo.js b/test/function/shorthand-properties/foo.js index 25941fc..831360e 100644 --- a/test/function/shorthand-properties/foo.js +++ b/test/function/shorthand-properties/foo.js @@ -1,7 +1,10 @@ +import baz from './baz.js'; + function bar () { return 'foo-bar'; } export var foo = { - bar + bar, + baz }; diff --git a/test/function/shorthand-properties/main.js b/test/function/shorthand-properties/main.js index 5118a11..754fba8 100644 --- a/test/function/shorthand-properties/main.js +++ b/test/function/shorthand-properties/main.js @@ -1,8 +1,5 @@ +import bar from './baz.js'; import { foo } from './foo'; -function bar () { - return 'main-bar'; -} - assert.equal( bar(), 'main-bar' ); assert.equal( foo.bar(), 'foo-bar' ); diff --git a/test/function/update-expression-of-import-fails/_config.js b/test/function/update-expression-of-import-fails/_config.js index f2f1390..5f5c27c 100644 --- a/test/function/update-expression-of-import-fails/_config.js +++ b/test/function/update-expression-of-import-fails/_config.js @@ -4,7 +4,7 @@ var assert = require( 'assert' ); module.exports = { description: 'disallows updates to imported bindings', error: function ( err ) { - assert.equal( err.file, path.resolve( __dirname, 'main.js' ) ); + assert.equal( path.normalize(err.file), path.resolve( __dirname, 'main.js' ) ); assert.deepEqual( err.loc, { line: 3, column: 0 }); assert.ok( /Illegal reassignment/.test( err.message ) ); } diff --git a/test/sourcemaps/basic-support/_config.js b/test/sourcemaps/basic-support/_config.js index b83cd6b..c547c32 100644 --- a/test/sourcemaps/basic-support/_config.js +++ b/test/sourcemaps/basic-support/_config.js @@ -18,7 +18,7 @@ module.exports = { assert.equal( originalLoc.line, 4 ); assert.equal( originalLoc.column, 0 ); - assert.equal( path.resolve( originalLoc.source ), path.resolve( __dirname, 'main.js' ) ); + assert.equal( originalLoc.source, 'sourcemaps/basic-support/main.js' ); // foo.js generatedLoc = getLocation( code, code.indexOf( "console.log( 'hello from foo.js' )" ) ); @@ -26,7 +26,7 @@ module.exports = { assert.equal( originalLoc.line, 2 ); assert.equal( originalLoc.column, 1 ); - assert.equal( path.resolve( originalLoc.source ), path.resolve( __dirname, 'foo.js' ) ); + assert.equal( originalLoc.source, 'sourcemaps/basic-support/foo.js' ); // bar.js generatedLoc = getLocation( code, code.indexOf( "console.log( 'hello from bar.js' )" ) ); @@ -34,6 +34,6 @@ module.exports = { assert.equal( originalLoc.line, 2 ); assert.equal( originalLoc.column, 1 ); - assert.equal( path.resolve( originalLoc.source ), path.resolve( __dirname, 'bar.js' ) ); + assert.equal( originalLoc.source, 'sourcemaps/basic-support/bar.js' ); } }; diff --git a/test/sourcemaps/names/_config.js b/test/sourcemaps/names/_config.js index 14f081a..ce0bccb 100644 --- a/test/sourcemaps/names/_config.js +++ b/test/sourcemaps/names/_config.js @@ -1,4 +1,3 @@ -var path = require( 'path' ); var assert = require( 'assert' ); var getLocation = require( '../../utils/getLocation' ); var SourceMapConsumer = require( 'source-map' ).SourceMapConsumer; @@ -9,21 +8,20 @@ module.exports = { moduleName: 'myModule' }, test: function ( code, map ) { - var match = /Object\.create\( ([^\.]+)\.prototype/.exec( code ); + var smc = new SourceMapConsumer( map ); - var deconflictedName = match[1]; - if ( deconflictedName === 'Foo' ) throw new Error( 'Need to update this test!' ); + var pattern = /Object\.create\( ([\w\$\d]+)\.prototype \)/; + var match = pattern.exec( code ); - var smc = new SourceMapConsumer( map ); + var generatedLoc = getLocation( code, match.index + 'Object.create ( '.length ); + var original = smc.originalPositionFor( generatedLoc ); + assert.equal( original.name, 'Bar' ); - var index = code.indexOf( deconflictedName ); - var generatedLoc = getLocation( code, index ); - var originalLoc = smc.originalPositionFor( generatedLoc ); - assert.equal( originalLoc.name, 'Foo' ); + pattern = /function Foo([\w\$\d]+)/; + match = pattern.exec( code ); - index = code.indexOf( deconflictedName, index + 1 ); - generatedLoc = getLocation( code, index ); - originalLoc = smc.originalPositionFor( generatedLoc ); - assert.equal( originalLoc.name, 'Bar' ); + generatedLoc = getLocation( code, match.index + 'function '.length ); + original = smc.originalPositionFor( generatedLoc ); + assert.equal( original.name, 'Foo' ); } }; diff --git a/test/test.js b/test/test.js index 337d33d..520330a 100644 --- a/test/test.js +++ b/test/test.js @@ -2,11 +2,11 @@ require( 'source-map-support' ).install(); require( 'console-group' ).install(); var path = require( 'path' ); +var os = require( 'os' ); var sander = require( 'sander' ); var assert = require( 'assert' ); var exec = require( 'child_process' ).exec; var babel = require( 'babel-core' ); -var sequence = require( './utils/promiseSequence' ); var rollup = require( '../dist/rollup' ); var FUNCTION = path.resolve( __dirname, 'function' ); @@ -32,6 +32,10 @@ function extend ( target ) { return target; } +function normaliseOutput ( code ) { + return code.toString().trim().replace( /\r\n/g, '\n' ); +} + describe( 'rollup', function () { describe( 'sanity checks', function () { it( 'exists', function () { @@ -41,6 +45,36 @@ describe( 'rollup', function () { it( 'has a rollup method', function () { assert.equal( typeof rollup.rollup, 'function' ); }); + + it( 'fails without options or options.entry', function () { + assert.throws( function () { + rollup.rollup(); + }, /must supply options\.entry/ ); + + assert.throws( function () { + rollup.rollup({}); + }, /must supply options\.entry/ ); + }); + }); + + describe( 'bundle.write()', function () { + it( 'fails without options or options.dest', function () { + return rollup.rollup({ + entry: 'x', + resolveId: function () { return 'test'; }, + load: function () { + return '// empty'; + } + }).then( function ( bundle ) { + assert.throws( function () { + bundle.write(); + }, /must supply options\.dest/ ); + + assert.throws( function () { + bundle.write({}); + }, /must supply options\.dest/ ); + }); + }); }); describe( 'function', function () { @@ -76,60 +110,60 @@ describe( 'rollup', function () { format: 'cjs' })); - if ( config.error ) { + if ( config.generateError ) { unintendedError = new Error( 'Expected an error while generating output' ); } } catch ( err ) { - if ( config.error ) { - config.error( err ); + if ( config.generateError ) { + config.generateError( err ); } else { unintendedError = err; } } if ( unintendedError ) throw unintendedError; + if ( config.error || config.generateError ) return; var code; - try { - if ( config.babel ) { - code = babel.transform( result.code, { - blacklist: [ 'es6.modules' ], - loose: [ 'es6.classes' ] - }).code; - } else { - code = result.code; - } + if ( config.babel ) { + code = babel.transform( result.code, { + blacklist: [ 'es6.modules' ], + loose: [ 'es6.classes' ] + }).code; + } else { + code = result.code; + } - var module = { - exports: {} - }; + var module = { + exports: {} + }; - var context = extend({ - require: require, - module: module, - exports: module.exports, - assert: assert - }, config.context || {} ); + var context = extend({ + require: require, + module: module, + exports: module.exports, + assert: assert + }, config.context || {} ); - var contextKeys = Object.keys( context ); - var contextValues = contextKeys.map( function ( key ) { - return context[ key ]; - }); + var contextKeys = Object.keys( context ); + var contextValues = contextKeys.map( function ( key ) { + return context[ key ]; + }); + try { var fn = new Function( contextKeys, code ); fn.apply( {}, contextValues ); - if ( config.error ) { + if ( config.runtimeError ) { unintendedError = new Error( 'Expected an error while executing output' ); - } - - if ( config.exports ) { - config.exports( module.exports ); + } else { + if ( config.exports ) config.exports( module.exports ); + if ( config.bundle ) config.bundle( bundle ); } } catch ( err ) { - if ( config.error ) { - config.error( err ); + if ( config.runtimeError ) { + config.runtimeError( err ); } else { unintendedError = err; } @@ -157,49 +191,45 @@ describe( 'rollup', function () { sander.readdirSync( FORM ).sort().forEach( function ( dir ) { if ( dir[0] === '.' ) return; // .DS_Store... - describe( dir, function () { - var config = require( FORM + '/' + dir + '/_config' ); + var config = require( FORM + '/' + dir + '/_config' ); - var options = extend( {}, config.options, { - entry: FORM + '/' + dir + '/main.js' - }); - - var bundlePromise = rollup.rollup( options ); + var options = extend( {}, config.options, { + entry: FORM + '/' + dir + '/main.js' + }); + ( config.skip ? describe.skip : config.solo ? describe.only : describe)( dir, function () { PROFILES.forEach( function ( profile ) { - ( config.skip ? it.skip : config.solo ? it.only : it )( 'generates ' + profile.format, function () { - if ( config.solo ) console.group( dir ); - - return bundlePromise.then( function ( bundle ) { + it( 'generates ' + profile.format, function () { + return rollup.rollup( options ).then( function ( bundle ) { var options = extend( {}, config.options, { dest: FORM + '/' + dir + '/_actual/' + profile.format + '.js', format: profile.format }); return bundle.write( options ).then( function () { - var actualCode = sander.readFileSync( FORM, dir, '_actual', profile.format + '.js' ).toString().trim(); + var actualCode = normaliseOutput( sander.readFileSync( FORM, dir, '_actual', profile.format + '.js' ) ); var expectedCode; var actualMap; var expectedMap; try { - expectedCode = sander.readFileSync( FORM, dir, '_expected', profile.format + '.js' ).toString().trim(); + expectedCode = normaliseOutput( sander.readFileSync( FORM, dir, '_expected', profile.format + '.js' ) ); } catch ( err ) { expectedCode = 'missing file'; } try { actualMap = JSON.parse( sander.readFileSync( FORM, dir, '_actual', profile.format + '.js.map' ).toString() ); + actualMap.sourcesContent = actualMap.sourcesContent.map( normaliseOutput ); } catch ( err ) {} try { expectedMap = JSON.parse( sander.readFileSync( FORM, dir, '_expected', profile.format + '.js.map' ).toString() ); + expectedMap.sourcesContent = expectedMap.sourcesContent.map( normaliseOutput ); } catch ( err ) {} assert.equal( actualCode, expectedCode ); assert.deepEqual( actualMap, expectedMap ); - - if ( config.solo ) console.groupEnd(); }); }); }); @@ -219,15 +249,13 @@ describe( 'rollup', function () { entry: SOURCEMAPS + '/' + dir + '/main.js' }); - var bundlePromise = rollup.rollup( options ); - PROFILES.forEach( function ( profile ) { ( config.skip ? it.skip : config.solo ? it.only : it )( 'generates ' + profile.format, function () { - return bundlePromise.then( function ( bundle ) { + return rollup.rollup( options ).then( function ( bundle ) { var options = extend( {}, config.options, { format: profile.format, sourceMap: true, - sourceMapFile: 'bundle.js' + sourceMapFile: path.resolve( __dirname, 'bundle.js' ) }); var result = bundle.generate( options ); @@ -249,9 +277,13 @@ describe( 'rollup', function () { ( config.skip ? it.skip : config.solo ? it.only : it )( dir, function ( done ) { process.chdir( path.resolve( CLI, dir ) ); + if (os.platform() === 'win32') { + config.command = "node " + path.resolve( __dirname, '../bin' ) + path.sep + config.command; + } + exec( config.command, { env: { - PATH: path.resolve( __dirname, '../bin' ) + ':' + process.env.PATH + PATH: path.resolve( __dirname, '../bin' ) + path.delimiter + process.env.PATH } }, function ( err, code, stderr ) { if ( err ) return done( err ); @@ -310,7 +342,7 @@ describe( 'rollup', function () { else { var expected = sander.readFileSync( '_expected.js' ).toString(); try { - assert.equal( code.trim(), expected.trim() ); + assert.equal( normaliseOutput( code ), normaliseOutput( expected ) ); done(); } catch ( err ) { done( err );