diff --git a/demo/kitchen-sink/require.js b/demo/kitchen-sink/require.js index 39dbad8e..062516ac 100644 --- a/demo/kitchen-sink/require.js +++ b/demo/kitchen-sink/require.js @@ -1,18 +1,18 @@ /** vim: et:ts=4:sw=4:sts=4 - * @license RequireJS 2.1.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. + * @license RequireJS 2.1.5 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. * Available via the MIT or new BSD license. * see: http://github.com/jrburke/requirejs for details */ //Not using strict: uneven strict support in browsers, #392, and causes //problems with requirejs.exec()/transpiler plugins that may not be strict. /*jslint regexp: true, nomen: true, sloppy: true */ -/*global window, navigator, document, importScripts, jQuery, setTimeout, opera */ +/*global window, navigator, document, importScripts, setTimeout, opera */ var requirejs, require, define; (function (global) { var req, s, head, baseElement, dataMain, src, interactiveScript, currentlyAddingScript, mainScript, subPath, - version = '2.1.1', + version = '2.1.5', commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg, cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g, jsSuffixRegExp = /\.js$/, @@ -21,7 +21,6 @@ var requirejs, require, define; ostring = op.toString, hasOwn = op.hasOwnProperty, ap = Array.prototype, - aps = ap.slice, apsp = ap.splice, isBrowser = !!(typeof window !== 'undefined' && navigator && document), isWebWorker = !isBrowser && typeof importScripts !== 'undefined', @@ -81,6 +80,10 @@ var requirejs, require, define; return hasOwn.call(obj, prop); } + function getOwn(obj, prop) { + return hasProp(obj, prop) && obj[prop]; + } + /** * Cycles over properties in an object and calls a function for each * property value. If the function returns a truthy value, then the @@ -89,7 +92,7 @@ var requirejs, require, define; function eachProp(obj, func) { var prop; for (prop in obj) { - if (obj.hasOwnProperty(prop)) { + if (hasProp(obj, prop)) { if (func(obj[prop], prop)) { break; } @@ -188,15 +191,21 @@ var requirejs, require, define; var inCheckLoaded, Module, context, handlers, checkLoadedTimeoutId, config = { + //Defaults. Do not set a default for map + //config to speed up normalize(), which + //will run faster if there is no default. waitSeconds: 7, baseUrl: './', paths: {}, pkgs: {}, shim: {}, - map: {}, config: {} }, registry = {}, + //registry of just enabled modules, to speed + //cycle breaking code when lots of modules + //are registered, but not activated. + enabledRegistry = {}, undefEvents = {}, defQueue = [], defined = {}, @@ -261,7 +270,7 @@ var requirejs, require, define; //otherwise, assume it is a top-level require that will //be relative to baseUrl in the end. if (baseName) { - if (config.pkgs[baseName]) { + if (getOwn(config.pkgs, baseName)) { //If the baseName is a package name, then just treat it as one //name to concat the name with. normalizedBaseParts = baseParts = [baseName]; @@ -279,7 +288,7 @@ var requirejs, require, define; //Some use of packages may use a . path to reference the //'main' module name, so normalize for that. - pkgConfig = config.pkgs[(pkgName = name[0])]; + pkgConfig = getOwn(config.pkgs, (pkgName = name[0])); name = name.join('/'); if (pkgConfig && name === pkgName + '/' + pkgConfig.main) { name = pkgName; @@ -292,7 +301,7 @@ var requirejs, require, define; } //Apply map config if available. - if (applyMap && (baseParts || starMap) && map) { + if (applyMap && map && (baseParts || starMap)) { nameParts = name.split('/'); for (i = nameParts.length; i > 0; i -= 1) { @@ -302,12 +311,12 @@ var requirejs, require, define; //Find the longest baseName segment match in the config. //So, do joins on the biggest to smallest lengths of baseParts. for (j = baseParts.length; j > 0; j -= 1) { - mapValue = map[baseParts.slice(0, j).join('/')]; + mapValue = getOwn(map, baseParts.slice(0, j).join('/')); //baseName segment has config, find if it has one for //this name. if (mapValue) { - mapValue = mapValue[nameSegment]; + mapValue = getOwn(mapValue, nameSegment); if (mapValue) { //Match, update name to the new value. foundMap = mapValue; @@ -325,8 +334,8 @@ var requirejs, require, define; //Check for a star map match, but just hold on to it, //if there is a shorter segment match later in a matching //config, then favor over this star map. - if (!foundStarMap && starMap && starMap[nameSegment]) { - foundStarMap = starMap[nameSegment]; + if (!foundStarMap && starMap && getOwn(starMap, nameSegment)) { + foundStarMap = getOwn(starMap, nameSegment); starI = i; } } @@ -358,7 +367,7 @@ var requirejs, require, define; } function hasPathFallback(id) { - var pathConfig = config.paths[id]; + var pathConfig = getOwn(config.paths, id); if (pathConfig && isArray(pathConfig) && pathConfig.length > 1) { removeScript(id); //Pop off the first array value, since it failed, and @@ -419,7 +428,7 @@ var requirejs, require, define; if (prefix) { prefix = normalize(prefix, parentName, applyMap); - pluginModule = defined[prefix]; + pluginModule = getOwn(defined, prefix); } //Account for relative paths if there is a base name. @@ -472,7 +481,7 @@ var requirejs, require, define; function getModule(depMap) { var id = depMap.id, - mod = registry[id]; + mod = getOwn(registry, id); if (!mod) { mod = registry[id] = new context.Module(depMap); @@ -483,7 +492,7 @@ var requirejs, require, define; function on(depMap, name, fn) { var id = depMap.id, - mod = registry[id]; + mod = getOwn(registry, id); if (hasProp(defined, id) && (!mod || mod.defineEmitComplete)) { @@ -503,7 +512,7 @@ var requirejs, require, define; errback(err); } else { each(ids, function (id) { - var mod = registry[id]; + var mod = getOwn(registry, id); if (mod) { //Set error on module, so it skips timeout checks. mod.error = err; @@ -562,7 +571,7 @@ var requirejs, require, define; id: mod.map.id, uri: mod.map.url, config: function () { - return (config.config && config.config[mod.map.id]) || {}; + return (config.config && getOwn(config.config, mod.map.id)) || {}; }, exports: defined[mod.map.id] }); @@ -573,6 +582,7 @@ var requirejs, require, define; function cleanRegistry(id) { //Clean up machinery used for waiting modules. delete registry[id]; + delete enabledRegistry[id]; } function breakCycle(mod, traced, processed) { @@ -584,14 +594,14 @@ var requirejs, require, define; traced[id] = true; each(mod.depMaps, function (depMap, i) { var depId = depMap.id, - dep = registry[depId]; + dep = getOwn(registry, depId); //Only force things that have not completed //being defined, so still in the registry, //and only if it has not been matched up //in the module already. if (dep && !mod.depMatched[i] && !processed[depId]) { - if (traced[depId]) { + if (getOwn(traced, depId)) { mod.defineDep(i, defined[depId]); mod.check(); //pass false? } else { @@ -621,7 +631,7 @@ var requirejs, require, define; inCheckLoaded = true; //Figure out the state of all the modules. - eachProp(registry, function (mod) { + eachProp(enabledRegistry, function (mod) { map = mod.map; modId = map.id; @@ -691,9 +701,9 @@ var requirejs, require, define; } Module = function (map) { - this.events = undefEvents[map.id] || {}; + this.events = getOwn(undefEvents, map.id) || {}; this.map = map; - this.shim = config.shim[map.id]; + this.shim = getOwn(config.shim, map.id); this.depExports = []; this.depMaps = []; this.depMatched = []; @@ -802,7 +812,7 @@ var requirejs, require, define; }, /** - * Checks is the module is ready to define itself, and if so, + * Checks if the module is ready to define itself, and if so, * define it. */ check: function () { @@ -880,7 +890,7 @@ var requirejs, require, define; } //Clean up - delete registry[id]; + cleanRegistry(id); this.defined = true; } @@ -914,8 +924,7 @@ var requirejs, require, define; name = this.map.name, parentName = this.map.parentMap ? this.map.parentMap.name : null, localRequire = context.makeRequire(map.parentMap, { - enableBuildCallback: true, - skipMap: true + enableBuildCallback: true }); //If current map is not normalized, wait for that @@ -940,7 +949,7 @@ var requirejs, require, define; }); })); - normalizedMod = registry[normalizedMap.id]; + normalizedMod = getOwn(registry, normalizedMap.id); if (normalizedMod) { //Mark this as a dependency for this plugin, so it //can be traced for cycles. @@ -1005,11 +1014,19 @@ var requirejs, require, define; //it. getModule(moduleMap); + //Transfer any config to this other module. + if (hasProp(config.config, id)) { + config.config[moduleName] = config.config[id]; + } + try { req.exec(text); } catch (e) { - throw new Error('fromText eval for ' + moduleName + - ' failed: ' + e); + return onError(makeError('fromtexteval', + 'fromText eval for ' + id + + ' failed: ' + e, + e, + [id])); } if (hasInteractive) { @@ -1039,6 +1056,7 @@ var requirejs, require, define; }, enable: function () { + enabledRegistry[this.map.id] = this; this.enabled = true; //Set flag mentioning that the module is enabling, @@ -1060,7 +1078,7 @@ var requirejs, require, define; !this.skipMap); this.depMaps[i] = depMap; - handler = handlers[depMap.id]; + handler = getOwn(handlers, depMap.id); if (handler) { this.depExports[i] = handler(this); @@ -1085,7 +1103,7 @@ var requirejs, require, define; //Skip special modules like 'require', 'exports', 'module' //Also, don't call enable if it is already enabled, //important in circular dependency cases. - if (!handlers[id] && mod && !mod.enabled) { + if (!hasProp(handlers, id) && mod && !mod.enabled) { context.enable(depMap, this); } })); @@ -1093,7 +1111,7 @@ var requirejs, require, define; //Enable each plugin that is used in //a dependency eachProp(this.pluginMaps, bind(this, function (pluginMap) { - var mod = registry[pluginMap.id]; + var mod = getOwn(registry, pluginMap.id); if (mod && !mod.enabled) { context.enable(pluginMap, this); } @@ -1126,7 +1144,10 @@ var requirejs, require, define; }; function callGetModule(args) { - getModule(makeModuleMap(args[0], null, true)).init(args[1], args[2]); + //Skip modules already defined. + if (!hasProp(defined, args[0])) { + getModule(makeModuleMap(args[0], null, true)).init(args[1], args[2]); + } } function removeListener(node, func, name, ieName) { @@ -1195,6 +1216,7 @@ var requirejs, require, define; Module: Module, makeModuleMap: makeModuleMap, nextTick: req.nextTick, + onError: onError, /** * Set a configuration for the context. @@ -1221,6 +1243,9 @@ var requirejs, require, define; eachProp(cfg, function (value, prop) { if (objs[prop]) { if (prop === 'map') { + if (!config.map) { + config.map = {}; + } mixin(config[prop], value, true, true); } else { mixin(config[prop], value, true); @@ -1239,7 +1264,7 @@ var requirejs, require, define; deps: value }; } - if (value.exports && !value.exportsFn) { + if ((value.exports || value.init) && !value.exportsFn) { value.exportsFn = context.makeShimExports(value); } shim[id] = value; @@ -1301,7 +1326,7 @@ var requirejs, require, define; if (value.init) { ret = value.init.apply(global, arguments); } - return ret || getGlobal(value.exports); + return ret || (value.exports && getGlobal(value.exports)); } return fn; }, @@ -1325,14 +1350,14 @@ var requirejs, require, define; //If require|exports|module are requested, get the //value for them from the special handlers. Caveat: //this only works while module is being defined. - if (relMap && handlers[deps]) { + if (relMap && hasProp(handlers, deps)) { return handlers[deps](registry[relMap.id]); } //Synchronous access to one module. If require.get is //available (as in the Node adapter), prefer that. if (req.get) { - return req.get(context, deps, relMap); + return req.get(context, deps, relMap, localRequire); } //Normalize module name, if it contains . or .. @@ -1383,16 +1408,20 @@ var requirejs, require, define; * plain URLs like nameToUrl. */ toUrl: function (moduleNamePlusExt) { - var index = moduleNamePlusExt.lastIndexOf('.'), - ext = null; + var ext, + index = moduleNamePlusExt.lastIndexOf('.'), + segment = moduleNamePlusExt.split('/')[0], + isRelative = segment === '.' || segment === '..'; - if (index !== -1) { + //Have a file extension alias, and it is not the + //dots from a relative path. + if (index !== -1 && (!isRelative || index > 1)) { ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length); moduleNamePlusExt = moduleNamePlusExt.substring(0, index); } return context.nameToUrl(normalize(moduleNamePlusExt, - relMap && relMap.id, true), ext); + relMap && relMap.id, true), ext, true); }, defined: function (id) { @@ -1413,7 +1442,7 @@ var requirejs, require, define; takeGlobalQueue(); var map = makeModuleMap(id, relMap, true), - mod = registry[id]; + mod = getOwn(registry, id); delete defined[id]; delete urlFetched[map.url]; @@ -1437,11 +1466,12 @@ var requirejs, require, define; /** * Called to enable a module if it is still in the registry - * awaiting enablement. parent module is passed in for context, - * used by the optimizer. + * awaiting enablement. A second arg, parent, the parent module, + * is passed in for context, when this method is overriden by + * the optimizer. Not shown here to keep code compact. */ - enable: function (depMap, parent) { - var mod = registry[depMap.id]; + enable: function (depMap) { + var mod = getOwn(registry, depMap.id); if (mod) { getModule(depMap).enable(); } @@ -1455,7 +1485,7 @@ var requirejs, require, define; */ completeLoad: function (moduleName) { var found, args, mod, - shim = config.shim[moduleName] || {}, + shim = getOwn(config.shim, moduleName) || {}, shExports = shim.exports; takeGlobalQueue(); @@ -1481,9 +1511,9 @@ var requirejs, require, define; //Do this after the cycle of callGetModule in case the result //of those calls/init calls changes the registry. - mod = registry[moduleName]; + mod = getOwn(registry, moduleName); - if (!found && !defined[moduleName] && mod && !mod.inited) { + if (!found && !hasProp(defined, moduleName) && mod && !mod.inited) { if (config.enforceDefine && (!shExports || !getGlobal(shExports))) { if (hasPathFallback(moduleName)) { return; @@ -1510,7 +1540,7 @@ var requirejs, require, define; * it is assumed to have already been normalized. This is an * internal API, not a public one. Use toUrl for the public API. */ - nameToUrl: function (moduleName, ext) { + nameToUrl: function (moduleName, ext, skipExt) { var paths, pkgs, pkg, pkgPath, syms, i, parentModule, url, parentPath; @@ -1534,8 +1564,8 @@ var requirejs, require, define; //and work up from it. for (i = syms.length; i > 0; i -= 1) { parentModule = syms.slice(0, i).join('/'); - pkg = pkgs[parentModule]; - parentPath = paths[parentModule]; + pkg = getOwn(pkgs, parentModule); + parentPath = getOwn(paths, parentModule); if (parentPath) { //If an array, it means there are a few choices, //Choose the one that is desired @@ -1559,7 +1589,7 @@ var requirejs, require, define; //Join the path parts together, then figure out if baseUrl is needed. url = syms.join('/'); - url += (ext || (/\?/.test(url) ? '' : '.js')); + url += (ext || (/\?/.test(url) || skipExt ? '' : '.js')); url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url; } @@ -1660,7 +1690,7 @@ var requirejs, require, define; contextName = config.context; } - context = contexts[contextName]; + context = getOwn(contexts, contextName); if (!context) { context = contexts[contextName] = req.s.newContext(contextName); } @@ -1798,7 +1828,7 @@ var requirejs, require, define; node.attachEvent('onreadystatechange', context.onScriptLoad); //It would be great to add an error handler here to catch //404s in IE9+. However, onreadystatechange will fire before - //the error handler, so that does not help. If addEvenListener + //the error handler, so that does not help. If addEventListener //is used, then IE will fire error before load, but we cannot //use that pathway given the connect.microsoft.com issue //mentioned above about not doing the 'script execute, @@ -1827,16 +1857,24 @@ var requirejs, require, define; return node; } else if (isWebWorker) { - //In a web worker, use importScripts. This is not a very - //efficient use of importScripts, importScripts will block until - //its script is downloaded and evaluated. However, if web workers - //are in play, the expectation that a build has been done so that - //only one script needs to be loaded anyway. This may need to be - //reevaluated if other use cases become common. - importScripts(url); + try { + //In a web worker, use importScripts. This is not a very + //efficient use of importScripts, importScripts will block until + //its script is downloaded and evaluated. However, if web workers + //are in play, the expectation that a build has been done so that + //only one script needs to be loaded anyway. This may need to be + //reevaluated if other use cases become common. + importScripts(url); - //Account for anonymous modules - context.completeLoad(moduleName); + //Account for anonymous modules + context.completeLoad(moduleName); + } catch (e) { + context.onError(makeError('importscripts', + 'importScripts failed for ' + + moduleName + ' at ' + url, + e, + [moduleName])); + } } }; diff --git a/lib/ace/mode/css/csslint.js b/lib/ace/mode/css/csslint.js index 02d1ba15..9f4c3bc8 100644 --- a/lib/ace/mode/css/csslint.js +++ b/lib/ace/mode/css/csslint.js @@ -22,8 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Build time: 12-September-2012 01:46:26 */ - +/* Build time: 17-January-2013 10:55:01 */ /*! Parser-Lib Copyright (c) 2009-2011 Nicholas C. Zakas. All rights reserved. @@ -47,7 +46,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Version v0.1.9, Build time: 23-July-2012 10:52:31 */ +/* Version v0.2.2, Build time: 17-January-2013 10:26:34 */ var parserlib = {}; (function(){ @@ -934,7 +933,7 @@ TokenStreamBase : TokenStreamBase })(); -/* +/* Parser-Lib Copyright (c) 2009-2011 Nicholas C. Zakas. All rights reserved. @@ -957,7 +956,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Version v0.1.9, Build time: 23-July-2012 10:52:31 */ +/* Version v0.2.2, Build time: 17-January-2013 10:26:34 */ (function(){ var EventTarget = parserlib.util.EventTarget, TokenStreamBase = parserlib.util.TokenStreamBase, @@ -1106,7 +1105,36 @@ var Colors = { white :"#ffffff", whitesmoke :"#f5f5f5", yellow :"#ffff00", - yellowgreen :"#9acd32" + yellowgreen :"#9acd32", + //CSS2 system colors http://www.w3.org/TR/css3-color/#css2-system + activeBorder :"Active window border.", + activecaption :"Active window caption.", + appworkspace :"Background color of multiple document interface.", + background :"Desktop background.", + buttonface :"The face background color for 3-D elements that appear 3-D due to one layer of surrounding border.", + buttonhighlight :"The color of the border facing the light source for 3-D elements that appear 3-D due to one layer of surrounding border.", + buttonshadow :"The color of the border away from the light source for 3-D elements that appear 3-D due to one layer of surrounding border.", + buttontext :"Text on push buttons.", + captiontext :"Text in caption, size box, and scrollbar arrow box.", + graytext :"Grayed (disabled) text. This color is set to #000 if the current display driver does not support a solid gray color.", + highlight :"Item(s) selected in a control.", + highlighttext :"Text of item(s) selected in a control.", + inactiveborder :"Inactive window border.", + inactivecaption :"Inactive window caption.", + inactivecaptiontext :"Color of text in an inactive caption.", + infobackground :"Background color for tooltip controls.", + infotext :"Text color for tooltip controls.", + menu :"Menu background.", + menutext :"Text in menus.", + scrollbar :"Scroll bar gray area.", + threeddarkshadow :"The color of the darker (generally outer) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.", + threedface :"The face background color for 3-D elements that appear 3-D due to two concentric layers of surrounding border.", + threedhighlight :"The color of the lighter (generally outer) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.", + threedlightshadow :"The color of the darker (generally inner) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.", + threedshadow :"The color of the lighter (generally inner) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.", + window :"Window background.", + windowframe :"Window frame.", + windowtext :"Text in windows." }; /*global SyntaxUnit, Parser*/ /** @@ -1195,7 +1223,7 @@ MediaFeature.prototype.constructor = MediaFeature; */ function MediaQuery(modifier, mediaType, features, line, col){ - SyntaxUnit.call(this, (modifier ? modifier + " ": "") + (mediaType ? mediaType + " " : "") + features.join(" and "), line, col, Parser.MEDIA_QUERY_TYPE); + SyntaxUnit.call(this, (modifier ? modifier + " ": "") + (mediaType ? mediaType : "") + (mediaType && features.length > 0 ? " and " : "") + features.join(" and "), line, col, Parser.MEDIA_QUERY_TYPE); /** * The media modifier ("not" or "only") @@ -1934,18 +1962,21 @@ Parser.prototype = function(){ }); }, - _operator: function(){ + _operator: function(inFunction){ /* - * operator + * operator (outside function) * : '/' S* | ',' S* | /( empty )/ + * operator (inside function) + * : '/' S* | '+' S* | '*' S* | '-' S* /( empty )/ * ; */ var tokenStream = this._tokenStream, token = null; - if (tokenStream.match([Tokens.SLASH, Tokens.COMMA])){ + if (tokenStream.match([Tokens.SLASH, Tokens.COMMA]) || + (inFunction && tokenStream.match([Tokens.PLUS, Tokens.STAR, Tokens.MINUS]))){ token = tokenStream.token(); this._readWhitespace(); } @@ -2556,7 +2587,7 @@ Parser.prototype = function(){ while(tokenStream.match([Tokens.PLUS, Tokens.MINUS, Tokens.DIMENSION, Tokens.NUMBER, Tokens.STRING, Tokens.IDENT, Tokens.LENGTH, Tokens.FREQ, Tokens.ANGLE, Tokens.TIME, - Tokens.RESOLUTION])){ + Tokens.RESOLUTION, Tokens.SLASH])){ value += tokenStream.token().value; value += this._readWhitespace(); @@ -2732,7 +2763,7 @@ Parser.prototype = function(){ return result; }, - _expr: function(){ + _expr: function(inFunction){ /* * expr * : term [ operator term ]* @@ -2741,7 +2772,7 @@ Parser.prototype = function(){ var tokenStream = this._tokenStream, values = [], - //valueParts = [], + //valueParts = [], value = null, operator = null; @@ -2751,8 +2782,8 @@ Parser.prototype = function(){ values.push(value); do { - operator = this._operator(); - + operator = this._operator(inFunction); + //if there's an operator, keep building up the value parts if (operator){ values.push(operator); @@ -2888,7 +2919,7 @@ Parser.prototype = function(){ if (tokenStream.match(Tokens.FUNCTION)){ functionText = tokenStream.token().value; this._readWhitespace(); - expr = this._expr(); + expr = this._expr(true); functionText += expr; //START: Horrible hack in case it's an IE filter @@ -3502,7 +3533,7 @@ var Properties = { "-o-animation-name" : { multi: "none | ", comma: true }, "-o-animation-play-state" : { multi: "running | paused", comma: true }, - "appearance" : "icon | window | desktop | workspace | document | tooltip | dialog | button | push-button | hyperlink | radio-button | checkbox | menu-item | tab | menu | menubar | pull-down-menu | pop-up-menu | list-menu | radio-group | checkbox-group | outline-tree | range | field | combo-box | signature | password | normal | inherit", + "appearance" : "icon | window | desktop | workspace | document | tooltip | dialog | button | push-button | hyperlink | radio-button | checkbox | menu-item | tab | menu | menubar | pull-down-menu | pop-up-menu | list-menu | radio-group | checkbox-group | outline-tree | range | field | combo-box | signature | password | normal | none | inherit", "azimuth" : function (expression) { var simple = " | leftwards | rightwards | inherit", direction = "left-side | far-left | left | center-left | center | center-right | right | far-right | right-side", @@ -3623,7 +3654,7 @@ var Properties = { valid = ValidationTypes.isAny(expression, numeric); if (!valid) { - if (expression.peek() == "/" && count > 1 && !slash) { + if (expression.peek() == "/" && count > 0 && !slash) { slash = true; max = count + 5; expression.next(); @@ -3711,7 +3742,7 @@ var Properties = { //D "direction" : "ltr | rtl | inherit", - "display" : "inline | block | list-item | inline-block | table | inline-table | table-row-group | table-header-group | table-footer-group | table-row | table-column-group | table-column | table-cell | table-caption | box | inline-box | grid | inline-grid | none | inherit", + "display" : "inline | block | list-item | inline-block | table | inline-table | table-row-group | table-header-group | table-footer-group | table-row | table-column-group | table-column | table-cell | table-caption | box | inline-box | grid | inline-grid | none | inherit | -moz-box | -moz-inline-block | -moz-inline-box | -moz-inline-grid | -moz-inline-stack | -moz-inline-table | -moz-grid | -moz-grid-group | -moz-grid-line | -moz-groupbox | -moz-deck | -moz-popup | -moz-stack | -moz-marker", "dominant-baseline" : 1, "drop-initial-after-adjust" : "central | middle | after-edge | text-after-edge | ideographic | alphabetic | mathematical | | ", "drop-initial-after-align" : "baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical", @@ -3914,7 +3945,7 @@ var Properties = { "user-select" : "none | text | toggle | element | elements | all | inherit", //V - "vertical-align" : " | | baseline | sub | super | top | text-top | middle | bottom | text-bottom | inherit", + "vertical-align" : "auto | use-script | baseline | sub | super | top | text-top | central | middle | bottom | text-bottom | | ", "visibility" : "visible | hidden | collapse | inherit", "voice-balance" : 1, "voice-duration" : 1, @@ -3927,7 +3958,7 @@ var Properties = { "volume" : 1, //W - "white-space" : "normal | pre | nowrap | pre-wrap | pre-line | inherit", + "white-space" : "normal | pre | nowrap | pre-wrap | pre-line | inherit | -pre-wrap | -o-pre-wrap | -moz-pre-wrap | -hp-pre-wrap", //http://perishablepress.com/wrapping-content/ "white-space-collapse" : 1, "widows" : " | inherit", "width" : " | | auto | inherit" , @@ -6006,7 +6037,7 @@ var ValidationTypes = { }, /** - * Determines if the next part(s) of the given expresion + * Determines if the next part(s) of the given expression * are one of a group. */ isAnyOfGroup: function(expression, types) { @@ -6087,7 +6118,11 @@ var ValidationTypes = { }, "": function(part){ - return part.type == "length" || part.type == "number" || part.type == "integer" || part == "0"; + if (part.type == "function" && /^(?:\-(?:ms|moz|o|webkit)\-)?calc/i.test(part)){ + return true; + }else{ + return part.type == "length" || part.type == "number" || part.type == "integer" || part == "0"; + } }, "": function(part){ @@ -6153,10 +6188,16 @@ var ValidationTypes = { var types = this, result = false, numeric = " | ", - xDir = "left | center | right", - yDir = "top | center | bottom", - part, - i, len; + xDir = "left | right", + yDir = "top | bottom", + count = 0, + hasNext = function() { + return expression.hasNext() && expression.peek() != ","; + }; + + while (expression.peek(count) && expression.peek(count) != ",") { + count++; + } /* = [ @@ -6168,40 +6209,48 @@ var ValidationTypes = { [ center | [ left | right ] [ | ]? ] && [ center | [ top | bottom ] [ | ]? ] ] +*/ -*/ - - if (ValidationTypes.isAny(expression, "top | bottom")) { - result = true; + if (count < 3) { + if (ValidationTypes.isAny(expression, xDir + " | center | " + numeric)) { + result = true; + ValidationTypes.isAny(expression, yDir + " | center | " + numeric); + } else if (ValidationTypes.isAny(expression, yDir)) { + result = true; + ValidationTypes.isAny(expression, xDir + " | center"); + } } else { - - //must be two-part - if (ValidationTypes.isAny(expression, numeric)){ - if (expression.hasNext()){ - result = ValidationTypes.isAny(expression, numeric + " | " + yDir); - } - } else if (ValidationTypes.isAny(expression, xDir)){ - if (expression.hasNext()){ - - //two- or three-part - if (ValidationTypes.isAny(expression, yDir)){ + if (ValidationTypes.isAny(expression, xDir)) { + if (ValidationTypes.isAny(expression, yDir)) { + result = true; + ValidationTypes.isAny(expression, numeric); + } else if (ValidationTypes.isAny(expression, numeric)) { + if (ValidationTypes.isAny(expression, yDir)) { result = true; - ValidationTypes.isAny(expression, numeric); - - } else if (ValidationTypes.isAny(expression, numeric)){ - - //could also be two-part, so check the next part - if (ValidationTypes.isAny(expression, yDir)){ - ValidationTypes.isAny(expression, numeric); - } - + } else if (ValidationTypes.isAny(expression, "center")) { result = true; } } - } - } - + } else if (ValidationTypes.isAny(expression, yDir)) { + if (ValidationTypes.isAny(expression, xDir)) { + result = true; + ValidationTypes.isAny(expression, numeric); + } else if (ValidationTypes.isAny(expression, numeric)) { + if (ValidationTypes.isAny(expression, xDir)) { + result = true; + ValidationTypes.isAny(expression, numeric); + } else if (ValidationTypes.isAny(expression, "center")) { + result = true; + } + } + } else if (ValidationTypes.isAny(expression, "center")) { + if (ValidationTypes.isAny(expression, xDir + " | " + yDir)) { + result = true; + ValidationTypes.isAny(expression, numeric); + } + } + } return result; }, @@ -6308,9 +6357,10 @@ var ValidationTypes = { }; + parserlib.css = { -Colors :Colors, -Combinator :Combinator, +Colors :Colors, +Combinator :Combinator, Parser :Parser, PropertyName :PropertyName, PropertyValue :PropertyValue, @@ -6328,7 +6378,6 @@ ValidationError :ValidationError })(); - /** * Main CSSLint object. * @class CSSLint @@ -6338,11 +6387,12 @@ ValidationError :ValidationError /*global parserlib, Reporter*/ var CSSLint = (function(){ - var rules = [], - formatters = [], - api = new parserlib.util.EventTarget(); - - api.version = "0.9.9"; + var rules = [], + formatters = [], + embeddedRuleset = /\/\*csslint([^\*]*)\*\//, + api = new parserlib.util.EventTarget(); + + api.version = "0.9.10"; //------------------------------------------------------------------------- // Rule Management @@ -6365,35 +6415,70 @@ var CSSLint = (function(){ api.clearRules = function(){ rules = []; }; - + /** * Returns the rule objects. - * @return {Array} An array of rule objects. + * @return An array of rule objects. * @method getRules */ api.getRules = function(){ - return [].concat(rules).sort(function(a,b){ + return [].concat(rules).sort(function(a,b){ return a.id > b.id ? 1 : 0; }); }; - + /** * Returns a ruleset configuration object with all current rules. - * @return {Object} A ruleset object. + * @return A ruleset object. * @method getRuleset */ api.getRuleset = function() { var ruleset = {}, i = 0, len = rules.length; - + while (i < len){ ruleset[rules[i++].id] = 1; //by default, everything is a warning } - + return ruleset; }; + /** + * Returns a ruleset object based on embedded rules. + * @param {String} text A string of css containing embedded rules. + * @param {Object} ruleset A ruleset object to modify. + * @return {Object} A ruleset object. + * @method getEmbeddedRuleset + */ + function applyEmbeddedRuleset(text, ruleset){ + var valueMap, + embedded = text && text.match(embeddedRuleset), + rules = embedded && embedded[1]; + + if (rules) { + valueMap = { + "true": 2, // true is error + "": 1, // blank is warning + "false": 0, // false is ignore + + "2": 2, // explicit error + "1": 1, // explicit warning + "0": 0 // explicit ignore + }; + + rules.toLowerCase().split(",").forEach(function(rule){ + var pair = rule.split(":"), + property = pair[0] || "", + value = pair[1] || ""; + + ruleset[property.trim()] = valueMap[value.trim()]; + }); + } + + return ruleset; + } + //------------------------------------------------------------------------- // Formatters //------------------------------------------------------------------------- @@ -6407,7 +6492,7 @@ var CSSLint = (function(){ // formatters.push(formatter); formatters[formatter.id] = formatter; }; - + /** * Retrieves a formatter for use. * @param {String} formatId The name of the format to retrieve. @@ -6417,7 +6502,7 @@ var CSSLint = (function(){ api.getFormatter = function(formatId){ return formatters[formatId]; }; - + /** * Formats the results in a particular format for a single file. * @param {Object} result The results returned from CSSLint.verify(). @@ -6430,16 +6515,16 @@ var CSSLint = (function(){ api.format = function(results, filename, formatId, options) { var formatter = this.getFormatter(formatId), result = null; - + if (formatter){ result = formatter.startFormat(); result += formatter.formatResults(results, filename, options || {}); result += formatter.endFormat(); } - + return result; }; - + /** * Indicates if the given format is supported. * @param {String} formatId The ID of the format to check. @@ -6475,16 +6560,20 @@ var CSSLint = (function(){ // normalize line endings lines = text.replace(/\n\r?/g, "$split$").split('$split$'); - + if (!ruleset){ ruleset = this.getRuleset(); } - + + if (embeddedRuleset.test(text)){ + ruleset = applyEmbeddedRuleset(text, ruleset); + } + reporter = new Reporter(lines, ruleset); - + ruleset.errors = 2; //always report parsing errors as errors for (i in ruleset){ - if(ruleset.hasOwnProperty(i)){ + if(ruleset.hasOwnProperty(i) && ruleset[i]){ if (rules[i]){ rules[i].init(parser, reporter); } @@ -6501,9 +6590,10 @@ var CSSLint = (function(){ report = { messages : reporter.messages, - stats : reporter.stats + stats : reporter.stats, + ruleset : reporter.ruleset }; - + //sort by line numbers, rollups at the bottom report.messages.sort(function (a, b){ if (a.rollup && !b.rollup){ @@ -6513,8 +6603,8 @@ var CSSLint = (function(){ } else { return a.line - b.line; } - }); - + }); + return report; }; @@ -6525,7 +6615,6 @@ var CSSLint = (function(){ return api; })(); - /*global CSSLint*/ /** * An instance of Report is used to report results of the @@ -6923,6 +7012,72 @@ CSSLint.addRule({ } }); +/* + * Rule: Use the bulletproof @font-face syntax to avoid 404's in old IE + * (http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax) + */ +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "bulletproof-font-face", + name: "Use the bulletproof @font-face syntax", + desc: "Use the bulletproof @font-face syntax to avoid 404's in old IE (http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax).", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + count = 0, + fontFaceRule = false, + firstSrc = true, + ruleFailed = false, + line, col; + + // Mark the start of a @font-face declaration so we only test properties inside it + parser.addListener("startfontface", function(event){ + fontFaceRule = true; + }); + + parser.addListener("property", function(event){ + // If we aren't inside an @font-face declaration then just return + if (!fontFaceRule) { + return; + } + + var propertyName = event.property.toString().toLowerCase(), + value = event.value.toString(); + + // Set the line and col numbers for use in the endfontface listener + line = event.line; + col = event.col; + + // This is the property that we care about, we can ignore the rest + if (propertyName === 'src') { + var regex = /^\s?url\(['"].+\.eot\?.*['"]\)\s*format\(['"]embedded-opentype['"]\).*$/i; + + // We need to handle the advanced syntax with two src properties + if (!value.match(regex) && firstSrc) { + ruleFailed = true; + firstSrc = false; + } else if (value.match(regex) && !firstSrc) { + ruleFailed = false; + } + } + + + }); + + // Back to normal rules that we don't need to test + parser.addListener("endfontface", function(event){ + fontFaceRule = false; + + if (ruleFailed) { + reporter.report("@font-face declaration doesn't follow the fontspring bulletproof syntax.", line, col, rule); + } + }); + } +}); /* * Rule: Include all compatible vendor prefixes to reach a wider * range of users. @@ -6967,7 +7122,7 @@ CSSLint.addRule({ "border-end-style" : "webkit moz", "border-end-width" : "webkit moz", "border-image" : "webkit moz o", - "border-radius" : "webkit moz", + "border-radius" : "webkit", "border-start" : "webkit moz", "border-start-color" : "webkit moz", "border-start-style" : "webkit moz", @@ -7383,7 +7538,17 @@ CSSLint.addRule({ propertiesToCheck = { color: 1, background: 1, - "background-color": 1 + "border-color": 1, + "border-top-color": 1, + "border-right-color": 1, + "border-bottom-color": 1, + "border-left-color": 1, + border: 1, + "border-top": 1, + "border-right": 1, + "border-bottom": 1, + "border-left": 1, + "background-color": 1 }, properties; @@ -7556,14 +7721,13 @@ CSSLint.addRule({ moz: 0, webkit: 0, oldWebkit: 0, - ms: 0, o: 0 }; }); parser.addListener("property", function(event){ - if (/\-(moz|ms|o|webkit)(?:\-(?:linear|radial))\-gradient/i.test(event.value)){ + if (/\-(moz|o|webkit)(?:\-(?:linear|radial))\-gradient/i.test(event.value)){ gradients[RegExp.$1] = 1; } else if (/\-webkit\-gradient/i.test(event.value)){ gradients.oldWebkit = 1; @@ -7586,15 +7750,11 @@ CSSLint.addRule({ missing.push("Old Webkit (Safari 4+, Chrome)"); } - if (!gradients.ms){ - missing.push("Internet Explorer 10+"); - } - if (!gradients.o){ missing.push("Opera 11.1+"); } - if (missing.length && missing.length < 5){ + if (missing.length && missing.length < 4){ reporter.report("Missing vendor-prefixed CSS gradients for " + missing.join(", ") + ".", event.selectors[0].line, event.selectors[0].col, rule); } @@ -7987,6 +8147,62 @@ CSSLint.addRule({ }); } +}); +/* + * Rule: Warn people with approaching the IE 4095 limit + */ +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "selector-max-approaching", + name: "Warn when approaching the 4095 selector limit for IE", + desc: "Will warn when selector count is >= 3800 selectors.", + browsers: "IE", + + //initialization + init: function(parser, reporter) { + var rule = this, count = 0; + + parser.addListener('startrule', function(event) { + count += event.selectors.length; + }); + + parser.addListener("endstylesheet", function() { + if (count >= 3800) { + reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.",0,0,rule); + } + }); + } + +}); +/* + * Rule: Warn people past the IE 4095 limit + */ +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "selector-max", + name: "Error when past the 4095 selector limit for IE", + desc: "Will error when selector count is > 4095.", + browsers: "IE", + + //initialization + init: function(parser, reporter){ + var rule = this, count = 0; + + parser.addListener('startrule',function(event) { + count += event.selectors.length; + }); + + parser.addListener("endstylesheet", function() { + if (count > 4095) { + reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.",0,0,rule); + } + }); + } + }); /* * Rule: Use shorthand properties where possible. @@ -8511,9 +8727,480 @@ CSSLint.addRule({ }); /*global CSSLint*/ +(function() { + /** + * Replace special characters before write to output. + * + * Rules: + * - single quotes is the escape sequence for double-quotes + * - & is the escape sequence for & + * - < is the escape sequence for < + * - > is the escape sequence for > + * + * @param {String} message to escape + * @return escaped message as {String} + */ + var xmlEscape = function(str) { + if (!str || str.constructor !== String) { + return ""; + } + + return str.replace(/[\"&><]/g, function(match) { + switch (match) { + case "\"": + return """; + case "&": + return "&"; + case "<": + return "<"; + case ">": + return ">"; + } + }); + }; + + CSSLint.addFormatter({ + //format information + id: "checkstyle-xml", + name: "Checkstyle XML format", + + /** + * Return opening root XML tag. + * @return {String} to prepend before all results + */ + startFormat: function(){ + return ""; + }, + + /** + * Return closing root XML tag. + * @return {String} to append after all results + */ + endFormat: function(){ + return ""; + }, + + /** + * Returns message when there is a file read error. + * @param {String} filename The name of the file that caused the error. + * @param {String} message The error message + * @return {String} The error message. + */ + readError: function(filename, message) { + return ""; + }, + + /** + * Given CSS Lint results for a file, return output for this format. + * @param results {Object} with error and warning messages + * @param filename {String} relative file path + * @param options {Object} (UNUSED for now) specifies special handling of output + * @return {String} output for results + */ + formatResults: function(results, filename, options) { + var messages = results.messages, + output = []; + + /** + * Generate a source string for a rule. + * Checkstyle source strings usually resemble Java class names e.g + * net.csslint.SomeRuleName + * @param {Object} rule + * @return rule source as {String} + */ + var generateSource = function(rule) { + if (!rule || !('name' in rule)) { + return ""; + } + return 'net.csslint.' + rule.name.replace(/\s/g,''); + }; + + + + if (messages.length > 0) { + output.push(""); + CSSLint.Util.forEach(messages, function (message, i) { + //ignore rollups for now + if (!message.rollup) { + output.push(""); + } + }); + output.push(""); + } + + return output.join(""); + } + }); + +}()); +/*global CSSLint*/ +CSSLint.addFormatter({ + //format information + id: "compact", + name: "Compact, 'porcelain' format", + + /** + * Return content to be printed before all file results. + * @return {String} to prepend before all results + */ + startFormat: function() { + return ""; + }, + + /** + * Return content to be printed after all file results. + * @return {String} to append after all results + */ + endFormat: function() { + return ""; + }, + + /** + * Given CSS Lint results for a file, return output for this format. + * @param results {Object} with error and warning messages + * @param filename {String} relative file path + * @param options {Object} (Optional) specifies special handling of output + * @return {String} output for results + */ + formatResults: function(results, filename, options) { + var messages = results.messages, + output = ""; + options = options || {}; + + /** + * Capitalize and return given string. + * @param str {String} to capitalize + * @return {String} capitalized + */ + var capitalize = function(str) { + return str.charAt(0).toUpperCase() + str.slice(1); + }; + + if (messages.length === 0) { + return options.quiet ? "" : filename + ": Lint Free!"; + } + + CSSLint.Util.forEach(messages, function(message, i) { + if (message.rollup) { + output += filename + ": " + capitalize(message.type) + " - " + message.message + "\n"; + } else { + output += filename + ": " + "line " + message.line + + ", col " + message.col + ", " + capitalize(message.type) + " - " + message.message + "\n"; + } + }); + + return output; + } +}); +/*global CSSLint*/ +CSSLint.addFormatter({ + //format information + id: "csslint-xml", + name: "CSSLint XML format", + + /** + * Return opening root XML tag. + * @return {String} to prepend before all results + */ + startFormat: function(){ + return ""; + }, + + /** + * Return closing root XML tag. + * @return {String} to append after all results + */ + endFormat: function(){ + return ""; + }, + + /** + * Given CSS Lint results for a file, return output for this format. + * @param results {Object} with error and warning messages + * @param filename {String} relative file path + * @param options {Object} (UNUSED for now) specifies special handling of output + * @return {String} output for results + */ + formatResults: function(results, filename, options) { + var messages = results.messages, + output = []; + + /** + * Replace special characters before write to output. + * + * Rules: + * - single quotes is the escape sequence for double-quotes + * - & is the escape sequence for & + * - < is the escape sequence for < + * - > is the escape sequence for > + * + * @param {String} message to escape + * @return escaped message as {String} + */ + var escapeSpecialCharacters = function(str) { + if (!str || str.constructor !== String) { + return ""; + } + return str.replace(/\"/g, "'").replace(/&/g, "&").replace(//g, ">"); + }; + + if (messages.length > 0) { + output.push(""); + CSSLint.Util.forEach(messages, function (message, i) { + if (message.rollup) { + output.push(""); + } else { + output.push(""); + } + }); + output.push(""); + } + + return output.join(""); + } +}); +/*global CSSLint*/ +CSSLint.addFormatter({ + //format information + id: "junit-xml", + name: "JUNIT XML format", + + /** + * Return opening root XML tag. + * @return {String} to prepend before all results + */ + startFormat: function(){ + return ""; + }, + + /** + * Return closing root XML tag. + * @return {String} to append after all results + */ + endFormat: function() { + return ""; + }, + + /** + * Given CSS Lint results for a file, return output for this format. + * @param results {Object} with error and warning messages + * @param filename {String} relative file path + * @param options {Object} (UNUSED for now) specifies special handling of output + * @return {String} output for results + */ + formatResults: function(results, filename, options) { + + var messages = results.messages, + output = [], + tests = { + 'error': 0, + 'failure': 0 + }; + + /** + * Generate a source string for a rule. + * JUNIT source strings usually resemble Java class names e.g + * net.csslint.SomeRuleName + * @param {Object} rule + * @return rule source as {String} + */ + var generateSource = function(rule) { + if (!rule || !('name' in rule)) { + return ""; + } + return 'net.csslint.' + rule.name.replace(/\s/g,''); + }; + + /** + * Replace special characters before write to output. + * + * Rules: + * - single quotes is the escape sequence for double-quotes + * - < is the escape sequence for < + * - > is the escape sequence for > + * + * @param {String} message to escape + * @return escaped message as {String} + */ + var escapeSpecialCharacters = function(str) { + + if (!str || str.constructor !== String) { + return ""; + } + + return str.replace(/\"/g, "'").replace(//g, ">"); + + }; + + if (messages.length > 0) { + + messages.forEach(function (message, i) { + + // since junit has no warning class + // all issues as errors + var type = message.type === 'warning' ? 'error' : message.type; + + //ignore rollups for now + if (!message.rollup) { + + // build the test case seperately, once joined + // we'll add it to a custom array filtered by type + output.push(""); + output.push("<" + type + " message=\"" + escapeSpecialCharacters(message.message) + "\">"); + output.push(""); + + tests[type] += 1; + + } + + }); + + output.unshift(""); + output.push(""); + + } + + return output.join(""); + + } +}); +/*global CSSLint*/ +CSSLint.addFormatter({ + //format information + id: "lint-xml", + name: "Lint XML format", + + /** + * Return opening root XML tag. + * @return {String} to prepend before all results + */ + startFormat: function(){ + return ""; + }, + + /** + * Return closing root XML tag. + * @return {String} to append after all results + */ + endFormat: function(){ + return ""; + }, + + /** + * Given CSS Lint results for a file, return output for this format. + * @param results {Object} with error and warning messages + * @param filename {String} relative file path + * @param options {Object} (UNUSED for now) specifies special handling of output + * @return {String} output for results + */ + formatResults: function(results, filename, options) { + var messages = results.messages, + output = []; + + /** + * Replace special characters before write to output. + * + * Rules: + * - single quotes is the escape sequence for double-quotes + * - & is the escape sequence for & + * - < is the escape sequence for < + * - > is the escape sequence for > + * + * @param {String} message to escape + * @return escaped message as {String} + */ + var escapeSpecialCharacters = function(str) { + if (!str || str.constructor !== String) { + return ""; + } + return str.replace(/\"/g, "'").replace(/&/g, "&").replace(//g, ">"); + }; + + if (messages.length > 0) { + + output.push(""); + CSSLint.Util.forEach(messages, function (message, i) { + if (message.rollup) { + output.push(""); + } else { + output.push(""); + } + }); + output.push(""); + } + + return output.join(""); + } +}); +/*global CSSLint*/ +CSSLint.addFormatter({ + //format information + id: "text", + name: "Plain Text", + + /** + * Return content to be printed before all file results. + * @return {String} to prepend before all results + */ + startFormat: function() { + return ""; + }, + + /** + * Return content to be printed after all file results. + * @return {String} to append after all results + */ + endFormat: function() { + return ""; + }, + + /** + * Given CSS Lint results for a file, return output for this format. + * @param results {Object} with error and warning messages + * @param filename {String} relative file path + * @param options {Object} (Optional) specifies special handling of output + * @return {String} output for results + */ + formatResults: function(results, filename, options) { + var messages = results.messages, + output = ""; + options = options || {}; + + if (messages.length === 0) { + return options.quiet ? "" : "\n\ncsslint: No errors in " + filename + "."; + } + + output = "\n\ncsslint: There are " + messages.length + " problems in " + filename + "."; + var pos = filename.lastIndexOf("/"), + shortFilename = filename; + + if (pos === -1){ + pos = filename.lastIndexOf("\\"); + } + if (pos > -1){ + shortFilename = filename.substring(pos+1); + } + + CSSLint.Util.forEach(messages, function (message, i) { + output = output + "\n\n" + shortFilename; + if (message.rollup) { + output += "\n" + (i+1) + ": " + message.type; + output += "\n" + message.message; + } else { + output += "\n" + (i+1) + ": " + message.type + " at line " + message.line + ", col " + message.col; + output += "\n" + message.message; + output += "\n" + message.evidence; + } + }); + + return output; + } +}); exports.CSSLint = CSSLint; - -}); +}); \ No newline at end of file diff --git a/lib/ace/mode/lua.js b/lib/ace/mode/lua.js index 47325600..2ef9031a 100644 --- a/lib/ace/mode/lua.js +++ b/lib/ace/mode/lua.js @@ -37,6 +37,7 @@ var Tokenizer = require("../tokenizer").Tokenizer; var LuaHighlightRules = require("./lua_highlight_rules").LuaHighlightRules; var LuaFoldMode = require("./folding/lua").FoldMode; var Range = require("../range").Range; +var WorkerClient = require("../worker/worker_client").WorkerClient; var Mode = function() { this.$tokenizer = new Tokenizer(new LuaHighlightRules().getRules()); @@ -143,6 +144,21 @@ oop.inherits(Mode, TextMode); session.outdentRows(new Range(row, 0, row + 2, 0)); }; + this.createWorker = function(session) { + var worker = new WorkerClient(["ace"], "ace/mode/lua_worker", "Worker"); + worker.attachToDocument(session.getDocument()); + + worker.on("error", function(e) { + session.setAnnotations([e.data]); + }); + + worker.on("ok", function(e) { + session.clearAnnotations(); + }); + + return worker; + }; + }).call(Mode.prototype); exports.Mode = Mode; diff --git a/lib/ace/mode/lua/luaparse.js b/lib/ace/mode/lua/luaparse.js new file mode 100644 index 00000000..c1c780da --- /dev/null +++ b/lib/ace/mode/lua/luaparse.js @@ -0,0 +1,1680 @@ +define(function(require, exports, module) { +/*global exports:true require:true define:true console:true */ + +(function (root, name, factory) { + 'use strict'; + + if (typeof exports !== 'undefined') { + factory(exports); + } else if (typeof define === 'function' && define.amd) { + define(['exports'], factory); + } else { + factory((root[name] = {})); + } +}(this, 'luaparse', function (exports) { + 'use strict'; + + exports.version = '0.0.1'; + + var input, options, length; + + var defaultOptions = exports.defaultOptions = { + // Explicitly tell the parser when the input ends. + wait: false + // Store comments as an array in the chunk object. + , comments: true + }; + + // The available tokens expressed as enum flags so they can be checked with + // bitwise operations. + + var EOF = 1, StringLiteral = 2, Keyword = 4, Identifier = 8 + , NumericLiteral = 16, Punctuator = 32, BooleanLiteral = 64 + , NilLiteral = 128; + + // As this parser is a bit different from luas own, the error messages + // will be different in some situations. + + var errors = exports.errors = { + unexpected: 'Unexpected %1 \'%2\' near \'%3\'' + , expected: '\'%1\' expected near \'%2\'' + , expectedToken: '%1 expected near \'%2\'' + , unfinishedString: 'unfinished string near \'%1\'' + , malformedNumber: 'malformed number near \'%1\'' + }; + + // ### Abstract Syntax Tree + // + // The default AST structure is inspired by the Mozilla Parser API but can + // easily be customized by overriding these functions. + + var ast = exports.ast = { + labelStatement: function(label) { + return { + type: 'LabelStatement' + , label: label + }; + } + + , breakStatement: function() { + return { + type: 'BreakStatement' + }; + } + + , gotoStatement: function(label) { + return { + type: 'GotoStatement' + , label: label + }; + } + + , returnStatement: function(args) { + return { + type: 'ReturnStatement' + , 'arguments': args + }; + } + + , ifStatement: function(clauses) { + return { + type: 'IfStatement' + , clauses: clauses + }; + } + , elseifClause: function(condition, body) { + return { + type: 'ElseifClause' + , condition: condition + , body: body + }; + } + , elseClause: function(body) { + return { + type: 'ElseClause' + , body: body + }; + } + + , whileStatement: function(condition, body) { + return { + type: 'WhileStatement' + , condition: condition + , body: body + }; + } + + , doStatement: function(body) { + return { + type: 'DoStatement' + , body: body + }; + } + + , repeatStatement: function(condition, body) { + return { + type: 'RepeatStatement' + , condition: condition + , body: body + }; + } + + , localStatement: function(variables, init) { + return { + type: 'LocalStatement' + , variables: variables + , init: init + }; + } + + , assignmentStatement: function(variables, init) { + return { + type: 'AssignmentStatement' + , variables: variables + , init: init + }; + } + + , callStatement: function(expression) { + return { + type: 'CallStatement' + , expression: expression + }; + } + + , functionStatement: function(identifier, parameters, isVararg, isLocal, body) { + return { + type: 'FunctionDeclaration' + , identifier: identifier + , vararg: isVararg + , local: isLocal + , parameters: parameters + , body: body + }; + } + + , forNumericStatement: function(variable, start, end, step, body) { + return { + type: 'ForNumericStatement' + , variable: variable + , start: start + , end: end + , step: step + , body: body + }; + } + + , forGenericStatement: function(variables, iterators, body) { + return { + type: 'ForGenericStatement' + , variables: variables + , iterators: iterators + , body: body + }; + } + + , chunk: function(body) { + return { + type: 'Chunk' + , body: body + }; + } + + , identifier: function(name) { + return { + type: 'Identifier' + , name: name + }; + } + + , literal: function(value, raw) { + return { + type: 'Literal' + , value: value + , raw: raw + }; + } + , varargLiteral: function() { + return { + type: 'VarargLiteral' + }; + } + + , tableKey: function(key, value) { + return { + type: 'TableKey' + , key: key + , value: value + }; + } + , tableKeyString: function(key, value) { + return { + type: 'TableKeyString' + , key: key + , value: value + }; + } + , tableValue: function(value) { + return { + type: 'TableValue' + , value: value + }; + } + + + , tableConstructorExpression: function(fields) { + return { + type: 'TableConstructorExpression' + , fields: fields + }; + } + , binaryExpression: function(operator, left, right) { + var type = ('and' === operator || 'or' === operator) ? + 'LogicalExpression' : + 'BinaryExpression'; + + return { + type: type + , operator: operator + , left: left + , right: right + }; + } + , unaryExpression: function(operator, argument) { + return { + type: 'UnaryExpression' + , operator: operator + , argument: argument + }; + } + , memberExpression: function(base, indexer, identifier) { + return { + type: 'MemberExpression' + , indexer: indexer + , identifier: identifier + , base: base + }; + } + + , indexExpression: function(base, index) { + return { + type: 'IndexExpression' + , base: base + , index: index + }; + } + + , callExpression: function(base, args) { + return { + type: 'CallExpression' + , base: base + , 'arguments': args + }; + } + + , tableCallExpression: function(base, args) { + return { + type: 'TableCallExpression' + , base: base + , 'arguments': args + }; + } + + , stringCallExpression: function(base, argument) { + return { + type: 'StringCallExpression' + , base: base + , argument: argument + }; + } + }; + + // Helpers + // ------- + + var slice = Array.prototype.slice + , toString = Object.prototype.toString; + + // A sprintf implementation using %index (beginning at 1) to input + // arguments in the format string. + // + // Example: + // + // // Unexpected function in token + // sprintf('Unexpected %2 in %1.', 'token', 'function'); + + function sprintf(format) { + var args = slice.call(arguments, 1); + format = format.replace(/%(\d)/g, function (match, index) { + match = ''; // jshint + return '' + args[index - 1] || ''; + }); + return format; + } + + // Returns a new object with the properties from all objectes passed as + // arguments. Last argument takes precedence. + // + // Example: + // + // this.options = extend(options, { output: false }); + + function extend() { + var args = slice.call(arguments) + , dest = {} + , src, prop; + + for (var i = 0, l = args.length; i < l; i++) { + src = args[i]; + for (prop in src) if (src.hasOwnProperty(prop)) { + dest[prop] = src[prop]; + } + } + return dest; + } + + // ### Error functions + + // #### Raise an exception. + // + // Raise an exception by passing a token, a string format and its paramters. + // + // The passed tokens location will automatically be added to the error + // message if it exists, if not it will default to the lexers current + // position. + // + // Example: + // + // // [1:0] expected [ near ( + // raise(token, "expected %1 near %2", '[', token.value); + + function raise(token) { + var message = sprintf.apply(null, slice.call(arguments, 1)) + , error, col; + + if ('undefined' !== typeof token.line) { + col = token.range[0] - token.lineStart; + error = new SyntaxError(sprintf('[%1:%2] %3', token.line, col, message)); + error.line = token.line; + error.index = token.range[0]; + error.column = col; + } else { + col = index - lineStart + 1; + error = new SyntaxError(sprintf('[%1:%2] %3', line, col, message)); + error.index = index; + error.line = line; + error.column = col; + } + throw error; + } + + // #### Raise an unexpected token error. + // + // Example: + // + // // expected near '0' + // raiseUnexpectedToken('', token); + + function raiseUnexpectedToken(type, token) { + raise(token, errors.expectedToken, type, token.value); + } + + // #### Raise a general unexpected error + // + // Usage should pass either a token object or a symbol string which was + // expected. We can also specify a nearby token such as , this will + // default to the currently active token. + // + // Example: + // + // // Unexpected symbol 'end' near '' + // unexpected(token); + // + // If there's no token in the buffer it means we have reached . + + function unexpected(found, near) { + if ('undefined' === typeof near) near = lookahead.value; + if ('undefined' !== typeof found.type) { + var type; + switch (found.type) { + case StringLiteral: type = 'string'; break; + case Keyword: type = 'keyword'; break; + case Identifier: type = 'identifier'; break; + case NumericLiteral: type = 'number'; break; + case Punctuator: type = 'symbol'; break; + case BooleanLiteral: type = 'boolean'; break; + case NilLiteral: + return raise(found, errors.unexpected, 'symbol', 'nil', near); + } + return raise(found, errors.unexpected, type, found.value, near); + } + return raise(found, errors.unexpected, 'symbol', found, near); + } + + // Lexer + // ----- + // + // The lexer, or the tokenizer reads the input string character by character + // and derives a token left-right. To be as efficient as possible the lexer + // prioritizes the common cases such as identifiers. It also works with + // character codes instead of characters as string comparisons was the + // biggest bottleneck of the parser. + // + // If `options.comments` is enabled, all comments encountered will be stored + // in an array which later will be appended to the chunk object. If disabled, + // they will simply be disregarded. + // + // When the lexer has derived a valid token, it will be returned as an object + // containing its value and as well as its position in the input string (this + // is always enabled to provide proper debug messages). + // + // `readToken()` starts lexing and returns the following token in the stream. + + var index + , token + , lookahead + , comments + , tokenStart + , line + , lineStart; + + function readToken() { + skipWhiteSpace(); + + // Skip comments beginning with -- + while (45 === input.charCodeAt(index) && + 45 === input.charCodeAt(index + 1)) { + scanComment(); + skipWhiteSpace(); + } + if (index >= length) return { + type : EOF + , value: '' + , line: line + , lineStart: lineStart + , range: [index, index] + }; + + var char = input.charCodeAt(index) + , next = input.charCodeAt(index + 1); + + // Memorize the range index where the token begins. + tokenStart = index; + if (isIdentifierStart(char)) return scanIdentifierOrKeyword(); + + switch (char) { + case 39: case 34: // '" + return scanStringLiteral(); + + // 0-9 + case 48: case 49: case 50: case 51: case 52: case 53: + case 54: case 55: case 56: case 57: + return scanNumericLiteral(); + + case 46: // . + // If the dot is followed by a digit it's a float. + if (isDecDigit(next)) return scanNumericLiteral(); + if (46 === next) { + if (46 === input.charCodeAt(index + 2)) return scanPunctuator('...'); + return scanPunctuator('..'); + } + return scanPunctuator('.'); + + case 61: // = + if (61 === next) return scanPunctuator('=='); + return scanPunctuator('='); + + case 62: // > + if (61 === next) return scanPunctuator('>='); + return scanPunctuator('>'); + + case 60: // < + if (61 === next) return scanPunctuator('<='); + return scanPunctuator('<'); + + case 126: // ~ + if (61 === next) return scanPunctuator('~='); + return raise({}, errors.expected, '=', '~'); + + case 58: // : + if (58 === next) return scanPunctuator('::'); + return scanPunctuator(':'); + + case 91: // [ + // Check for a multiline string, they begin with [= or [[ + if (91 === next || 61 === next) return scanLongStringLiteral(); + return scanPunctuator('['); + + // \* / ^ % , { } ] ( ) ; # - + + case 42: case 47: case 94: case 37: case 44: case 123: case 125: + case 93: case 40: case 41: case 59: case 35: case 45: case 43: + return scanPunctuator(input.charAt(index)); + } + + return unexpected(input.charAt(index)); + } + + // Whitespace has no semantic meaning in lua so simply skip ahead while + // tracking the encounted newlines. Newlines are also tracked in all + // token functions where multiline values are allowed. + + function skipWhiteSpace() { + while (index < length) { + var char = input.charCodeAt(index); + if (isWhiteSpace(char)) { + index++; + } else if (isLineTerminator(char)) { + line++; + lineStart = ++index; + } else { + break; + } + } + } + + // Identifiers, keywords, booleans and nil all look the same syntax wise. We + // simply go through them one by one and defaulting to an identifier if no + // previous case matched. + + function scanIdentifierOrKeyword() { + var value, type; + + // Slicing the input string is prefered before string concatenation in a + // loop for performance reasons. + while (isIdentifierPart(input.charCodeAt(++index))); + value = input.slice(tokenStart, index); + + // Decide on the token type and possibly cast the value. + if (isKeyword(value)) { + type = Keyword; + } else if ('true' === value || 'false' === value) { + type = BooleanLiteral; + value = ('true' === value); + } else if ('nil' === value) { + type = NilLiteral; + value = null; + } else { + type = Identifier; + } + + return { + type: type + , value: value + , line: line + , lineStart: lineStart + , range: [tokenStart, index] + }; + } + + // Once a punctuator reaches this function it should already have been + // validated so we simply return it as a token. + + function scanPunctuator(value) { + index += value.length; + return { + type: Punctuator + , value: value + , line: line + , lineStart: lineStart + , range: [tokenStart, index] + }; + } + + // Find the string literal by matching the delimiter marks used. + + function scanStringLiteral() { + var delimiter = input.charCodeAt(index++) + , stringStart = index + , string = '' + , char; + + while (index < length) { + char = input.charCodeAt(index++); + if (delimiter === char) break; + if (92 === char) { // \ + string += input.slice(stringStart, index - 1) + readEscapeSequence(); + stringStart = index; + } + // EOF or `\n` terminates a string literal. If we haven't found the + // ending delimiter by now, raise an exception. + else if (index >= length || isLineTerminator(char)) { + string += input.slice(stringStart, index - 1); + raise({}, errors.unfinishedString, string + String.fromCharCode(char)); + } + } + string += input.slice(stringStart, index - 1); + + return { + type: StringLiteral + , value: string + , line: line + , lineStart: lineStart + , range: [tokenStart, index] + }; + } + + // Expect a multiline string literal and return it as a regular string + // literal, if it doesn't validate into a valid multiline string, throw an + // exception. + + function scanLongStringLiteral() { + var string = readLongString(); + // Fail if it's not a multiline literal. + if (false === string) raise(token, errors.expected, '[', token.value); + + return { + type: StringLiteral + , value: string + , line: line + , lineStart: lineStart + , range: [tokenStart, index] + }; + } + + // Numeric literals will be returned as floating-point numbers instead of + // strings. The raw value should be retrieved from slicing the input string + // later on in the process. + // + // If a hexadecimal number is encountered, it will be converted. + + function scanNumericLiteral() { + var char = input.charAt(index) + , next = input.charAt(index + 1); + + var value = ('0' === char && ~'xX'.indexOf(next || null)) ? + readHexLiteral() : readDecLiteral(); + + return { + type: NumericLiteral + , value: value + , line: line + , lineStart: lineStart + , range: [tokenStart, index] + }; + } + + // Lua hexadecimals have an optional fraction part and an optional binary + // exoponent part. These are not included in JavaScript so we will compute + // all three parts separately and then sum them up at the end of the function + // with the following algorithm. + // + // Digit := toDec(digit) + // Fraction := toDec(fraction) / 16 ^ fractionCount + // BinaryExp := 2 ^ binaryExp + // Number := ( Digit + Fraction ) * BinaryExp + + function readHexLiteral() { + var fraction = 0 // defaults to 0 as it gets summed + , binaryExponent = 1 // defaults to 1 as it gets multiplied + , binarySign = 1 // positive + , digit, fractionStart, exponentStart, digitStart; + + digitStart = index += 2; // Skip 0x part + + // A minimum of one hex digit is required. + if (!isHexDigit(input.charCodeAt(index))) + raise({}, errors.malformedNumber, input.slice(tokenStart, index)); + + while (isHexDigit(input.charCodeAt(index))) index++; + // Convert the hexadecimal digit to base 10. + digit = parseInt(input.slice(digitStart, index), 16); + + // Fraction part i optional. + if ('.' === input.charAt(index)) { + fractionStart = ++index; + + while (isHexDigit(input.charCodeAt(index))) index++; + fraction = input.slice(fractionStart, index); + + // Empty fraction parts should default to 0, others should be converted + // 0.x form so we can use summation at the end. + fraction = (fractionStart === index) ? 0 + : parseInt(fraction, 16) / Math.pow(16, index - fractionStart); + } + + // Binary exponents are optional + if (~'pP'.indexOf(input.charAt(index) || null)) { + index++; + + // Sign part is optional and defaults to 1 (positive). + if (~'+-'.indexOf(input.charAt(index) || null)) + binarySign = ('+' === input.charAt(index++)) ? 1 : -1; + + exponentStart = index; + + // The binary exponent sign requires a decimal digit. + if (!isDecDigit(input.charCodeAt(index))) + raise({}, errors.malformedNumber, input.slice(tokenStart, index)); + + while (isDecDigit(input.charCodeAt(index))) index++; + binaryExponent = input.slice(exponentStart, index); + + // Calculate the binary exponent of the number. + binaryExponent = Math.pow(2, binaryExponent * binarySign); + } + + return (digit + fraction) * binaryExponent; + } + + // Decimal numbers are exactly the same in Lua and in JavaScript, because of + // this we check where the token ends and then parse it with native + // functions. + + function readDecLiteral() { + while (isDecDigit(input.charCodeAt(index))) index++; + // Fraction part is optional + if ('.' === input.charAt(index)) { + index++; + // Fraction part defaults to 0 + while (isDecDigit(input.charCodeAt(index))) index++; + } + // Exponent part is optional. + if (~'eE'.indexOf(input.charAt(index) || null)) { + index++; + // Sign part is optional. + if (~'+-'.indexOf(input.charAt(index) || null)) index++; + // An exponent is required to contain at least one decimal digit. + if (!isDecDigit(input.charCodeAt(index))) + raise({}, errors.malformedNumber, input.slice(tokenStart, index)); + + while (isDecDigit(input.charCodeAt(index))) index++; + } + + return parseFloat(input.slice(tokenStart, index)); + } + + + // Translate escape sequences to the actual characters. + + function readEscapeSequence() { + var sequenceStart = index; + switch (input.charAt(index)) { + // Lua allow the following escape sequences. + // We don't escape the bell sequence. + case 'n': index++; return '\n'; + case 'r': index++; return '\r'; + case 't': index++; return '\t'; + case 'v': index++; return '\v'; + case 'b': index++; return '\b'; + case 'f': index++; return '\f'; + // Skips the following span of white-space. + case 'z': index++; skipWhiteSpace(); return ''; + // Byte representation should for now be returned as is. + case 'x': + // \xXX, where XX is a sequence of exactly two hexadecimal digits + if (isHexDigit(input.charCodeAt(index + 1)) && + isHexDigit(input.charCodeAt(index + 2))) { + index += 3; + // Return it as is, without translating the byte. + return '\\' + input.slice(sequenceStart, index); + } + return '\\' + input.charAt(index++); + default: + // \ddd, where ddd is a sequence of up to three decimal digits. + if (isDecDigit(input.charCodeAt(index))) { + while (isDecDigit(input.charCodeAt(++index))); + return '\\' + input.slice(sequenceStart, index); + } + // Simply return the \ as is, it's not escaping any sequence. + return input.charAt(index++); + } + } + + // Comments begin with -- after which it will be decided if they are + // multiline comments or not. + // + // The multiline functionality works the exact same way as with string + // literals so we reuse the functionality. + + function scanComment() { + tokenStart = index; + index += 2; // -- + + var char = input.charAt(index) + , content = '' + , isLong = false + , commentStart = index; + + if ('[' === char) { + content = readLongString(); + // This wasn't a multiline comment after all. + if (false === content) content = char; + else { + isLong = true; + index += 2; // Trailing -- + } + } + // Scan until next line as long as it's not a multiline comment. + if (!isLong) { + while (index < length) { + if (isLineTerminator(input.charCodeAt(index))) break; + index++; + } + content = input.slice(commentStart, index); + } + + if (options.comments) { + comments.push({ + type: 'Comment' + , value: content + , raw: input.slice(tokenStart, index) + }); + } + } + + // Read a multiline string by calculating the depth of `=` characters and + // then appending until an equal depth is found. + + function readLongString() { + var level = 0 + , content = '' + , terminator = false + , char, stringStart; + + index++; // [ + + // Calculate the depth of the comment. + while ('=' === input.charAt(index + level)) level++; + // Exit, this is not a long string afterall. + if ('[' !== input.charAt(index + level)) return false; + + index += level + 1; + + // If the first character is a newline, ignore it and begin on next line. + if (isLineTerminator(input.charCodeAt(index))) { + line++; + lineStart = index++; + } + + stringStart = index; + while (index < length) { + char = input.charAt(index++); + + // We have to keep track of newlines as `skipWhiteSpace()` does not get + // to scan this part. + if (isLineTerminator(char.charCodeAt(0))) { + line++; + lineStart = index; + } + + // Once the delimiter is found, iterate through the depth count and see + // if it matches. + + if (']' === char) { + terminator = true; + for (var i = 0; i < level; i++) { + if ('=' !== input.charAt(index + i)) terminator = false; + } + if (']' !== input.charAt(index + level)) terminator = false; + } + + // We reached the end of the multiline string. Get out now. + if (terminator) break; + + if ('\\' === char) { + content += input.slice(stringStart, index - 1) + readEscapeSequence(); + stringStart = index; + } + } + content += input.slice(stringStart, index - 1); + index += level + 1; + + return content; + } + + // ## Lex functions and helpers. + + // Read the next token. + // + // This is actually done by setting the current token to the lookahead and + // reading in the new lookahead token. + + function next() { + token = lookahead; + lookahead = readToken(); + } + + // Consume a token if its value matches. Once consumed or not, return the + // success of the operation. + + function consume(value) { + if (value === token.value) { + next(); + return true; + } + return false; + } + + // Check if the given expression exists and raise an exception if not. + // + // As expressions can return null due to the design of the parser, we often + // need this strict expression check as well. + + function expectExpression(expression) { + if (null == expression) raiseUnexpectedToken('', token); + else return expression; + } + + // Expect the next token value to match. If not, throw an exception. + + function expect(value) { + if (value === token.value) next(); + else raise(token, errors.expected, value, token.value); + } + + // ### Validation functions + + function isWhiteSpace(char) { + return 9 === char || 32 === char || 0xB === char || 0xC === char; + } + + function isLineTerminator(char) { + return 10 === char || 13 === char; + } + + function isDecDigit(char) { + return char >= 48 && char <= 57; + } + + function isHexDigit(char) { + return (char >= 48 && char <= 57) || (char >= 97 && char <= 102) || (char >= 65 && char <= 70); + } + + // From [Lua 5.2](http://www.lua.org/manual/5.2/manual.html#8.1) onwards + // identifiers cannot use locale-dependet letters. + + function isIdentifierStart(char) { + return (char >= 65 && char <= 90) || (char >= 97 && char <= 122) || 95 === char; + } + + function isIdentifierPart(char) { + return (char >= 65 && char <= 90) || (char >= 97 && char <= 122) || 95 === char || (char >= 48 && char <= 57); + } + + // [3.1 Lexical Conventions](http://www.lua.org/manual/5.2/manual.html#3.1) + // + // `true`, `false` and `nil` will not be considered keywords, but literals. + + function isKeyword(id) { + switch (id.length) { + case 2: + return 'do' === id || 'if' === id || 'in' === id || 'or' === id; + case 3: + return 'and' === id || 'end' === id || 'for' === id || 'not' === id; + case 4: + return 'else' === id || 'goto' === id || 'then' === id; + case 5: + return 'break' === id || 'local' === id || 'until' === id || 'while' === id; + case 6: + return 'elseif' === id || 'repeat' === id || 'return' === id; + case 8: + return 'function' === id; + } + return false; + } + + function isUnary(token) { + if (Punctuator === token.type) return ~'#-'.indexOf(token.value); + if (Keyword === token.type) return 'not' === token.value; + return false; + } + + // @TODO this needs to be rethought. + function isCallExpression(expression) { + switch (expression.type) { + case 'CallExpression': + case 'TableCallExpression': + case 'StringCallExpression': + return true; + } + return false; + } + + // Check if the token syntactically closes a block. + + function isBlockFollow(token) { + if (EOF === token.type) return true; + if (Keyword !== token.type) return false; + switch (token.value) { + case 'else': case 'elseif': + case 'end': case 'until': + return true; + default: + return false; + } + } + + // Parse functions + // --------------- + + // Chunk is the main program object. Syntactically it's the same as a block. + // + // chunk ::= block + + function parseChunk() { + next(); + var body = parseBlock(); + if (EOF !== token.type) unexpected(token); + return ast.chunk(body); + } + + // A block contains a list of statements with an optional return statement + // as its last statement. + // + // block ::= {stat} [retstat] + + function parseBlock(terminator) { + var block = [] + , statement; + + while (!isBlockFollow(token)) { + // Return has to be the last statement in a block. + if ('return' === token.value) { + block.push(parseStatement()); + break; + } + statement = parseStatement(); + // Statements are only added if they are returned, this allows us to + // ignore some statements, such as EmptyStatement. + if (statement) block.push(statement); + } + // Doesn't really need an ast node + return block; + } + + // There are two types of statements, simple and compound. + // + // statement ::= break | goto | do | while | repeat | return + // | if | for | function | local | label | assignment + // | functioncall | ';' + + function parseStatement() { + if (Keyword === token.type) { + switch (token.value) { + case 'local': next(); return parseLocalStatement(); + case 'if': next(); return parseIfStatement(); + case 'return': next(); return parseReturnStatement(); + case 'function': next(); + var name = parseFunctionName(); + return parseFunctionDeclaration(name); + case 'while': next(); return parseWhileStatement(); + case 'for': next(); return parseForStatement(); + case 'repeat': next(); return parseRepeatStatement(); + case 'break': next(); return parseBreakStatement(); + case 'do': next(); return parseDoStatement(); + case 'goto': next(); return parseGotoStatement(); + } + } + + if (Punctuator === token.type) { + if (consume('::')) return parseLabelStatement(); + } + + // When a `;` is encounted, simply eat it without storing it. + if (consume(';')) return; + + return parseAssignmentOrCallStatement(); + } + + // ## Statements + + // label ::= '::' Name '::' + + function parseLabelStatement() { + var label = parseIdentifier(); + expect('::'); + return ast.labelStatement(label); + } + + // break ::= 'break' + + function parseBreakStatement() { + return ast.breakStatement(); + } + + // goto ::= 'goto' Name + + function parseGotoStatement() { + var label = parseIdentifier(); + return ast.gotoStatement(label); + } + + // do ::= 'do' block 'end' + + function parseDoStatement() { + var body = parseBlock(); + expect('end'); + return ast.doStatement(body); + } + + // while ::= 'while' exp 'do' block 'end' + + function parseWhileStatement() { + var condition = parseExpression(); + expect('do'); + var body = parseBlock(); + expect('end'); + return ast.whileStatement(condition, body); + } + + // repeat ::= 'repeat' block 'until' exp + + function parseRepeatStatement() { + var body = parseBlock(); + expect('until'); + var condition = expectExpression(parseExpression()); + return ast.repeatStatement(condition, body); + } + + // retstat ::= 'return' [exp {',' exp}] [';'] + + function parseReturnStatement() { + var expressions = []; + + if ('end' !== token.value) { + var expression = parseExpression(); + if (null != expression) expressions.push(expression); + while (consume(',')) { + expression = expectExpression(parseExpression()); + expressions.push(expression); + } + consume(';'); // grammar tells us ; is optional here. + } + return ast.returnStatement(expressions); + } + + // if ::= 'if' exp 'then' block {elif} ['else' block] 'end' + // elif ::= 'elseif' exp 'then' block + + function parseIfStatement() { + var clauses = [] + , condition + , body; + + do { + condition = parseExpression(); + expect('then'); + body = parseBlock(); + clauses.push(ast.elseifClause(condition, body)); + } while (consume('elseif')); + + if (consume('else')) { + body = parseBlock(); + clauses.push(ast.elseClause(body)); + } + + expect('end'); + return ast.ifStatement(clauses); + } + + // There are two types of for statements, generic and numeric. + // + // for ::= Name '=' exp ',' exp [',' exp] 'do' block 'end' + // for ::= namelist 'in' explist 'do' block 'end' + // namelist ::= Name {',' Name} + // explist ::= exp {',' exp} + + function parseForStatement() { + var variable = parseIdentifier() + , body; + + // If the first expression is followed by a `=` punctuator, this is a + // Numeric For Statement. + if (consume('=')) { + // Start expression + var start = expectExpression(parseExpression()); + expect(','); + // End expression + var end = expectExpression(parseExpression()); + // Optional step expression + var step = consume(',') ? expectExpression(parseExpression()) : null; + + expect('do'); + body = parseBlock(); + expect('end'); + + return ast.forNumericStatement(variable, start, end, step, body); + + // If not, it's a Generic For Statement + } else { + // The namelist can contain one or more identifiers. + var variables = [variable]; + while (consume(',')) variables.push(parseIdentifier()); + expect('in'); + var iterators = []; + + // One or more expressions in the explist. + do { + var expression = expectExpression(parseExpression()); + iterators.push(expression); + } while (consume(',')); + + expect('do'); + body = parseBlock(); + expect('end'); + + return ast.forGenericStatement(variables, iterators, body); + } + } + + // Local statements can either be variable assignments or function + // definitions. If a function definition is found, it will be delegated to + // `parseFunctionDeclaration()` with the isLocal flag. + // + // This AST structure might change into a local assignment with a function + // child. + // + // local ::= 'local' 'function' Name funcdecl + // | 'local' Name {',' Name} ['=' exp {',' exp} + + function parseLocalStatement() { + if (Identifier === token.type) { + var variables = []; + var init = []; + + do { + variables.push(parseIdentifier()); + } while (consume(',')); + + if (consume('=')) { + do { + var expression = expectExpression(parseExpression()); + init.push(expression); + } while (consume(',')); + } + + return ast.localStatement(variables, init); + } + if (consume('function')) { + // MemberExpressions are not allowed in local function statements. + var name = parseIdentifier(); + return parseFunctionDeclaration(name, true); + } else { + raiseUnexpectedToken('', token); + } + } + + // assignment ::= varlist '=' explist + // varlist ::= prefixexp {',' prefixexp} + // explist ::= exp {',' exp} + // + // call ::= callexp + // callexp ::= prefixexp args | prefixexp ':' Name args + + function parseAssignmentOrCallStatement() { + // Keep a reference to the previous token for better error messages in case + // of invalid statement + var previous = token + , expression = parsePrefixExpression(); + + if (null == expression) return unexpected(token); + if (~',='.indexOf(token.value)) { + var variables = [expression] + , init = [] + , exp; + + while (consume(',')) { + exp = expectExpression(parsePrefixExpression()); + variables.push(exp); + } + expect('='); + do { + exp = expectExpression(parseExpression()); + init.push(exp); + } while (consume(',')); + return ast.assignmentStatement(variables, init); + } + if (isCallExpression(expression)) { + return ast.callStatement(expression); + } + // The prefix expression was neither part of an assignment or a + // callstatement, however as it was valid it's been consumed, so raise + // the exception on the previous token to provide a helpful message. + return unexpected(previous); + } + + + + // ### Non-statements + + // Identifier ::= Name + + function parseIdentifier() { + var identifier = token.value; + if (Identifier !== token.type) raiseUnexpectedToken('', token); + next(); + return ast.identifier(identifier); + } + + + // Parse the functions parameters and body block. The name should already + // have been parsed and passed to this declaration function. By separating + // this we allow for anonymous functions in expressions. + // + // For local functions there's a boolean parameter which needs to be set + // when parsing the declaration. + // + // funcdecl ::= '(' [parlist] ')' block 'end' + // parlist ::= Name {',' Name} | [',' '...'] | '...' + + function parseFunctionDeclaration(name, isLocal) { + var isVararg = false; + var parameters = []; + expect('('); + + if (consume('...')) isVararg = true; + else if (Identifier === token.type) { + do { + if (consume('...')) { + isVararg = true; + break; + } + parameters.push(parseIdentifier()); + } while (consume(',')); + } + if (isVararg) expect(')'); + else if (!consume(')')) raiseUnexpectedToken(' or \'...\'', token); + + var body = parseBlock(); + expect('end'); + + isLocal = isLocal || false; + return ast.functionStatement(name, parameters, isVararg, isLocal, body); + } + + // Parse the function name as identifiers and member expressions. + // + // Name {'.' Name} [':' Name] + + function parseFunctionName() { + var base = parseIdentifier(); + + while (consume('.')) { + base = ast.memberExpression(base, '.', parseIdentifier()); + } + + if (consume(':')) { + base = ast.memberExpression(base, ':', parseIdentifier()); + } + + return base; + } + + // tableconstructor ::= '{' [fieldlist] '}' + // fieldlist ::= field {fieldsep field} fieldsep + // field ::= '[' exp ']' '=' exp | Name = 'exp' | exp + // + // fieldsep ::= ',' | ';' + + function parseTableConstructor() { + var fields = [] + , key, value; + + while (true) { + if (Punctuator === token.type && consume('[')) { + key = parseExpression(); + expect(']'); + expect('='); + value = expectExpression(parseExpression()); + fields.push(ast.tableKey(key, value)); + } else if (Identifier === token.type) { + key = parseExpression(); + if (consume('=')) { + value = parseExpression(); + fields.push(ast.tableKeyString(key, value)); + } else { + fields.push(ast.tableValue(key)); + } + } else { + if (null == (value = parseExpression())) break; + fields.push(ast.tableValue(value)); + } + if (~',;'.indexOf(token.value)) { + next(); + continue; + } + if ('}' === token.value) break; + } + expect('}'); + return ast.tableConstructorExpression(fields); + } + + // Expression parser + // ----------------- + // + // Expressions are evaluated and always return a value. + // + // exp ::= (unop exp | primary | prefixexp ) { binop exp } + // + // primary ::= nil | false | true | Number | String | '...' + // | functiondef | tableconstructor + // + // prefixexp ::= (Name | '(' exp ')' ) { '[' exp ']' + // | '.' Name | ':' Name args | args } + // + + function parseExpression() { + var expression = parseSubExpression(0); + return expression; + } + + // Return the precedence priority of the operator. + // + // As unary `-` can't be distinguished from binary `-`, unary precedence + // isn't described in this table but in `parseSubExpression()` itself. + // + // As this function gets hit on every expression it's been optimized due to + // the expensive CompareICStub which took ~8% of the parse time. + + function binaryPrecedence(operator) { + var char = operator.charCodeAt(0) + , length = operator.length; + + if (1 === length) { + switch (char) { + case 94: return 10; // ^ + case 42: case 47: case 37: return 7; // * / % + case 43: case 45: return 6; // + - + case 60: case 62: return 3; // < > + } + } else if (2 === length) { + switch (char) { + case 46: return 5; // .. + case 60: case 62: case 61: case 126: return 3; // <= >= == ~= + case 111: return 1; // or + } + } else if (97 === char && 'and' === operator) return 2; + return 0; + } + + // Implement an operator-precedence parser to handle binary operator + // precedence. + // + // We use this algorithm because it's compact, it's fast and Lua core uses + // the same so we can be sure our expressions are parsed in the same manner + // without excessive amounts of tests. + // + // exp ::= (unop exp | primary | prefixexp ) { binop exp } + + function parseSubExpression(minPrecedence) { + var operator = token.value; + // The left-hand side in binary operations. + var expression; + + // UnaryExpression + if (isUnary(token)) { + next(); + var argument = expectExpression(parseSubExpression(8)); + expression = ast.unaryExpression(operator, argument); + } + if (null == expression) { + // PrimaryExpression + expression = parsePrimaryExpression(); + + // PrefixExpression + if (null == expression) { + expression = parsePrefixExpression(); + } + } + // This is not a valid left hand expression. + if (null == expression) return null; + + var precedence; + while (true) { + operator = token.value; + + precedence = (Punctuator === token.type || Keyword === token.type) ? + binaryPrecedence(operator) : 0; + + if (precedence === 0 || precedence <= minPrecedence) break; + // Right-hand precedence operators + if ('^' === operator || '..' === operator) precedence--; + next(); + var right = expectExpression(parseSubExpression(precedence)); + expression = ast.binaryExpression(operator, expression, right); + } + return expression; + } + + // prefixexp ::= prefix {suffix} + // prefix ::= Name | '(' exp ')' + // suffix ::= '[' exp ']' | '.' Name | ':' Name args | args + // + // args ::= '(' [explist] ')' | tableconstructor | String + + function parsePrefixExpression() { + var base; + + // The prefix + if (Identifier === token.type) { + base = parseIdentifier(); + } else if (consume('(')) { + base = parseExpression(); + expect(')'); + } else { + return null; + } + + // The suffix + var expression, identifier; + while (true) { + expectExpression(base); + if (Punctuator === token.type) { + switch (token.value) { + case '[': + next(); + expression = parseExpression(); + base = ast.indexExpression(base, expression); + expect(']'); + break; + case '.': + next(); + identifier = parseIdentifier(); + base = ast.memberExpression(base, '.', identifier); + break; + case ':': + next(); + identifier = parseIdentifier(); + base = ast.memberExpression(base, ':', identifier); + // Once a : is found, this has to be a callexpression, otherwise + // throw an error. + base = parseCallExpression(base); + break; + case '(': case '{': // args + base = parseCallExpression(base); + break; + default: + return base; + } + } else if (StringLiteral === token.type) { + base = parseCallExpression(base); + } else { + break; + } + } + + return base; + } + + // args ::= '(' [explist] ')' | tableconstructor | String + + function parseCallExpression(base) { + if (Punctuator === token.type) { + switch (token.value) { + case '(': + next(); + + // List of expressions + var expressions = []; + var expression = parseExpression(); + if (null != expression) expressions.push(expression); + while (consume(',')) { + expression = expectExpression(parseExpression()); + expressions.push(expression); + } + + expect(')'); + return ast.callExpression(base, expressions); + + case '{': + next(); + var table = parseTableConstructor(); + return ast.tableCallExpression(base, table); + } + + } else if (StringLiteral === token.type) { + var string = token.value; + next(); + return ast.stringCallExpression(base, string); + } + + raiseUnexpectedToken('function arguments', token); + } + + // primary ::= String | Numeric | nil | true | false + // | functiondef | tableconstructor | '...' + + function parsePrimaryExpression() { + var literals = StringLiteral | NumericLiteral | BooleanLiteral | NilLiteral + , value = token.value; + + if (token.type & literals) { + var raw = input.slice(token.range[0], token.range[1]); + next(); + return ast.literal(value, raw); + } else if (Keyword === token.type && 'function' === token.value) { + next(); + return parseFunctionDeclaration(null); + } else if (Punctuator === token.type) { + // Semantically dotsliteral can only exist within a vararg functions. + if (consume('...')) return ast.varargLiteral(value); + if (consume('{')) return parseTableConstructor(); + } + } + + // Parser + // ------ + + // Export the main parser. + // + // - `wait` Hold parsing until end() is called. Defaults to false + // + // Example: + // + // var parser = require('luaparser'); + // parser.parse('i = 0'); + + exports.parse = parse; + + function parse(_input, _options) { + if ('undefined' === typeof _options && 'object' === typeof _input) { + _options = _input; + _input = undefined; + } + if (!_options) _options = {}; + + input = _input || ''; + options = extend(defaultOptions, _options); + + // Rewind the lexer + index = 0; + line = 1; + lineStart = 0; + length = input.length; + + if (options.comments) comments = []; + if (!options.wait) return end(); + return exports; + } + + // Write to the source code buffer without beginning the parse. + exports.write = write; + + function write(_input) { + input += String(_input); + length = input.length; + return exports; + } + + // Send an EOF and begin parsing. + exports.end = end; + + function end(_input) { + if ('undefined' !== typeof _input) write(_input); + + length = input.length; + // Initialize with a lookahead token. + lookahead = readToken(); + + var chunk = parseChunk(); + if (options.comments) chunk.comments = comments; + return chunk; + } + + // Expose the lex function + exports.lex = readToken; + +})); +/* vim: set sw=2 ts=2 et tw=79 : */ + +}); \ No newline at end of file diff --git a/lib/ace/mode/lua_worker.js b/lib/ace/mode/lua_worker.js new file mode 100644 index 00000000..9c08f614 --- /dev/null +++ b/lib/ace/mode/lua_worker.js @@ -0,0 +1,71 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Distributed under the BSD license: + * + * Copyright (c) 2010, Ajax.org B.V. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Ajax.org B.V. nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ***** END LICENSE BLOCK ***** */ + +define(function(require, exports, module) { +"use strict"; + +var oop = require("../lib/oop"); +var Mirror = require("../worker/mirror").Mirror; +var luaparse = require("../mode/lua/luaparse"); + +var Worker = exports.Worker = function(sender) { + Mirror.call(this, sender); + this.setTimeout(500); +}; + +oop.inherits(Worker, Mirror); + +(function() { + + this.onUpdate = function() { + var value = this.doc.getValue(); + + // var t=Date.now() + try { + luaparse.parse(value); + } catch(e) { + if (e instanceof SyntaxError) { + this.sender.emit("error", { + row: e.line - 1, + column: e.column, + text: e.message, + type: "error" + }); + } + // console.log( t-Date.now()) + return; + } + // console.log( t-Date.now()) + this.sender.emit("ok"); + }; + +}).call(Worker.prototype); + +}); diff --git a/lib/ace/worker/worker_client.js b/lib/ace/worker/worker_client.js index ddd27050..81d83bf3 100644 --- a/lib/ace/worker/worker_client.js +++ b/lib/ace/worker/worker_client.js @@ -53,12 +53,12 @@ var WorkerClient = function(topLevelNamespaces, mod, classname) { // nameToUrl is renamed to toUrl in requirejs 2 if (require.nameToUrl && !require.toUrl) require.toUrl = require.nameToUrl; - workerUrl = normalizePath(require.toUrl("ace/worker/worker", null, "_")); + workerUrl = normalizePath(require.toUrl("ace/worker/worker.js", null, "_")); } var tlns = {}; topLevelNamespaces.forEach(function(ns) { - tlns[ns] = normalizePath(require.toUrl(ns, null, "_").replace(/.js(\?.*)?$/, "")); + tlns[ns] = normalizePath(require.toUrl(ns, null, "_").replace(/(\.js)?(\?.*)?$/, "")); }); } diff --git a/tool/update_deps.js b/tool/update_deps.js index 0f868dbc..0fc8caa4 100644 --- a/tool/update_deps.js +++ b/tool/update_deps.js @@ -23,6 +23,10 @@ var deps = [{ path: "../../demo/kitchen-sink/require.js", url: "https://raw.github.com/jrburke/requirejs/master/require.js", needsFixup: false +}, { + path: "mode/lua/luaparse.js", + url: "https://raw.github.com/oxyc/luaparse/master/lib/luaparse.js", + needsFixup: true }] var download = function(href, callback) { @@ -105,9 +109,8 @@ void function(){ if (!data) return if (x.name == "parser.js") { - console.log(data) data = data.replace("var parser = (function(){", "") - .replace(/\nreturn parser[\x00-\uffff]*$/, "\n\nmodule.exports = parser;\n\n") + .replace(/\nreturn (new Parser)[\s\S]*$/, "\n\nmodule.exports = $1;\n\n") } else { data = data.replace("(function() {", "") .replace(/}\).call\(this\);\s*$/, "")