diff --git a/.jshintrc b/.jshintrc index 6cec05b..c04e85c 100644 --- a/.jshintrc +++ b/.jshintrc @@ -14,9 +14,9 @@ "noempty": false, // warns when you have an empty block in your code "nonew": true, // prohibits the use of constructor functions for side-effects "plusplus": false, // prohibits the use of unary increment and decrement operators - "quotmark": true, // enforces the consistency of quotation marks used throughout your code (true | single | double) + "quotmark": false, // enforces the consistency of quotation marks used throughout your code (true | single | double) "undef": true, // prohibits the use of explicitly undeclared variables - "unused": true, // warns when you define and never use your variables + "unused": true, // warns when you define and never use your variables (true | false | strict) "strict": false, // requires all functions to run in ECMAScript 5's strict mode (prohibits the global scoped strict mode) "trailing": true, // makes it an error to leave a trailing whitespace in your code "maxparams": 5, // set the max number of formal parameters allowed per function @@ -27,6 +27,7 @@ // Suppress "laxcomma": true, + "multistr": true, // Environment "node": true diff --git a/Gruntfile.js b/Gruntfile.js index 2e6f8f5..9e9025d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -10,7 +10,7 @@ module.exports = function (grunt) { - require('time-grunt')(grunt); + require ('time-grunt') (grunt); // Project configuration. grunt.initConfig ({ @@ -18,7 +18,7 @@ module.exports = function (grunt) jshint: { all: [ 'Gruntfile.js', - 'tasks/*.js' + 'tasks/**/*.js' ], options: { jshintrc: '.jshintrc' @@ -31,21 +31,33 @@ module.exports = function (grunt) }, 'angular-build-tool': { - options: { - main: 'App' - }, - 'test-js-only': { + 'test-js-app': { + options: { + main: 'App', + externalModules: 'Library1' + }, src: 'tests/js-only/**/*.js', targetScript: 'dist/main.js' }, - 'test-js-fail': { - options: { - main: 'App2' + 'test-js-lib': { + options: { + main: 'Library1', + renameModuleRefs: true }, src: 'tests/js-only/**/*.js', - targetScript: 'dist/main.js' + targetScript: 'dist/library1.js' + }, + 'test-js-fail': { + options: { + main: 'Library2' + }, + src: 'tests/js-only/**/*.js', + targetScript: 'dist/library2.js' }, 'test-stylesheets': { + options: { + main: 'App' + }, src: 'tests/stylesheets/**/*.js', targetScript: 'dist/main.js', targetCSS: 'dist/main.css' @@ -67,7 +79,8 @@ module.exports = function (grunt) // Test tasks below can also be executed with the command line option `--build debug` to generate debug builds. - grunt.registerTask ('test-js-only', ['clean', 'angular-build-tool:test-js-only']); + grunt.registerTask ('test-js-app', ['clean', 'angular-build-tool:test-js-app']); + grunt.registerTask ('test-js-lib', ['clean', 'angular-build-tool:test-js-lib']); grunt.registerTask ('test-js-fail', ['clean', 'angular-build-tool:test-js-fail']); grunt.registerTask ('test-stylesheets', ['clean', 'angular-build-tool:test-stylesheets']); diff --git a/tasks/angular-build-tool.js b/tasks/angular-build-tool.js index dfb0274..204517f 100644 --- a/tasks/angular-build-tool.js +++ b/tasks/angular-build-tool.js @@ -25,7 +25,7 @@ var getProperties = util.getProperties , indent = util.indent , sprintf = util.sprintf , csprintf = util.csprintf - //, debug = util.debug +//, debug = util.debug , ModuleDef = types.ModuleDef , fatal = gruntUtil.fatal , warn = gruntUtil.warn @@ -163,7 +163,8 @@ module.exports = function (grunt) { /** @type {Object.} */ var modules = {}; - (options.externalModules || []).forEach (function (moduleName) + ((typeof options.externalModules === 'string' ? [options.externalModules] : options.externalModules) || []). + forEach (function (moduleName) { /** @type {ModuleDef} */ var module = modules[moduleName] = new ModuleDef (); @@ -194,7 +195,7 @@ module.exports = function (grunt) // Ignore irrelevant files. if (!moduleHeader) { if (!forceInclude || !grunt.file.isMatch ({matchBase: true}, forceInclude, path)) { - info ('Ignored file:'.cyan, path); + info ('Ignored file: %', path.cyan); return; } standaloneScripts.push ({ @@ -202,35 +203,60 @@ module.exports = function (grunt) content: script }); } - else { - // Get information about the specified module. - var module = modules[moduleHeader.name]; - // If this is the first time a specific module is mentioned, create the respective information record. - if (!module) - module = modules[moduleHeader.name] = new ModuleDef (); - // Skip the file if it defines an external module. - else if (module.external) - return; - // Reject additional attempts to redeclare a module (only appending is allowed). - else if (!moduleHeader.append) - fatal ('Can\'t redeclare module %', moduleHeader.name); - // Fill out the module definition record. - module.name = moduleHeader.name; - // The file is appending definitions to a module declared elsewhere. - if (moduleHeader.append) { - module.bodies.push (script); - // Append the file path to the bottom of the paths list. - module.filePaths.push (path); - } - // Otherwise, the file contains a module declaration. - else { - if (module.head) - fatal ('Duplicate module definition: %', moduleHeader.name); - module.head = script; - // Add the file path to the top of the paths list. - module.filePaths.unshift (path); - module.requires = moduleHeader.requires; - } + else setupModuleInfo (moduleHeader, script, path); + } + + /** + * Store information about the specified module retrieved from the given source code on the specified file. + * @param {ModuleHeaderInfo} moduleHeader + * @param {string} fileContent + * @param {string} filePath + */ + function setupModuleInfo (moduleHeader, fileContent, filePath) + { + var STAT = sourceExtract.EXTRACT_STAT; + switch (moduleHeader.status) { + + case STAT.OK: + + // Get information about the specified module. + var module = modules[moduleHeader.name]; + // If this is the first time a specific module is mentioned, create the respective information record. + if (!module) + module = modules[moduleHeader.name] = new ModuleDef (); + // Skip the file if it defines an external module. + else if (module.external) + return; + // Reject additional attempts to redeclare a module (only appending is allowed). + else if (!moduleHeader.append) + fatal ('Can\'t redeclare module %', moduleHeader.name); + // Fill out the module definition record. + module.name = moduleHeader.name; + // The file is appending definitions to a module declared elsewhere. + if (moduleHeader.append) { + module.bodies.push (fileContent); + // Append the file path to the bottom of the paths list. + module.filePaths.push (filePath); + } + // Otherwise, the file contains a module declaration. + else { + if (module.head) + fatal ('Duplicate module definition: %', moduleHeader.name); + module.head = fileContent; + // Add the file path to the top of the paths list. + module.filePaths.unshift (filePath); + module.requires = moduleHeader.requires; + } + break; + + case STAT.MULTIPLE_MODULES: + + fatal ('Definitions for multiple modules were found on the same file.' + NL + reportErrorLocation(filePath)); + break; + + case STAT.MULTIPLE_DECLS: + + fatal ('More than one module declaration was found on the same file.' + NL + reportErrorLocation(filePath)); } } @@ -245,9 +271,8 @@ module.exports = function (grunt) * @param {Array.} output * @param {function(ModuleDef, Array.)} processHook */ - function includeModule (moduleName, output, processHook) + function traceModule (moduleName, output, processHook) { - info ('Including module %.', moduleName); var module = modules[moduleName]; if (!module) fatal ('Module % was not found.', moduleName); @@ -258,10 +283,12 @@ module.exports = function (grunt) if (module.requires) { module.requires.forEach (function (modName) { - includeModule (modName, output, processHook); + traceModule (modName, output, processHook); }); } + // Ignore references to already loaded modules. if (!loaded[module.name]) { + info ('Including module %.', moduleName); loaded[module.name] = true; processHook (module, output); } @@ -305,6 +332,7 @@ module.exports = function (grunt) function buildDebugPackage (mainName, targetScript, targetStylesheet) { var output = ['document.write (\'']; + targetStylesheet = targetStylesheet; // momentarily disable jsHint warning // Output the standalone scripts (if any). if (standaloneScripts.length) @@ -314,7 +342,7 @@ module.exports = function (grunt) }).join ('\\\n')); // Output the modules (if any). - includeModule (mainName, output, buildDebugScriptForModule); + traceModule (mainName, output, includeModuleInDebugBuild); output.push ('\');'); writeFile (targetScript, output.join ('\\\n')); } @@ -324,7 +352,7 @@ module.exports = function (grunt) * @param {ModuleDef} module * @param {Array.} output */ - function buildDebugScriptForModule (module, output) + function includeModuleInDebugBuild (module, output) { module.filePaths.forEach (function (path) { @@ -346,13 +374,14 @@ module.exports = function (grunt) function buildReleasePackage (mainName, targetScript, targetStylesheet) { var output = []; + targetStylesheet = targetStylesheet; // momentarily disable jsHint warning // Output the standalone scripts (if any). if (standaloneScripts.length) output.push (standaloneScripts.map (function (e) {return e.content;}).join ('\n')); // Output the modules (if any). - includeModule (mainName, output, buildReleaseScriptForModule); + traceModule (mainName, output, includeModuleInReleaseBuild); writeFile (targetScript, output.join ('\n')); } @@ -361,7 +390,7 @@ module.exports = function (grunt) * @param {ModuleDef} module * @param {Array.} output */ - function buildReleaseScriptForModule (module, output) + function includeModuleInReleaseBuild (module, output) { // Fist process the head module declaration. var head = optimize (module.head, module.filePaths[0], module); @@ -373,7 +402,9 @@ module.exports = function (grunt) if (head.data.trim ()) output.push (head.data); // Output a module declaration with no definitions. - output.push (sprintf ('angular.module ('%', %);%', module.name, toList (module.requires), options.moduleFooter)); + output.push (sprintf ('angular.module (\'%\', %);%', module.name, + toList (module.requires), options.moduleFooter) + ); } // Enclose the module contents in a self-invoking function which receives the module instance as an argument. else { @@ -412,7 +443,10 @@ module.exports = function (grunt) //---------------------------------------------------------- // Module already enclosed in a closure with no arguments. //---------------------------------------------------------- - return {status: STAT.INDENTED, data: sourceTrans.renameModuleRefExps (module, result.data, options.moduleVar)}; + return /** @type {OperationResult} */ { + status: STAT.INDENTED, + data: sourceTrans.renameModuleRefExps (module, options.indent + result.data, options.moduleVar) + }; case stat.NO_CLOSURE_FOUND: @@ -432,7 +466,10 @@ module.exports = function (grunt) // If --force, continue. } // Either the code is valid or --force was used, so process it. - return {status: STAT.OK, data: sourceTrans.renameModuleRefExps (module, source, options.moduleVar)}; + return /** @type {OperationResult} */ { + status: STAT.OK, + data: sourceTrans.renameModuleRefExps (module, source, options.moduleVar) + }; case stat.RENAME_REQUIRED: @@ -441,15 +478,20 @@ module.exports = function (grunt) // Module already enclosed in a closure, with its reference // passed in as the function's argument. //---------------------------------------------------------- - if (options.renameModuleRefs) - return {status: STAT.OK, data: sourceTrans.renameModuleVariableRefs (source, result.data, options.moduleVar)}; - else - warn ('The module variable reference % doesn\'t match the preset name on the config. setting moduleVar=\'%\'.%%%', - result.data, options.moduleVar, NL, reportErrorLocation (path), + /** @type {ModuleClosureInfo} */ + var modInfo = result.data; + if (!options.renameModuleRefs) { + warn ('The module variable reference % doesn\'t match the preset name on the config setting ' + + 'moduleVar=\'%\'.%%%', + modInfo.moduleVar, options.moduleVar, NL, reportErrorLocation (path), getExplanation ('Either rename the variable or enable renameModuleRefs.') ); - // If --force, continue. - break; + // If --force, continue. + } + return /** @type {OperationResult} */ { + status: STAT.OK, + data: sourceTrans.renameModuleVariableRefs (modInfo.closureBody, modInfo.moduleVar, options.moduleVar) + }; case stat.INVALID_DECLARATION: @@ -463,7 +505,7 @@ module.exports = function (grunt) throw new Error ('Optimize failed. It returned ' + JSON.stringify (result)); } // Optimization failed. Return the unaltered source code. - return {status: STAT.OK, data: source}; + return /** @type {OperationResult} */ {status: STAT.OK, data: source}; } /** @@ -473,7 +515,7 @@ module.exports = function (grunt) */ function conditionalIndent (result) { - return result.status === STAT.INDENTED ? result.data : indent (result.data); + return result.status === STAT.INDENTED ? result.data : indent (result.data, 1, options.indent); } /** diff --git a/tasks/lib/gruntUtil.js b/tasks/lib/gruntUtil.js index 6a286dd..6271d10 100644 --- a/tasks/lib/gruntUtil.js +++ b/tasks/lib/gruntUtil.js @@ -9,6 +9,7 @@ var util = require ('./util'); var csprintf = util.csprintf + , icsprintf = util.icsprintf , indent = util.indent , NL = util.NL; @@ -48,40 +49,43 @@ exports.reportErrorLocation = function (path) /** * Stops execution with an error message. - * Arguments are the same as the ones on sprintf. + * Arguments are the same as the ones on sprintf but supports color tags like csprintf does. + * Default color is red. */ exports.fatal = function () { - grunt.fail.fatal (csprintf.apply (null, ['red'].concat ([].slice.call (arguments)))); + grunt.fail.fatal (icsprintf('red',arguments)); }; /** * Displays an error message and, if --force is not enabled, stops execution. - * Arguments are the same as the ones on sprintf. + * Arguments are the same as the ones on sprintf but supports color tags like csprintf does. + * Default color is yellow. */ exports.warn = function () { - grunt.fail.warn (csprintf.apply (null, ['yellow'].concat ([].slice.call (arguments)))); + grunt.fail.warn (icsprintf('yellow',arguments)); }; /** * Displays a message. - * Arguments are the same as the ones on sprintf but supports color tags like csprintf. + * Arguments are the same as the ones on sprintf but supports color tags like csprintf does. + * Default color is white. */ exports.writeln = function () { - grunt.log.writeln (csprintf.apply (null, ['white'].concat ([].slice.call (arguments)))); + grunt.log.writeln (icsprintf('white',arguments)); }; /** * Displays the given message colored grey, but only if running in verbose mode. - * @param {string} msg - * @returns {string} + * Arguments are the same as the ones on sprintf but supports color tags like csprintf does. + * Default color is white. */ exports.info = function () { if (verbose) - grunt.log.writeln (csprintf2('white',arguments)); + grunt.log.writeln (icsprintf('white',arguments)); }; /** @@ -94,11 +98,3 @@ exports.getExplanation = function (msg) { return (verbose ? indent (csprintf ('grey', msg)) : ' Use -v for more info.'.grey) + NL; }; - -//------------------------------------------------------------------------------ -// PRIVATE -//------------------------------------------------------------------------------ - -function csprintf2 (color, args) { - return csprintf.apply (null, [color].concat ([].slice.call (args))); -} \ No newline at end of file diff --git a/tasks/lib/sandboxRun.js b/tasks/lib/sandboxRun.js index c3d8d81..7f3ec04 100644 --- a/tasks/lib/sandboxRun.js +++ b/tasks/lib/sandboxRun.js @@ -11,7 +11,8 @@ * In order to do that, it executes the code in an isolated sandbox. * If any function or variable is created on the global scope as a result from that execution, a waning is issued. * @param {string} source Javascript code to be analized. - * @return {false|Object} Returns false if the code is valid, otherwise returns an object with detected global scope properties as keys. + * @return {false|Object} Returns false if the code is valid, otherwise returns an object with detected global scope + * properties as keys. */ exports.detectInvalidSourceCode = function (source) { @@ -30,7 +31,7 @@ exports.detectInvalidSourceCode = function (source) service: mockupMethod, value: mockupMethod } - , noop = function (x) {} + , noop = function () {} , consoleMockup = { assert: noop, debug: noop, @@ -62,6 +63,7 @@ exports.detectInvalidSourceCode = function (source) delete sandbox.console; delete sandbox.window; // Check if the sandbox contains any property at all. + /*jshint unused:false */ for (var prop in sandbox) throw sandbox; return false; diff --git a/tasks/lib/sourceExtract.js b/tasks/lib/sourceExtract.js index 3a443c5..3fd7eb9 100644 --- a/tasks/lib/sourceExtract.js +++ b/tasks/lib/sourceExtract.js @@ -8,7 +8,8 @@ var util = require ('./util'); -var tokenize = util.tokenize; +var tokenize = util.tokenize + , sprintf = util.sprintf; //------------------------------------------------------------------------------ // TYPES @@ -43,7 +44,7 @@ var tokenize = util.tokenize; * @name ModuleClosureInfo#moduleDeps * @type {string} * List of module dependencies. -*/ + */ /** * @name ModuleHeaderInfo @@ -51,6 +52,10 @@ var tokenize = util.tokenize; * angular.module('name',[]) or * angular.module('name'). *//** + * @name ModuleHeaderInfo#status + * @type {EXTRACT_STAT} + * The result status. + *//** * @name ModuleHeaderInfo#name * @type {string} * Module name. @@ -63,7 +68,17 @@ var tokenize = util.tokenize; * @type {boolean} * If false this module reference is declaring the module and its dependencies. * If true this module reference is appending definitions to a module declared elsewhere. -*/ + */ + +/** + * Error codes returned by some functions of the sourceTrans module. + * @enum + */ +var EXTRACT_STAT = { + OK: 0, + MULTIPLE_MODULES: -1, + MULTIPLE_DECLS: -2 +}; //------------------------------------------------------------------------------ // PRIVATE DATA @@ -76,12 +91,6 @@ var tokenize = util.tokenize; * @type {string} */ var MODULE_DECL_EXP = 'angular `. module `( ["\'](.*?)["\'] (?:, (`[[^`]]*`]))? `)'; -/** - * Regular expression that matches an angular module declaration. - * @see MODULE_DECL_EXP - * @type {RegExp} - */ -var MATCH_MODULE_DECL = new RegExp (tokenize (MODULE_DECL_EXP), 'i'); /** * Regular expression string that matches javascript block/line comments. * @type {string} @@ -91,17 +100,17 @@ var MATCH_COMMENTS_EXP = '/`*[`s`S]*?`*/|//.*'; * Matches source code consisting only of white space and javascript comments. * @type {RegExp} */ -var MATCH_NO_SCRIPT = new RegExp (tokenize ('^ ((' + MATCH_COMMENTS_EXP + ') )*$')); +var MATCH_NO_SCRIPT = new RegExp (tokenize (sprintf ('^ ((%) )*$', MATCH_COMMENTS_EXP))); /** * Matches white space and javascript comments at the beginning of a file. * @type {RegExp} */ -var TRIM_COMMENTS_TOP = new RegExp (tokenize ('^ ((' + MATCH_COMMENTS_EXP + ') )*')); +var TRIM_COMMENTS_TOP = new RegExp (tokenize (sprintf ('^ ((%) )*', MATCH_COMMENTS_EXP))); /** * Matches white space and javascript comments at the end of a file. * @type {RegExp} */ -var TRIM_COMMENTS_BOTTOM = new RegExp (tokenize (' ((' + MATCH_COMMENTS_EXP + ') )*$')); +var TRIM_COMMENTS_BOTTOM = new RegExp (tokenize (sprintf (' ((%) )*$', MATCH_COMMENTS_EXP))); /** * Matches a self-invoking anonymous function that wraps all the remaining source code. * It assumes white space and comments have been already removed from both ends of the script. @@ -117,12 +126,19 @@ var TRIM_COMMENTS_BOTTOM = new RegExp (tokenize (' ((' + MATCH_COMMENTS_EXP + ') * * @type {RegExp} */ -var MATCH_MODULE_CLOSURE = new RegExp (tokenize ('^[`(!]function `( (.+?)? `) `{ ([`s`S]*?) `} `)? `( (' + MODULE_DECL_EXP + ')? `) ;?$'), 'i'); +var MATCH_MODULE_CLOSURE = new RegExp (tokenize (sprintf ( + '^[`(!]function `( (.+?)? `) `{ ([`s`S]*?) `} `)? `( (%)? `) ;?$', MODULE_DECL_EXP)), 'i'); //------------------------------------------------------------------------------ -// PUBLIC FUNCTIONS +// PUBLIC //------------------------------------------------------------------------------ +/** + * Error codes returned by some functions of the sourceExtract module. + * @type {EXTRACT_STAT} + */ +exports.EXTRACT_STAT = EXTRACT_STAT; + /** * Searches for an angular module declaration and, if found, extracts the module's name and dependencies from it. * Note: if the returned 'requires' property is undefined, that means the module declaration is appending @@ -135,13 +151,29 @@ var MATCH_MODULE_CLOSURE = new RegExp (tokenize ('^[`(!]function `( (.+?)? `) `{ */ exports.extractModuleHeader = function (source) { - var m = source.match (MATCH_MODULE_DECL); + var R = new RegExp (tokenize (MODULE_DECL_EXP), 'ig'); + var all = [], m; + while ((m = R.exec (source)) !== null) + all.push (m); // Ignore the file if it has no angular module definition. - if (!m) + if (!all.length) return null; + var moduleName = all[0][1] + , headerIndex = false; + for (var i = 0, x = all.length; i < x; ++i) { + if (all[i][1] !== moduleName) + return {status: EXTRACT_STAT.MULTIPLE_MODULES}; + if (all[i][2] !== undefined) { + if (headerIndex === false) + headerIndex = i; + else return {status: EXTRACT_STAT.MULTIPLE_DECLS}; + } + } + m = all[headerIndex || 0]; return /** @type {ModuleHeaderInfo} */ { - name: m[1], - append: !m[2], + status: EXTRACT_STAT.OK, + name: moduleName, + append: headerIndex === false, requires: m[2] && JSON.parse (m[2].replace (/'/g, '"')) || [] }; }; @@ -168,7 +200,7 @@ exports.getModuleClosureInfo = function (source) { /** @type {Array.} */ var m; - if (m = source.match (MATCH_MODULE_CLOSURE)) { + if ((m = source.match (MATCH_MODULE_CLOSURE))) { // Extract the function's body and some additional information about the module and how it's being declared. return /** @type {ModuleClosureInfo} */{ moduleVar: m[1], diff --git a/tasks/lib/sourceTrans.js b/tasks/lib/sourceTrans.js index 6943036..b1885dd 100644 --- a/tasks/lib/sourceTrans.js +++ b/tasks/lib/sourceTrans.js @@ -8,11 +8,9 @@ var sandboxRun = require ('./sandboxRun') , sourceExtract = require ('./sourceExtract') - , types = require ('./types') , util = require ('./util'); -var sprintf = util.sprintf - , OperationResult = types.OperationResult; +var sprintf = util.sprintf; //------------------------------------------------------------------------------ // TYPES @@ -26,7 +24,7 @@ var TRANS_STAT = { OK: 0, NO_CLOSURE_FOUND: -1, RENAME_REQUIRED: -2, - INVALID_DECLARATION: -2 + INVALID_DECLARATION: -3 }; //------------------------------------------------------------------------------ @@ -78,7 +76,7 @@ exports.optimize = function (source, moduleName, moduleVar) // Sanity check. if (modInfo.moduleName && modInfo.moduleName !== moduleName) - return {status: TRANS_STAT.INVALID_DECLARATION, data: modInfo.moduleName}; + return /** @type {OperationResult} */ {status: TRANS_STAT.INVALID_DECLARATION, data: modInfo.moduleName}; // Let's get that closure. source = sourceExtract.extractClosure (source, clean, modInfo.closureBody); @@ -87,11 +85,14 @@ exports.optimize = function (source, moduleName, moduleVar) // name from the preset name for module references, rename that parameter to the predefined name. if (modInfo.moduleVar && modInfo.moduleDecl && modInfo.moduleVar !== moduleVar) // Let the caller decide what to do. - return {status: TRANS_STAT.RENAME_NOT_ALLOWED, data: modInfo.moduleVar}; - return {status: TRANS_STAT.OK, data: source}; + return /** @type {OperationResult} */ { + status: TRANS_STAT.RENAME_REQUIRED, + data: modInfo + }; + return /** @type {OperationResult} */ {status: TRANS_STAT.OK, data: source}; } // No closure was detected. - return {status: TRANS_STAT.NO_CLOSURE_FOUND}; + return /** @type {OperationResult} */ {status: TRANS_STAT.NO_CLOSURE_FOUND}; }; @@ -113,7 +114,8 @@ exports.validateUnwrappedCode = function (source) }; /** - * Replace angular module reference expressions (with syntax angular.module(...)) inside the closure by variable references. + * Replace angular module reference expressions (with syntax angular.module(...)) inside the closure by + * variable references. * If the module expression defines no services/whatever, remove-it, as it will be regenerated outside the closure. * * @param {ModuleDef} module diff --git a/tasks/lib/types.js b/tasks/lib/types.js index 91734ad..9b29a7e 100644 --- a/tasks/lib/types.js +++ b/tasks/lib/types.js @@ -6,12 +6,6 @@ */ 'use strict'; -/** - * A result that includes a status value and optional data. - * @typedef {{status: number, data:*}} - */ -var OperationResult; - /** * A module definition record. * Contains all javascript defining the module, read from one or more source files. @@ -71,23 +65,32 @@ var TASK_OPTIONS = { */ main: '', /** - * Name of the variable representing the angular module being defined, to be used inside self-invoked anonymous functions. - * The default value is a relatively uncommon name. You may select another if this one causes a conflict with existing code. + * Name of the variable representing the angular module being defined, to be used inside self-invoked anonymous + * functions. + * The default value is a relatively uncommon name. You may select another if this one causes a conflict with existing + * code. * @type {string} */ moduleVar: 'declare', /** - * When true, angular module references passed as arguments to self-invoking functions will be renamed to config.moduleVar. + * When true, angular module references passed as arguments to self-invoking functions will be renamed to + * config.moduleVar. * - * When false, if the module reference parameter has a name that is different from the one defined on config.moduleVar, + * When false, if the module reference parameter has a name that is different from the one defined on + * config.moduleVar, * a warning will be issued and the task may stop. * @type {boolean} */ renameModuleRefs: false, /** * Code packaging method. - * When false, generates a single optimized javascript file with all required source code in the correct loading order. - * When true, generates a set of <script> tags to include all the required source files in the correct loading order. + * + * When false, generates a single optimized javascript file with all required source code in the correct + * loading order. + * + * When true, generates a set of <script> tags to include all the required source files in the correct + * loading order. + * * Note: The use of this setting as an option is, probably, not what you want. * Use the `debug` task argument instead. * @type {boolean} @@ -95,12 +98,20 @@ var TASK_OPTIONS = { debug: false, /** * A list of module names to ignore when building. - * This allows the source code to contain references to modules not present in the build (ex. 3rd party libraries that are loaded independently). + * This allows the source code to contain references to modules not present in the build (ex. 3rd party libraries that + * are loaded independently). * - * If a module reference (for module access or for declaring a dependency) is found in the source code, which targets a module that is not declared anywhere in the build's source files, the build operation aborts when that module name is not present on this list. - * @type {Array.} + * If a module reference (for module access or for declaring a dependency) is found in the source code, which targets + * a module that is not declared anywhere in the build's source files, the build operation aborts when that module + * name is not present on this list. + * @type {string|string[]} */ externalModules: null, + /** + * Indentation white space for one level. + * You may, for instance, configure it for tabs or additional spaces. + */ + indent: ' ', /** * This string will be appended to each module definition block. */ @@ -108,57 +119,72 @@ var TASK_OPTIONS = { }; /** + * @name FILE_GROUP_OPTIONS * Extended options for Grunt file groups. + *//** + * @name FILE_GROUP_OPTIONS#targetScript + * @type {string} + * Target javascript file name. + * The javascript build output will be saved to this path. + * + * Note: when multiple filegroups target the same file, only the first one will (re)create it, all others will + * append to it. + *//** + * @name FILE_GROUP_OPTIONS#targetCSS + * @type {string} + * Target CSS file name. + * The packaged stylesheets will be saved to this path. + * + * Note: when multiple filegroups target the same file, only the first one will (re)create it, all others will + * append to it. + *//** + * @name FILE_GROUP_OPTIONS#assetsTargetFolder + * @type {string} + * Target folder path for publishing assets. + * Relative paths for the source files as specified in stylesheet asset urls are preserved on the output, so the + * required folder structure will be recreated on the output target. + * Urls on the exported stylesheets will be rebased to this folder. + *//** + * @name FILE_GROUP_OPTIONS#forceInclude + * @type {string|string[]|null} + * A list of filenames or glob patterns that specify which javascript files should always be included in the build, + * even if they have no module declarations. + * + * Warning: the files must also be matched by src to be included. + * + * Note: patterns without slashes will match against the basename of the path even if it contains slashes, + * eg. pattern *.js will match filepath path/to/file.js. + * + * Usually, when a script file is found in the set of source files which doesn't contain a module declaration, + * that file is ignored. + * But, if the file name and path matches a file name or glob pattern specified here, it will still be included. + * + * Non-module files are output in the same order they were read, and before any module. + * + * Tip: You can append the current step's result script to another one that resulted from a previous build + * step. + * If you specify a target or file group exclusively for standalone script files and append the result to other built + * files, you will have more control on the order of the assembled files. + */ + +/** + * A function result composite value that includes a status value and optional data. + * @name OperationResult + *//** + * Result status code. 0 = OK, other values depend on the context this is being used on. + * @name OperationResult#status + * @type {number} + *//** + * Optional output data from the function that returned this record. + * @name OperationResult#data + * @type {*} */ -var FILE_GROUP_OPTIONS = { - /** - * Target javascript file name. - * The javascript build output will be saved to this path. - * - * Note: when multiple filegroups target the same file, only the first one will (re)create it, all others will append to it. - * @type {string} - */ - targetScript: '', - /** - * Target CSS file name. - * The packaged stylesheets will be saved to this path. - * - * Note: when multiple filegroups target the same file, only the first one will (re)create it, all others will append to it. - * @type {string} - */ - targetCSS: '', - /** - * Target folder path for publishing assets. - * Relative paths for the source files as specified in stylesheet asset urls are preserved on the output, so the required folder structure will be recreated on the output target. - * Urls on the exported stylesheets will be rebased to this folder. - * @type {string} - */ - assetsTargetFolder: '', - /** - * A list of filenames or glob patterns that specify which javascript files should always be included in the build, even if they have no module declarations. - * - * Warning: the files must also be matched by src to be included. - * - * Note: patterns without slashes will match against the basename of the path even if it contains slashes, eg. pattern *.js will match filepath path/to/file.js. - * - * Usually, when a script file is found in the set of source files which doesn't contain a module declaration, that file is ignored. - * But, if the file name and path matches a file name or glob pattern specified here, it will still be included. - * - * Non-module files are output in the same order they were read, and before any module. - * - * Tip: You can append the current step's result script to another one that resulted from a previous build step. - * If you specify a target or file group exclusively for standalone script files and append the result to other built files, you will have more control on the order of the assembled files. - * @type {string|Array.|null} - */ - forceInclude: null -}; //------------------------------------------------------------------------------ // EXPORT //------------------------------------------------------------------------------ module.exports = { - OperationResult: OperationResult, ModuleDef: ModuleDef, TASK_OPTIONS: TASK_OPTIONS }; \ No newline at end of file diff --git a/tasks/lib/util.js b/tasks/lib/util.js index c3df552..f1a5cfa 100644 --- a/tasks/lib/util.js +++ b/tasks/lib/util.js @@ -6,11 +6,11 @@ */ 'use strict'; -/** +/* * Get color and style in your node.js console. * Note: requiring this here modifies the String prototype! */ -var colors = require ('colors'); +require ('colors'); //------------------------------------------------------------------------------ // PUBLIC @@ -49,14 +49,15 @@ exports.getProperties = function (obj) */ exports.toList = function (array) { - return array.length ? "['" + array.join ("', '") + "']" : '[]'; + return array.length ? "['" + array.join ("', '") + "']" : "[]"; }; /** * Indents each line in the given text. * @param {string} text The text to be indented. * @param {number} [level=1] Indentation depth level. - * @param {string} [indentStr="  "] A white space string that represents each indentation level (ex. spaces or tabs). + * @param {string} [indentStr="  "] A white space string that represents each indentation level + * (ex. spaces or tabs). * @return {string} */ exports.indent = function (text, level, indentStr) @@ -89,12 +90,15 @@ exports.sprintf = function (str) * Placeholders are represented by the symbol %. * To colorize, use markup with the syntax: <color_name>text</color_name> * Warning: do not nest color tags! - * @param {string} baseColor The base color for the string. Segments with other colors will resume the base color where they end. + * @param {string} baseColor The base color for the string. Segments with other colors will resume the base color where + * they end. * @param {string} str The string to be formatted. + * @param {...string|...number} args Values for each placeholder in str. * @returns {string} */ -exports.csprintf = function (baseColor, str) +exports.csprintf = function (baseColor, str, args) { + /*jshint unused:false */ str = exports.sprintf.apply (null, [].slice.call (arguments, 1)); str = str.replace (/<(\w+)>([\s\S]*?)<\/\1>/g, function (m, m1, m2) { @@ -110,6 +114,19 @@ exports.csprintf = function (baseColor, str) return str[baseColor]; }; +/** + * @private + * Similar to csprintf but supports an args argument that should receive a function's + * arguments array-like object. + * + * @param {string} baseColor Color name. + * @param {Object} args Should be a function's arguments array-like object. + * @returns {string} + */ +exports.icsprintf = function (baseColor, args) { + return exports.csprintf.apply (null, [baseColor].concat ([].slice.call (args))); +}; + /** * Outputs debug information to the console. * @param {...*} args diff --git a/tests/js-only/App.js b/tests/js-only/App.js index 229cdc4..2afa9e6 100644 --- a/tests/js-only/App.js +++ b/tests/js-only/App.js @@ -1,5 +1,5 @@ -angular.module ('App', ['Submodule1']); +angular.module ('App', ['Submodule1', 'Library1']); diff --git a/tests/js-only/App2.js b/tests/js-only/App2.js deleted file mode 100644 index fa93065..0000000 --- a/tests/js-only/App2.js +++ /dev/null @@ -1 +0,0 @@ -angular.module ('App2', ['Submodule3']); diff --git a/tests/js-only/Library1.js b/tests/js-only/Library1.js new file mode 100644 index 0000000..7e6d14c --- /dev/null +++ b/tests/js-only/Library1.js @@ -0,0 +1 @@ +angular.module ('Library1', ['Submodule3']); diff --git a/tests/js-only/Library2.js b/tests/js-only/Library2.js new file mode 100644 index 0000000..28cc2ad --- /dev/null +++ b/tests/js-only/Library2.js @@ -0,0 +1 @@ +angular.module ('Library2', ['Submodule3', 'Submodule4']); diff --git a/tests/js-only/extra/Submodule3.js b/tests/js-only/extra/Submodule3.js index fd12488..d8bd5d2 100644 --- a/tests/js-only/extra/Submodule3.js +++ b/tests/js-only/extra/Submodule3.js @@ -1,6 +1,12 @@ -!function(module) { +(function (module) +{ + module.service ('testX', function () + { + // code ommited + }); - module.service ('testX', function () { - // code ommited -}); -} (angular.module('Submodule3', [])); \ No newline at end of file + angular.module ('Submodule3').service ('testX2', function () + { + // code ommited + }); +}) (angular.module ('Submodule3', [])); \ No newline at end of file diff --git a/tests/js-only/extra/Submodule4.js b/tests/js-only/extra/Submodule4.js new file mode 100644 index 0000000..029903b --- /dev/null +++ b/tests/js-only/extra/Submodule4.js @@ -0,0 +1,12 @@ +(function (declare) +{ + angular.module ('Submodule4').service ('testA', function () + { + // code ommited + }); + + declare.service ('testB', function () + { + // code ommited + }); +}) (angular.module ('Submodule4', [])); \ No newline at end of file