From 5df5874b2f30b8d1ac4f0509b7e464d24f281836 Mon Sep 17 00:00:00 2001 From: Adam Rackis Date: Fri, 25 Mar 2016 17:10:30 -0500 Subject: [PATCH 1/4] Bundle arithmetic now supports parens. Tests updated. Docs updated. --- .gitignore | 1 + README.md | 8 ++ lib/arithmetic.js | 123 +++++++++++++++++------ test/arithmetic.js | 144 ++++++++++++++++++++++++++- test/fixtures/test-tree/cjs-1.js | 4 + test/fixtures/test-tree/cjs-2.js | 3 + test/fixtures/test-tree/cjs-3.js | 4 + test/fixtures/test-tree/cjs-4.js | 1 + test/fixtures/test-tree/cjs-5.js | 1 + test/fixtures/test-tree/cjs-in-12.js | 1 + test/fixtures/test-tree/cjs-in-13.js | 1 + 11 files changed, 261 insertions(+), 30 deletions(-) create mode 100644 test/fixtures/test-tree/cjs-1.js create mode 100644 test/fixtures/test-tree/cjs-2.js create mode 100644 test/fixtures/test-tree/cjs-3.js create mode 100644 test/fixtures/test-tree/cjs-4.js create mode 100644 test/fixtures/test-tree/cjs-5.js create mode 100644 test/fixtures/test-tree/cjs-in-12.js create mode 100644 test/fixtures/test-tree/cjs-in-13.js diff --git a/.gitignore b/.gitignore index 2a324d4..e4db436 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ test/output test/output.js test/output.js.map dist +.idea/ diff --git a/README.md b/README.md index 646ecb6..cbe75e0 100644 --- a/README.md +++ b/README.md @@ -259,6 +259,14 @@ builder.bundle('app/**/* - [app/**/*]', 'dependencies.js', { minify: true, sourc The above means _take the tree of app and all its dependencies, and subtract just the modules in app_, thus leaving us with just the tree of dependencies of the app package. +#### Example - Multiple Common Bundles + +Parentheses are supported, so the following would bundle everything in common with `page1` and `page2`, and also everything in common between `page3` and `page4`: + +```javascript +builder.bundle('(app/page1.js & app/page2.js) + (app/page3.js & app/page4.js)', 'common.js'); +``` + #### Example - Direct Trace API Instead of using the arithmetic syntax, we can construct the trace ourselves. diff --git a/lib/arithmetic.js b/lib/arithmetic.js index d55d2a3..87f2328 100644 --- a/lib/arithmetic.js +++ b/lib/arithmetic.js @@ -15,35 +15,87 @@ function parseExpression(expressionString) { expressionString = ' + ' + expressionString; var args = expressionString.split(/ [\+\-\&] /); + var index = 0; var operations = []; - var curLen = 0; - for (var i = 0; i < args.length - 1; i++) { - var operator = expressionString[curLen + 1]; - var moduleName = args[i + 1]; + function getNextIdentifier() { + eatWhitespace(); + + if (expressionString.charAt(index) === '(') { + var closingParenIndex = index, + numOpenBeforeSelf = 0; + + while (++closingParenIndex < expressionString.length){ + if (expressionString.charAt(closingParenIndex) === '('){ + numOpenBeforeSelf++; + } else if (expressionString.charAt(closingParenIndex) === ')') { + if (numOpenBeforeSelf){ + numOpenBeforeSelf--; + } else { + break; + } + } + } + var wholeExpression = expressionString.substring(index + 1, closingParenIndex); + index = closingParenIndex + 1; + return { bulkOperation: wholeExpression }; + } - curLen += moduleName.length + 3; + var result = ""; + //scan the identifier + for (; index < expressionString.length; index++) { + if (/\s/.test(expressionString.charAt(index))) { + return result; + } else { + result += expressionString.charAt(index); + } + } + return result; + } - if (operator !== '+' && operator !== '-' && operator !== '&') - throw new TypeError('Expected operator before ' + operator); - if (!moduleName) - throw new TypeError('A module name is needed after ' + operator); + function getNextOperator() { + eatWhitespace(); + return expressionString.charAt(index++); //all operators are single characters at the moment + } - // detect [moduleName] syntax for individual modules not trees - var singleModule = moduleName.substr(0, 1) == '[' && moduleName.substr(moduleName.length - 1, 1) == ']'; - if (singleModule) - moduleName = moduleName.substr(1, moduleName.length - 2); + function eatWhitespace() { + //wind past whitespace + for (; index < expressionString.length; index++) { + if (/\S/.test(expressionString.charAt(index))) { + break; + } + } + } - var canonicalized = moduleName.substr(0, 1) == '`' && moduleName.substr(moduleName.length - 1, 1) == '`'; - if (canonicalized) - moduleName = moduleName.substr(1, moduleName.length - 2); + while (index < expressionString.length) { + var operator = getNextOperator(); - operations.push({ - operator: operator, - moduleName: moduleName, - singleModule: singleModule, - canonicalized: canonicalized - }); + var moduleName = getNextIdentifier(); + + if (typeof moduleName === 'object'){ + operations.push({ + operator: operator, + bulkOperation: moduleName.bulkOperation + }); + } else { + // detect [moduleName] syntax for individual modules not trees + var singleModule = moduleName.substr(0, 1) == '[' && moduleName.substr(moduleName.length - 1, 1) == ']'; + if (singleModule) { + moduleName = moduleName.substr(1, moduleName.length - 2); + } + + var canonicalized = moduleName.substr(0, 1) == '`' && moduleName.substr(moduleName.length - 1, 1) == '`'; + if (canonicalized) { + moduleName = moduleName.substr(1, moduleName.length - 2); + } + + operations.push({ + operator: operator, + moduleName: moduleName, + singleModule: singleModule, + canonicalized: canonicalized + }); + } } return operations; @@ -149,7 +201,7 @@ function expandGlobAndCanonicalize(builder, operation) { }); } -exports.traceExpression = function(builder, expression, traceOpts) { +exports.traceExpression = function traceExpression(builder, expression, traceOpts) { if (!expression) throw new Error('A module expression must be provided to trace.'); @@ -159,12 +211,22 @@ exports.traceExpression = function(builder, expression, traceOpts) { // expand any globbing operations in the expression var expandPromise = Promise.resolve(); operations.forEach(function(operation) { - expandPromise = expandPromise.then(function() { - return Promise.resolve(expandGlobAndCanonicalize(builder, operation)) - .then(function(expanded) { - expandedOperations = expandedOperations.concat(expanded); + if (operation.bulkOperation) { + var expandedTreePromise = traceExpression(builder, operation.bulkOperation, traceOpts) + expandPromise = expandPromise.then(function() { + return Promise.resolve(expandedTreePromise) + .then(function(fullTree){ + expandedOperations = expandedOperations.concat({ preExpandedTree: fullTree, operator: operation.operator }) + }); }); - }); + } else if (operation.operator) { + expandPromise = expandPromise.then(function() { + return Promise.resolve(expandGlobAndCanonicalize(builder, operation)) + .then(function (expanded) { + expandedOperations = expandedOperations.concat(expanded); + }); + }); + } }); return expandPromise.then(function() { @@ -174,7 +236,10 @@ exports.traceExpression = function(builder, expression, traceOpts) { // tree . module if (op.singleModule) return getTreeModuleOperation(builder, op.operator)(curTree, op.moduleName); - + + if (op.preExpandedTree){ + return getTreeOperation(op.operator)(curTree, op.preExpandedTree); + } // tree . tree return builder.tracer.traceCanonical(op.moduleName, traceOpts) .then(function(nextTrace) { diff --git a/test/arithmetic.js b/test/arithmetic.js index ade36d5..6b679a5 100644 --- a/test/arithmetic.js +++ b/test/arithmetic.js @@ -36,7 +36,8 @@ suite('Bundle Expressions', function() { builder.trace('*.js - [amd-*] - [sfx-format-*]') .then(function(tree) { assert.deepEqual(Object.keys(tree).sort(), [ - 'Buffer.js', 'amd.js', 'babel', 'cjs-globals.js', 'cjs-resolve.js', 'cjs.js', 'component.jsx!jsx.js', 'file.json', 'first.js', + 'Buffer.js', 'amd.js', 'babel', 'cjs-1.js', 'cjs-2.js', 'cjs-3.js', 'cjs-4.js', 'cjs-5.js', 'cjs-globals.js', 'cjs-in-12.js', 'cjs-in-13.js', + 'cjs-resolve.js', 'cjs.js', 'component.jsx!jsx.js', 'file.json', 'first.js', 'global-inner.js', 'global-outer.js', 'global.js', 'jquery-cdn', 'jquery.js', 'json-plugin.js', 'jsx.js', 'plugin.js', 'runtime.js', 'second.js', 'some.js!plugin.js', 'text-plugin.js', 'text.txt!text-plugin.js', 'third.js', 'umd.js']); }) @@ -50,4 +51,145 @@ suite('Bundle Expressions', function() { }) .then(done, done); }); + + test('cjs bundles added', function(done){ + builder.trace('cjs-1.js + cjs-2.js + cjs-3.js') + .then(function(tree) { + assert.deepEqual(Object.keys(tree).sort(), [ + 'cjs-1.js', 'cjs-2.js', 'cjs-3.js', 'cjs-in-12.js', 'cjs-in-13.js']); + }) + .then(done, done); + }); + + test('cjs bundles added with parens', function(done){ + builder.trace('(cjs-1.js & cjs-2.js) + (cjs-1.js & cjs-3.js)') + .then(function(tree) { + assert.deepEqual(Object.keys(tree).sort(), ['cjs-in-12.js', 'cjs-in-13.js']); + }) + .then(done, done); + }); + + test('cjs bundles added with parens and extra spaces', function(done){ + builder.trace(' ( cjs-1.js & cjs-2.js ) + ( cjs-1.js & cjs-3.js)') + .then(function(tree) { + assert.deepEqual(Object.keys(tree).sort(), ['cjs-in-12.js', 'cjs-in-13.js']); + }) + .then(done, done); + }); + + test('cjs bundles added with single-value parameters', function(done){ + builder.trace('(cjs-1.js) + (cjs-2.js) + (cjs-3.js)') + .then(function(tree) { + assert.deepEqual(Object.keys(tree).sort(), [ + 'cjs-1.js', 'cjs-2.js', 'cjs-3.js', 'cjs-in-12.js', 'cjs-in-13.js']); + }) + .then(done, done); + }); + + test('cjs bundles added with parens', function(done){ + builder.trace('(cjs-1.js & cjs-2.js)') + .then(function(tree) { + assert.deepEqual(Object.keys(tree).sort(), ['cjs-in-12.js']); + }) + .then(done, done); + }); + + test('cjs bundles added with parens', function(done){ + builder.trace('(cjs-1.js & cjs-2.js) + cjs-in-13.js - cjs-in-13.js') + .then(function(tree) { + assert.deepEqual(Object.keys(tree).sort(), ['cjs-in-12.js']); + }) + .then(done, done); + }); + + test('cjs bundles added with parens', function(done){ + builder.trace('(cjs-1.js & cjs-2.js) + cjs-in-13.js - cjs-in-13.js') + .then(function(tree) { + assert.deepEqual(Object.keys(tree).sort(), ['cjs-in-12.js']); + }) + .then(done, done); + }); + + test('cjs bundles added with parens', function(done){ + builder.trace('cjs-in-13.js + (cjs-1.js & cjs-2.js) - cjs-in-13.js') + .then(function(tree) { + assert.deepEqual(Object.keys(tree).sort(), ['cjs-in-12.js']); + }) + .then(done, done); + }); + + test('cjs bundles added with multiple parens', function(done){ + builder.trace('cjs-in-13.js + (cjs-1.js & cjs-2.js) - (cjs-in-13.js)') + .then(function(tree) { + assert.deepEqual(Object.keys(tree).sort(), ['cjs-in-12.js']); + }) + .then(done, done); + }); + + test('cjs bundles added with multiple parens 2', function(done){ + builder.trace('(cjs-1.js & cjs-2.js) + (cjs-1.js & cjs-3.js) - cjs-in-12.js') + .then(function(tree) { + assert.deepEqual(Object.keys(tree).sort(), ['cjs-in-13.js']); + }) + .then(done, done); + }); + + test('cjs bundles with nested parens 3', function(done){ + builder.trace('(cjs-1.js + cjs-2.js - ([cjs-1.js] + [cjs-2.js])) - (cjs-in-12.js)') + .then(function(tree) { + assert.deepEqual(Object.keys(tree).sort(), ['cjs-in-13.js']); + }) + .then(done, done); + }); + + test('cjs bundles with nested parens 4', function(done){ + builder.trace('(cjs-1.js + cjs-2.js - ([cjs-1.js] + [cjs-2.js])) - (cjs-in-12.js) + (cjs-4.js + cjs-5.js)') + .then(function(tree) { + assert.deepEqual(Object.keys(tree).sort(), ['cjs-4.js', 'cjs-5.js', 'cjs-in-13.js']); + }) + .then(done, done); + }); + + test('cjs bundles with nested parens 5', function(done){ + builder.trace('((cjs-1.js + cjs-2.js - ([cjs-1.js] + [cjs-2.js])) - (cjs-in-12.js) + (cjs-4.js + cjs-5.js)) - ([cjs-4.js] + [cjs-5.js])') + .then(function(tree) { + assert.deepEqual(Object.keys(tree).sort(), ['cjs-in-13.js']); + }) + .then(done, done); + }); + + test('cjs bundles with nested parens 5', function(done){ + builder.trace('((cjs-1.js + cjs-2.js - ([cjs-1.js] + [cjs-2.js] + ([cjs-4.js] + [cjs-5.js]))) - (cjs-4.js + cjs-5.js))') + .then(function(tree) { + assert.deepEqual(Object.keys(tree).sort(), ['cjs-in-12.js', 'cjs-in-13.js']); + }) + .then(done, done); + }); + + + test('ibid with single module subtracted', function(done){ + builder.trace('(cjs-1.js + cjs-2.js - ([cjs-1.js] + [cjs-2.js])) - ([cjs-in-12.js])') + .then(function(tree) { + assert.deepEqual(Object.keys(tree).sort(), ['cjs-in-13.js']); + }) + .then(done, done); + }); + + test('cjs bundles added with nested parens', function(done){ + builder.trace('(cjs-1.js + cjs-2.js - (cjs-1.js & cjs-2.js))') + .then(function(tree) { + assert.deepEqual(Object.keys(tree).sort(), ['cjs-1.js', 'cjs-2.js', 'cjs-in-13.js']); + }) + .then(done, done); + }); + + test('cjs bundles with parens and single modules', function(done){ + builder.trace('(cjs-1.js + cjs-2.js) - ([cjs-1.js] + [cjs-2.js])') + .then(function(tree) { + assert.deepEqual(Object.keys(tree).sort(), ['cjs-in-12.js', 'cjs-in-13.js']); + }) + .then(done, done); + }); + + }); \ No newline at end of file diff --git a/test/fixtures/test-tree/cjs-1.js b/test/fixtures/test-tree/cjs-1.js new file mode 100644 index 0000000..5ac73c8 --- /dev/null +++ b/test/fixtures/test-tree/cjs-1.js @@ -0,0 +1,4 @@ +var shared1 = require('./cjs-in-12.js'); +var shared2 = require('./cjs-in-13.js'); + +module.exports = { name: 'cjs1' }; diff --git a/test/fixtures/test-tree/cjs-2.js b/test/fixtures/test-tree/cjs-2.js new file mode 100644 index 0000000..7e6f547 --- /dev/null +++ b/test/fixtures/test-tree/cjs-2.js @@ -0,0 +1,3 @@ +var shared1 = require('./cjs-in-12.js'); + +module.exports = { name: 'cjs2' }; diff --git a/test/fixtures/test-tree/cjs-3.js b/test/fixtures/test-tree/cjs-3.js new file mode 100644 index 0000000..10b5d9d --- /dev/null +++ b/test/fixtures/test-tree/cjs-3.js @@ -0,0 +1,4 @@ +var shared1 = require('./cjs-in-13.js'); + +module.exports = { name: 'cjs3' }; + diff --git a/test/fixtures/test-tree/cjs-4.js b/test/fixtures/test-tree/cjs-4.js new file mode 100644 index 0000000..1d0bef1 --- /dev/null +++ b/test/fixtures/test-tree/cjs-4.js @@ -0,0 +1 @@ +module.exports = { name: 'cjs4' }; diff --git a/test/fixtures/test-tree/cjs-5.js b/test/fixtures/test-tree/cjs-5.js new file mode 100644 index 0000000..1f27aef --- /dev/null +++ b/test/fixtures/test-tree/cjs-5.js @@ -0,0 +1 @@ +module.exports = { name: 'cjs5' }; diff --git a/test/fixtures/test-tree/cjs-in-12.js b/test/fixtures/test-tree/cjs-in-12.js new file mode 100644 index 0000000..2f81c6d --- /dev/null +++ b/test/fixtures/test-tree/cjs-in-12.js @@ -0,0 +1 @@ +module.exports = { name: 'cjs-in-12' }; diff --git a/test/fixtures/test-tree/cjs-in-13.js b/test/fixtures/test-tree/cjs-in-13.js new file mode 100644 index 0000000..a81e3ba --- /dev/null +++ b/test/fixtures/test-tree/cjs-in-13.js @@ -0,0 +1 @@ +module.exports = { name: 'cjs-in-13' }; From 909e6a41b709a3b9fe499a7cc5aa4edd42c720ef Mon Sep 17 00:00:00 2001 From: Adam Rackis Date: Sun, 27 Mar 2016 17:06:49 -0500 Subject: [PATCH 2/4] Validation for bundle arithmetic --- lib/arithmetic.js | 45 +++++++++++++++++++++++++------------ test/arithmetic.js | 55 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 15 deletions(-) diff --git a/lib/arithmetic.js b/lib/arithmetic.js index 87f2328..1f956a8 100644 --- a/lib/arithmetic.js +++ b/lib/arithmetic.js @@ -13,15 +13,21 @@ var verifyTree = require('./utils').verifyTree; function parseExpression(expressionString) { expressionString = ' + ' + expressionString; - var args = expressionString.split(/ [\+\-\&] /); var index = 0; var operations = []; + var operatorRegex = /[\+\-\&]/; + var errorMessagesFromIndex = 3; function getNextIdentifier() { eatWhitespace(); + var firstChar = expressionString.charAt(index); - if (expressionString.charAt(index) === '(') { + if (operatorRegex.test(firstChar)){ + throw 'Syntax Error: Identifier or sub expression expected after <' + expressionString.slice(errorMessagesFromIndex).substr(0, index - errorMessagesFromIndex) + '> but found <' + firstChar + '> instead'; + } + + if (firstChar === '(') { var closingParenIndex = index, numOpenBeforeSelf = 0; @@ -36,6 +42,10 @@ function parseExpression(expressionString) { } } } + if (expressionString.charAt(closingParenIndex) !== ')'){ + throw 'Syntax Error: Expression <' + expressionString.substr(index) + '> is never terminated. Did you forget to add a closing ")"?'; + } + var wholeExpression = expressionString.substring(index + 1, closingParenIndex); index = closingParenIndex + 1; return { bulkOperation: wholeExpression }; @@ -55,7 +65,15 @@ function parseExpression(expressionString) { function getNextOperator() { eatWhitespace(); - return expressionString.charAt(index++); //all operators are single characters at the moment + if (index === expressionString.length) return null; + + var candidateResult = expressionString.charAt(index++); //all operators are single characters at the moment + + if (!operatorRegex.test(candidateResult)){ + throw 'Syntax Error: An operator was expected after <' + expressionString.slice(errorMessagesFromIndex).substr(0, index - 1 - errorMessagesFromIndex) + '> but found <' + expressionString.substring(index - 1) + '> instead'; + } + + return candidateResult; } function eatWhitespace() { @@ -67,31 +85,30 @@ function parseExpression(expressionString) { } } - while (index < expressionString.length) { - var operator = getNextOperator(); - - var moduleName = getNextIdentifier(); + var operator; + while (index < expressionString.length && (operator = getNextOperator())) { + var moduleNameOrSubExpression = getNextIdentifier(); - if (typeof moduleName === 'object'){ + if (typeof moduleNameOrSubExpression === 'object'){ operations.push({ operator: operator, - bulkOperation: moduleName.bulkOperation + bulkOperation: moduleNameOrSubExpression.bulkOperation }); } else { // detect [moduleName] syntax for individual modules not trees - var singleModule = moduleName.substr(0, 1) == '[' && moduleName.substr(moduleName.length - 1, 1) == ']'; + var singleModule = moduleNameOrSubExpression.substr(0, 1) == '[' && moduleNameOrSubExpression.substr(moduleNameOrSubExpression.length - 1, 1) == ']'; if (singleModule) { - moduleName = moduleName.substr(1, moduleName.length - 2); + moduleNameOrSubExpression = moduleNameOrSubExpression.substr(1, moduleNameOrSubExpression.length - 2); } - var canonicalized = moduleName.substr(0, 1) == '`' && moduleName.substr(moduleName.length - 1, 1) == '`'; + var canonicalized = moduleNameOrSubExpression.substr(0, 1) == '`' && moduleNameOrSubExpression.substr(moduleNameOrSubExpression.length - 1, 1) == '`'; if (canonicalized) { - moduleName = moduleName.substr(1, moduleName.length - 2); + moduleNameOrSubExpression = moduleNameOrSubExpression.substr(1, moduleNameOrSubExpression.length - 2); } operations.push({ operator: operator, - moduleName: moduleName, + moduleName: moduleNameOrSubExpression, singleModule: singleModule, canonicalized: canonicalized }); diff --git a/test/arithmetic.js b/test/arithmetic.js index 6b679a5..ebfaabc 100644 --- a/test/arithmetic.js +++ b/test/arithmetic.js @@ -190,6 +190,59 @@ suite('Bundle Expressions', function() { }) .then(done, done); }); +}); + +suite('Bundle Expression Validation', function() { + test('missing identifier 1', function(){ + return validateInvalidExpression('cjs-1.js + +'); + }); + + test('missing identifier 2', function(){ + return validateInvalidExpression('cjs-1.js + -'); + }); + + test('missing identifier 3', function(){ + return validateInvalidExpression('cjs-1.js + &'); + }); + + test('unclosed parens 1', function(){ + return validateInvalidExpression('(cjs-1.js + cjs-2.js'); + }); + + test('unclosed parens 2', function(){ + return validateInvalidExpression('(cjs-1.js + (cjs-2.js + cjs-3.js'); + }); + + test('unclosed parens 3', function(){ + return validateInvalidExpression('(cjs-1.js + (cjs-2.js + cjs-3.js)'); + }); + + test('unclosed parens 4', function(){ + return validateInvalidExpression('(cjs-2.js + cjs-3.js) + (cjs-1.js + (cjs-2.js + cjs-3.js)'); + }); + + test('missing operator 1', function(){ + return validateInvalidExpression('cjs-1.js + cjs-2.js cjs-3.js'); + }); + + function validateInvalidExpression(expression){ + return Promise + .resolve() + .then(function(){ return builder.trace(expression) }) + .then( + function(){ return Promise.reject('Invalid expression <' + expression + '> was parsed without error'); }, //it worked but shouldn't have + function(err){ + //uncomment this line to view the Syntax Errors' wordings in the test console + //console.log(err); + if (typeof err !== 'string' || !/^Syntax Error/i.test(err)){ + return Promise.reject('Syntax error was expected, but not generated') + } else { + return Promise.resolve(1); + } + } + ) + } + +}); -}); \ No newline at end of file From 30bd17f9da3f0b836542ac2ddfcb8e310ed04fca Mon Sep 17 00:00:00 2001 From: Adam Rackis Date: Mon, 28 Mar 2016 18:32:35 -0500 Subject: [PATCH 3/4] parsing and canonicalizing the expression prior to evaluating the trees. --- lib/arithmetic.js | 72 +++++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/lib/arithmetic.js b/lib/arithmetic.js index 1f956a8..148ee0e 100644 --- a/lib/arithmetic.js +++ b/lib/arithmetic.js @@ -218,55 +218,61 @@ function expandGlobAndCanonicalize(builder, operation) { }); } -exports.traceExpression = function traceExpression(builder, expression, traceOpts) { - if (!expression) - throw new Error('A module expression must be provided to trace.'); + exports.traceExpression = function(builder, expression, traceOpts) { + if (!expression) + throw new Error('A module expression must be provided to trace.'); + + return Promise + .resolve(expandAndCanonicalizeExpression(builder, expression)) + .then(function processExpandedOperations(expandedOperations) { + // chain the operations, applying them with the trace of the next module + return expandedOperations.reduce(function(p, op) { + return p.then(function(curTree) { + // tree . module + if (op.singleModule) + return getTreeModuleOperation(builder, op.operator)(curTree, op.moduleName); + + if (op.operationsTree){ + return processExpandedOperations(op.operationsTree).then(function(expandedTree){ + return getTreeOperation(op.operator)(curTree, expandedTree); + }); + } + // tree . tree + return builder.tracer.traceCanonical(op.moduleName, traceOpts) + .then(function(nextTrace) { + return getTreeOperation(op.operator)(curTree, nextTrace.tree); + }); + }); + }, Promise.resolve({})); + }); + }; +function expandAndCanonicalizeExpression(builder, expression){ var operations = parseExpression(expression); + var expandPromise = Promise.resolve(1); var expandedOperations = []; - // expand any globbing operations in the expression - var expandPromise = Promise.resolve(); - operations.forEach(function(operation) { + operations.forEach(function(operation){ if (operation.bulkOperation) { - var expandedTreePromise = traceExpression(builder, operation.bulkOperation, traceOpts) + var expandedTreePromise = expandAndCanonicalizeExpression(builder, operation.bulkOperation); expandPromise = expandPromise.then(function() { return Promise.resolve(expandedTreePromise) - .then(function(fullTree){ - expandedOperations = expandedOperations.concat({ preExpandedTree: fullTree, operator: operation.operator }) - }); + .then(function(expressionsOperations){ + expandedOperations = expandedOperations.concat({ operator: operation.operator, operationsTree: expressionsOperations }) + }); }); - } else if (operation.operator) { + } else { expandPromise = expandPromise.then(function() { return Promise.resolve(expandGlobAndCanonicalize(builder, operation)) .then(function (expanded) { expandedOperations = expandedOperations.concat(expanded); }); - }); + }) } }); - return expandPromise.then(function() { - // chain the operations, applying them with the trace of the next module - return expandedOperations.reduce(function(p, op) { - return p.then(function(curTree) { - // tree . module - if (op.singleModule) - return getTreeModuleOperation(builder, op.operator)(curTree, op.moduleName); - - if (op.preExpandedTree){ - return getTreeOperation(op.operator)(curTree, op.preExpandedTree); - } - // tree . tree - return builder.tracer.traceCanonical(op.moduleName, traceOpts) - .then(function(nextTrace) { - return getTreeOperation(op.operator)(curTree, nextTrace.tree); - }); - }); - }, Promise.resolve({})); - }); -}; - + return Promise.resolve(expandPromise).then(function(){ return expandedOperations; }); +} // returns a new tree containing tree1 n tree2 exports.intersectTrees = intersectTrees; From 48bc668d07ac548d390de7a51a36010e4d72d419 Mon Sep 17 00:00:00 2001 From: Adam Rackis Date: Mon, 28 Mar 2016 23:56:22 -0500 Subject: [PATCH 4/4] support for files with spaces in them --- lib/arithmetic.js | 14 +++--- test/arithmetic.js | 73 ++++++++++++++++++++-------- test/fixtures/test-tree/cjs space.js | 2 + 3 files changed, 63 insertions(+), 26 deletions(-) create mode 100644 test/fixtures/test-tree/cjs space.js diff --git a/lib/arithmetic.js b/lib/arithmetic.js index 148ee0e..5d08378 100644 --- a/lib/arithmetic.js +++ b/lib/arithmetic.js @@ -54,13 +54,15 @@ function parseExpression(expressionString) { var result = ""; //scan the identifier for (; index < expressionString.length; index++) { - if (/\s/.test(expressionString.charAt(index))) { + var currentChar = expressionString.charAt(index); + //can have spaces in file names - so we need whitespace, operator, whitespace. + if (/^\s+[\+\-\&]\s+/.test(expressionString.substr(index))) { return result; } else { - result += expressionString.charAt(index); + result += currentChar; } } - return result; + return result.replace(/\s+$/, ''); //it appears as though trailing whitespace is trimmed downstream, but I'm snipping here to be safe } function getNextOperator() { @@ -258,7 +260,7 @@ function expandAndCanonicalizeExpression(builder, expression){ expandPromise = expandPromise.then(function() { return Promise.resolve(expandedTreePromise) .then(function(expressionsOperations){ - expandedOperations = expandedOperations.concat({ operator: operation.operator, operationsTree: expressionsOperations }) + expandedOperations = expandedOperations.concat({ operator: operation.operator, operationsTree: expressionsOperations }); }); }); } else { @@ -298,7 +300,7 @@ function intersectTrees(tree1, tree2) { } return intersectTree; -}; +} // returns a new tree containing tree1 + tree2 exports.addTrees = addTrees; @@ -337,7 +339,7 @@ function subtractTrees(tree1, tree2) { } return subtractTree; -}; +} // pre-order tree traversal with a visitor and stop condition exports.traverseTree = traverseTree; diff --git a/test/arithmetic.js b/test/arithmetic.js index ebfaabc..97fa960 100644 --- a/test/arithmetic.js +++ b/test/arithmetic.js @@ -36,7 +36,7 @@ suite('Bundle Expressions', function() { builder.trace('*.js - [amd-*] - [sfx-format-*]') .then(function(tree) { assert.deepEqual(Object.keys(tree).sort(), [ - 'Buffer.js', 'amd.js', 'babel', 'cjs-1.js', 'cjs-2.js', 'cjs-3.js', 'cjs-4.js', 'cjs-5.js', 'cjs-globals.js', 'cjs-in-12.js', 'cjs-in-13.js', + 'Buffer.js', 'amd.js', 'babel', 'cjs space.js', 'cjs-1.js', 'cjs-2.js', 'cjs-3.js', 'cjs-4.js', 'cjs-5.js', 'cjs-globals.js', 'cjs-in-12.js', 'cjs-in-13.js', 'cjs-resolve.js', 'cjs.js', 'component.jsx!jsx.js', 'file.json', 'first.js', 'global-inner.js', 'global-outer.js', 'global.js', 'jquery-cdn', 'jquery.js', 'json-plugin.js', 'jsx.js', 'plugin.js', 'runtime.js', 'second.js', 'some.js!plugin.js', 'text-plugin.js', 'text.txt!text-plugin.js', 'third.js', 'umd.js']); @@ -61,14 +61,6 @@ suite('Bundle Expressions', function() { .then(done, done); }); - test('cjs bundles added with parens', function(done){ - builder.trace('(cjs-1.js & cjs-2.js) + (cjs-1.js & cjs-3.js)') - .then(function(tree) { - assert.deepEqual(Object.keys(tree).sort(), ['cjs-in-12.js', 'cjs-in-13.js']); - }) - .then(done, done); - }); - test('cjs bundles added with parens and extra spaces', function(done){ builder.trace(' ( cjs-1.js & cjs-2.js ) + ( cjs-1.js & cjs-3.js)') .then(function(tree) { @@ -87,6 +79,14 @@ suite('Bundle Expressions', function() { }); test('cjs bundles added with parens', function(done){ + builder.trace('(cjs-1.js & cjs-2.js) + (cjs-1.js & cjs-3.js)') + .then(function(tree) { + assert.deepEqual(Object.keys(tree).sort(), ['cjs-in-12.js', 'cjs-in-13.js']); + }) + .then(done, done); + }); + + test('cjs bundles added with parens 2', function(done){ builder.trace('(cjs-1.js & cjs-2.js)') .then(function(tree) { assert.deepEqual(Object.keys(tree).sort(), ['cjs-in-12.js']); @@ -94,7 +94,7 @@ suite('Bundle Expressions', function() { .then(done, done); }); - test('cjs bundles added with parens', function(done){ + test('cjs bundles added with parens 3', function(done){ builder.trace('(cjs-1.js & cjs-2.js) + cjs-in-13.js - cjs-in-13.js') .then(function(tree) { assert.deepEqual(Object.keys(tree).sort(), ['cjs-in-12.js']); @@ -102,7 +102,7 @@ suite('Bundle Expressions', function() { .then(done, done); }); - test('cjs bundles added with parens', function(done){ + test('cjs bundles added with parens 4', function(done){ builder.trace('(cjs-1.js & cjs-2.js) + cjs-in-13.js - cjs-in-13.js') .then(function(tree) { assert.deepEqual(Object.keys(tree).sort(), ['cjs-in-12.js']); @@ -110,7 +110,7 @@ suite('Bundle Expressions', function() { .then(done, done); }); - test('cjs bundles added with parens', function(done){ + test('cjs bundles added with parens 5', function(done){ builder.trace('cjs-in-13.js + (cjs-1.js & cjs-2.js) - cjs-in-13.js') .then(function(tree) { assert.deepEqual(Object.keys(tree).sort(), ['cjs-in-12.js']); @@ -134,7 +134,7 @@ suite('Bundle Expressions', function() { .then(done, done); }); - test('cjs bundles with nested parens 3', function(done){ + test('cjs bundles with nested parens', function(done){ builder.trace('(cjs-1.js + cjs-2.js - ([cjs-1.js] + [cjs-2.js])) - (cjs-in-12.js)') .then(function(tree) { assert.deepEqual(Object.keys(tree).sort(), ['cjs-in-13.js']); @@ -142,7 +142,7 @@ suite('Bundle Expressions', function() { .then(done, done); }); - test('cjs bundles with nested parens 4', function(done){ + test('cjs bundles with nested parens 2', function(done){ builder.trace('(cjs-1.js + cjs-2.js - ([cjs-1.js] + [cjs-2.js])) - (cjs-in-12.js) + (cjs-4.js + cjs-5.js)') .then(function(tree) { assert.deepEqual(Object.keys(tree).sort(), ['cjs-4.js', 'cjs-5.js', 'cjs-in-13.js']); @@ -150,7 +150,7 @@ suite('Bundle Expressions', function() { .then(done, done); }); - test('cjs bundles with nested parens 5', function(done){ + test('cjs bundles with nested parens 3', function(done){ builder.trace('((cjs-1.js + cjs-2.js - ([cjs-1.js] + [cjs-2.js])) - (cjs-in-12.js) + (cjs-4.js + cjs-5.js)) - ([cjs-4.js] + [cjs-5.js])') .then(function(tree) { assert.deepEqual(Object.keys(tree).sort(), ['cjs-in-13.js']); @@ -158,7 +158,7 @@ suite('Bundle Expressions', function() { .then(done, done); }); - test('cjs bundles with nested parens 5', function(done){ + test('cjs bundles with nested parens 4', function(done){ builder.trace('((cjs-1.js + cjs-2.js - ([cjs-1.js] + [cjs-2.js] + ([cjs-4.js] + [cjs-5.js]))) - (cjs-4.js + cjs-5.js))') .then(function(tree) { assert.deepEqual(Object.keys(tree).sort(), ['cjs-in-12.js', 'cjs-in-13.js']); @@ -190,6 +190,43 @@ suite('Bundle Expressions', function() { }) .then(done, done); }); + + test('file with space', function(done){ + builder.trace('cjs-1.js + cjs space.js') + .then(function(tree) { + assert.deepEqual(Object.keys(tree).sort(), [ + 'cjs space.js', 'cjs-1.js', 'cjs-in-12.js', 'cjs-in-13.js']); + }) + .then(done, done); + }); + + test('file with space 2', function(done){ + builder.trace('cjs-1.js + cjs space.js ') + .then(function(tree) { + assert.deepEqual(Object.keys(tree).sort(), [ + 'cjs space.js', 'cjs-1.js', 'cjs-in-12.js', 'cjs-in-13.js']); + }) + .then(done, done); + }); + + test('file with space 3', function(done){ + builder.trace('cjs-1.js + cjs space.js + cjs-2.js') + .then(function(tree) { + assert.deepEqual(Object.keys(tree).sort(), [ + 'cjs space.js', 'cjs-1.js', 'cjs-2.js', 'cjs-in-12.js', 'cjs-in-13.js']); + }) + .then(done, done); + }); + + test('file with space 4', function(done){ + builder.trace('cjs-1.js + cjs space.js + cjs-2.js ') + .then(function(tree) { + assert.deepEqual(Object.keys(tree).sort(), [ + 'cjs space.js', 'cjs-1.js', 'cjs-2.js', 'cjs-in-12.js', 'cjs-in-13.js']); + }) + .then(done, done); + }); + }); suite('Bundle Expression Validation', function() { @@ -221,10 +258,6 @@ suite('Bundle Expression Validation', function() { return validateInvalidExpression('(cjs-2.js + cjs-3.js) + (cjs-1.js + (cjs-2.js + cjs-3.js)'); }); - test('missing operator 1', function(){ - return validateInvalidExpression('cjs-1.js + cjs-2.js cjs-3.js'); - }); - function validateInvalidExpression(expression){ return Promise .resolve() diff --git a/test/fixtures/test-tree/cjs space.js b/test/fixtures/test-tree/cjs space.js new file mode 100644 index 0000000..66db397 --- /dev/null +++ b/test/fixtures/test-tree/cjs space.js @@ -0,0 +1,2 @@ +module.exports = { name: 'cjs space' }; +