Repository: mckamey/countdownjs Branch: master Commit: 1902fb23d21d Files: 35 Total size: 450.6 KB Directory structure: gitextract_1o3rx81s/ ├── .gitignore ├── LICENSE.txt ├── README.md ├── bower.json ├── build.xml ├── countdown.js ├── demo.html ├── demo.js ├── externs.js ├── index.html ├── lib/ │ ├── closure/ │ │ ├── COPYING │ │ ├── README │ │ ├── compiler.jar │ │ ├── docs.url │ │ └── download.url │ ├── jslint/ │ │ ├── download.url │ │ ├── jslint.js │ │ └── rhino.js │ ├── json/ │ │ └── json2.js │ ├── qunit/ │ │ ├── MIT-LICENSE.txt │ │ ├── download.url │ │ ├── qunit.css │ │ └── qunit.js │ ├── raf/ │ │ └── requestAnimationFrame.js │ └── rhino/ │ ├── LICENSE.txt │ ├── download.url │ └── js.jar ├── package.json ├── readme.html └── test/ ├── fonts/ │ └── SIL Open Font License 1.1.txt ├── formatTests.js ├── lint.js ├── styles.css ├── timespanTests.js └── unit.html ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ \.DS_Store \.classpath \.project \.settings \.iml \.ipr \.iws *\.bat *\.psd *\.ai *\.sh ga.js robots.txt antixss\.js countdown\.demo\.js ================================================ FILE: LICENSE.txt ================================================ The MIT License Copyright (c) 2006-2012 Stephen M. McKamey Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # [Countdown.js][1] A simple JavaScript API for producing an accurate, intuitive description of the timespan between two Date instances. ---- ## The Motivation While seemingly a trivial problem, the human descriptions for a span of time tend to be fuzzier than a computer naturally computes. More specifically, months are an inherently messed up unit of time. For instance, when a human says "in 1 month" how long do they mean? Banks often interpret this as *thirty days* but that is only correct one third of the time. People casually talk about a month being *four weeks long* but there is only one month in a year which is four weeks long and it is only that long about three quarters of the time. Even intuitively defining these terms can be problematic. For instance, what is the date one month after January 31st, 2001? JavaScript will happily call this March 3rd, 2001. Humans will typically debate either February 28th, 2001 or March 1st, 2001. It seems there isn't a "right" answer, per se. ## The Algorithm *Countdown.js* emphasizes producing intuitively correct description of timespans which are consistent as time goes on. To do this, *Countdown.js* uses the concept of "today's date next month" to mean "a month from now". As the days go by, *Countdown.js* produces consecutively increasing or decreasing counts without inconsistent jumps. The range of accuracy is only limited by the underlying system clock. *Countdown.js* approaches finding the difference between two times like an elementary school subtraction problem. Each unit acts like a base-10 place where any overflow is carried to the next highest unit, and any underflow is borrowed from the next highest unit. In base-10 subtraction, every column is worth 10 times the previous column. With time, it is a little more complex since the conversions between the units of time are not the same and months are an inconsistent number of days. Internally, *Countdown.js* maintains the concept of a "reference month" which determines how many days a given month or year represents. In the final step of the algorithm, *Countdown.js* then prunes the set of time units down to only those requested, forcing larger units down to smaller. ### Time Zones & Daylight Savings Time As of v2.4, *Countdown.js* performs all calculations with respect to the **viewer's local time zone**. Earlier versions performed difference calculations in UTC, which is generally the correct way to do math on time. In this situation, however, an issue with using UTC happens when either of the two dates being worked with is within one time zone offset of a month boundary. If the UTC interpretation of that instant in time is in a different month than that of the local time zone, then the viewer's perception is that the calculated time span is incorrect. This is the heart of the problem that *Countdown.js* attempts to solve: talking about spans of time can be ambiguous. Nearly all bugs reported for *Countdown.js* have been because the viewer expects something different due to their particular time zone. JavaScript ([ECMA-262](http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.7)) only works with dates as UTC or the local time zone, not arbitrary time zones. By design, all JS Date objects represent an instant in time (milliseconds since midnight Jan 1, 1970 **in UTC**) interpreted as the user's local time. Since most humans think about local time not UTC, it the most makes sense to perform this time span algorithm in reference to local time. Daylight Savings Time further complicates things, creating hours which get repeated and hours which cannot exist. *Countdown.js* effectively ignores these edge cases and talks about time preferring human intuition about time over surprise exactness. Example: A viewer asks for the description from noon the day before a daylight savings begins to noon the day after. A computer would answer "23 hours" whereas a human would confidently answer "1 day" even after being reminded to "Spring Forward". The computer is technically more accurate but this is not the value that humans actually expect or desire. Humans pretend that time is simple and makes sense. Unfortunately, humans made time far more complex than it needed to be with time zones and daylight savings. UTC simplifies time but at the cost of being inconsistent with human experience. ---- ## The API A simple but flexible API is the goal of *Countdown.js*. There is one global function with a set of static constants: var timespan = countdown(start|callback, end|callback, units, max, digits); The parameters are a starting Date, ending Date, an optional set of units, an optional maximum number of units, and an optional maximum number of decimal places on the smallest unit. `units` defaults to `countdown.DEFAULTS`, `max` defaults to `NaN` (all specified units), `digits` defaults to `0`. countdown.ALL = countdown.MILLENNIA | countdown.CENTURIES | countdown.DECADES | countdown.YEARS | countdown.MONTHS | countdown.WEEKS | countdown.DAYS | countdown.HOURS | countdown.MINUTES | countdown.SECONDS | countdown.MILLISECONDS; countdown.DEFAULTS = countdown.YEARS | countdown.MONTHS | countdown.DAYS | countdown.HOURS | countdown.MINUTES | countdown.SECONDS; This allows a very minimal call to accept the defaults and get the time since/until a single date. For example: countdown( new Date(2000, 0, 1) ).toString(); This will produce a human readable description like: 11 years, 8 months, 4 days, 10 hours, 12 minutes and 43 seconds ### The `start` / `end` arguments The parameters `start` and `end` can be one of several values: 1. `null` which indicates "now". 2. a JavaScript `Date` object. 3. a `number` specifying the number of milliseconds since midnight Jan 1, 1970 UTC (i.e., the "UNIX epoch"). 4. a callback `function` accepting one timespan argument. To reference a specific instant in time, either use a `number` offset from the epoch, or a JavaScript `Date` object instantiated with the specific offset from the epoch. In JavaScript, if a `Date` object is instantiated using year/month/date/etc values, then those values are interpreted interpreted in reference to the browser's local time zone and daylight savings settings. If `start` and `end` are both specified, then repeated calls to `countdown(...)` will always return the same result. If one date argument is left `null` while the other is provided, then repeated calls will count up if the provided date is in the past, and it will count down if the provided date is in the future. For example, var daysSinceLastWorkplaceAccident = countdown(507314280000, null, countdown.DAYS); If a callback function is supplied, then an interval timer will be started with a frequency based upon the smallest unit (e.g., if `countdown.SECONDS` is the smallest unit, the callback will be invoked once per second). Rather than returning a Timespan object, the timer's ID will be returned to allow canceling by passing into `window.clearInterval(id)`. For example, to show a timer since the page first loaded: var timerId = countdown( new Date(), function(ts) { document.getElementById('pageTimer').innerHTML = ts.toHTML("strong"); }, countdown.HOURS|countdown.MINUTES|countdown.SECONDS); // later on this timer may be stopped window.clearInterval(timerId); ### The `units` argument The static units constants can be combined using [standard bitwise operators](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/Bitwise_Operators). For example, to explicitly include "months or days" use bitwise-OR: countdown.MONTHS | countdown.DAYS To explicitly exclude units like "not weeks and not milliseconds" combine bitwise-NOT and bitwise-AND: ~countdown.WEEKS & ~countdown.MILLISECONDS [Equivalently](http://en.wikipedia.org/wiki/De_Morgan's_laws), to specify everything but "not weeks or milliseconds" wrap bitwise-NOT around bitwise-OR: ~(countdown.WEEKS | countdown.MILLISECONDS) ### The `max` argument The next optional argument `max` specifies a maximum number of unit labels to display. This allows specifying which units are interesting but only displaying the `max` most significant units. countdown(start, end, units).toString() => "5 years, 1 month, 19 days, 12 hours and 17 minutes" Specifying `max` as `2` ensures that only the two most significant units are displayed **(note the rounding of the least significant unit)**: countdown(start, end, units, 2).toString() => "5 years and 2 months" Negative or zero values of `max` are ignored. ---- #### Breaking change in v2.3.0! Previously, the `max` number of unit labels argument used to be specified when formatting in `timespan.toString(...)` and `timespan.toHTML(...)`. v2.3.0 moves it to `countdown(...)`, which improves efficiency as well as enabling fractional units (see below). ---- ### The `digits` argument The final optional argument `digits` allows fractional values on the smallest unit. countdown(start, end, units, max).toString() => "5 years and 2 months" Specifying `digits` as `2` allows up to 2 digits beyond the decimal point to be displayed **(note the rounding of the least significant unit)**: countdown(start, end, units, max, 2).toString() => "5 years and 1.65 months" `digits` must be between `0` and `20`, inclusive. ---- #### Rounding With the calculations of fractional units in v2.3.0, the smallest displayed unit now properly rounds. Previously, the equivalent of `"1.99 years"` would be truncated to `"1 year"`, as of v2.3.0 it will display as `"2 years"`. Typically, this is the intended interpretation but there are a few circumstances where people expect the truncated behavior. For example, people often talk about their age as the lowest possible interpretation. e.g., they claim "39-years-old" right up until the morning of their 40th birthday (some people do even for years after!). In these cases, after calling countdown(start,end,units,max,20) with the largest possible number of `digits`, you might want to set `ts.years = Math.floor(ts.years)` before calling `ts.toString()`. The vain might want you to set `ts.years = Math.min(ts.years, 39)`! ---- ### Timespan result The return value is a Timespan object which always contains the following fields: - `Date start`: the starting date object used for the calculation - `Date end`: the ending date object used for the calculation - `Number units`: the units specified - `Number value`: total milliseconds difference (i.e., `end` - `start`). If `end < start` then `value` will be negative. Typically the `end` occurs after `start`, but if the arguments were reversed, the only difference is `Timespan.value` will be negative. The sign of `value` can be used to determine if the event occurs in the future or in the past. The following time unit fields are only present if their corresponding units were requested: - `Number millennia` - `Number centuries` - `Number decades` - `Number years` - `Number months` - `Number days` - `Number hours` - `Number minutes` - `Number seconds` - `Number milliseconds` Finally, Timespan has two formatting methods each with some optional parameters. If the difference between `start` and `end` is less than the requested granularity of units, then `toString(...)` and `toHTML(...)` will return the empty label (defaults to an empty string). `String toString(emptyLabel)`: formats the Timespan object as an English sentence. e.g., using the same input: ts.toString() => "5 years, 1 month, 19 days, 12 hours and 17 minutes" `String toHTML(tagName, emptyLabel)`: formats the Timespan object as an English sentence, with the specified HTML tag wrapped around each unit. If no tag name is provided, "`span`" is used. e.g., using the same input: ts.toHTML() => "5 years, 1 month, 19 days, 12 hours and 17 minutes" ts.toHTML("em") => "5 years, 1 month, 19 days, 12 hours and 17 minutes" ---- ### Localization Very basic localization is supported via the static `setLabels` and `resetLabels` methods. These change the functionality for all timespans on the page. countdown.resetLabels(); countdown.setLabels(singular, plural, last, delim, empty, formatter); The arguments: - `singular` is a pipe (`'|'`) delimited ascending list of singular unit name overrides - `plural` is a pipe (`'|'`) delimited ascending list of plural unit name overrides - `last` is a delimiter before the last unit (default: `' and '`) - `delim` is a delimiter to use between all other units (default: `', '`), - `empty` is a label to use when all units are zero (default: `''`) - `formatter` is a function which takes a `number` and returns a `string` (default uses `Number.toString()`), allowing customization of the way numbers are formatted, e.g., commas every 3 digits or some unique style that is specific to your locale. Notice that the spacing is part of the labels. The following examples would translate the output into Brazilian Portuguese and French, respectively: countdown.setLabels( ' milissegundo| segundo| minuto| hora| dia| semana| mês| ano| década| século| milênio', ' milissegundos| segundos| minutos| horas| dias| semanas| meses| anos| décadas| séculos| milênios', ' e ', ' + ', 'agora'); countdown.setLabels( ' milliseconde| seconde| minute| heure| jour| semaine| mois| année| décennie| siècle| millénaire', ' millisecondes| secondes| minutes| heures| jours| semaines| mois| années| décennies| siècles| millénaires', ' et ', ', ', 'maintenant'); If you only wanted to override some of the labels just leave the other pipe-delimited places empty. Similarly, leave off any of the delimiter arguments which do not need overriding. countdown.setLabels( '||| hr| d', 'ms| sec|||| wks|| yrs', ', and finally '); ts.toString() => "1 millennium, 2 centuries, 5 yrs, 1 month, 7 wks, 19 days, 1 hr, 2 minutes, 17 sec, and finally 1 millisecond" If you only wanted to override the empty label: countdown.setLabels( null, null, null, null, 'Now.'); ts.toString() => "Now." The following would be effectively the same as calling `countdown.resetLabels()`: countdown.setLabels( ' millisecond| second| minute| hour| day| week| month| year| decade| century| millennium', ' milliseconds| seconds| minutes| hours| days| weeks| months| years| decades| centuries| millennia', ' and ', ', ', '', function(n){ return n.toString(); }); ---- ## License Distributed under the terms of [The MIT license][2]. ---- Copyright (c) 2006-2014 [Stephen M. McKamey][3] [1]: http://countdownjs.org [2]: https://raw.githubusercontent.com/mckamey/countdownjs/master/LICENSE.txt [3]: http://mck.me ================================================ FILE: bower.json ================================================ { "name": "countdownjs", "version": "2.6.1", "description": "A simple JavaScript API for producing an accurate, intuitive description of the timespan between two Date instances.", "main": "countdown.js", "keywords": [ "countdown", "timer", "clock", "date", "time", "timespan", "year", "month", "week", "day", "hour", "minute", "second" ], "authors": [ "Stephen McKamey (http://mck.me)" ], "license": "MIT", "homepage": "http://countdownjs.org", "ignore": [ "./!(countdown.js)" ] } ================================================ FILE: build.xml ================================================  ================================================ FILE: countdown.js ================================================ /*global window module */ /** * @license countdown.js v2.6.1 http://countdownjs.org * Copyright (c)2006-2014 Stephen M. McKamey. * Licensed under The MIT License. */ /*jshint bitwise:false */ /** * API entry * @public * @param {function(Object)|Date|number} start the starting date * @param {function(Object)|Date|number} end the ending date * @param {number} units the units to populate * @return {Object|number} */ var countdown = ( function() { /*jshint smarttabs:true */ 'use strict'; /** * @private * @const * @type {number} */ var MILLISECONDS = 0x001; /** * @private * @const * @type {number} */ var SECONDS = 0x002; /** * @private * @const * @type {number} */ var MINUTES = 0x004; /** * @private * @const * @type {number} */ var HOURS = 0x008; /** * @private * @const * @type {number} */ var DAYS = 0x010; /** * @private * @const * @type {number} */ var WEEKS = 0x020; /** * @private * @const * @type {number} */ var MONTHS = 0x040; /** * @private * @const * @type {number} */ var YEARS = 0x080; /** * @private * @const * @type {number} */ var DECADES = 0x100; /** * @private * @const * @type {number} */ var CENTURIES = 0x200; /** * @private * @const * @type {number} */ var MILLENNIA = 0x400; /** * @private * @const * @type {number} */ var DEFAULTS = YEARS|MONTHS|DAYS|HOURS|MINUTES|SECONDS; /** * @private * @const * @type {number} */ var MILLISECONDS_PER_SECOND = 1000; /** * @private * @const * @type {number} */ var SECONDS_PER_MINUTE = 60; /** * @private * @const * @type {number} */ var MINUTES_PER_HOUR = 60; /** * @private * @const * @type {number} */ var HOURS_PER_DAY = 24; /** * @private * @const * @type {number} */ var MILLISECONDS_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND; /** * @private * @const * @type {number} */ var DAYS_PER_WEEK = 7; /** * @private * @const * @type {number} */ var MONTHS_PER_YEAR = 12; /** * @private * @const * @type {number} */ var YEARS_PER_DECADE = 10; /** * @private * @const * @type {number} */ var DECADES_PER_CENTURY = 10; /** * @private * @const * @type {number} */ var CENTURIES_PER_MILLENNIUM = 10; /** * @private * @param {number} x number * @return {number} */ var ceil = Math.ceil; /** * @private * @param {number} x number * @return {number} */ var floor = Math.floor; /** * @private * @param {Date} ref reference date * @param {number} shift number of months to shift * @return {number} number of days shifted */ function borrowMonths(ref, shift) { var prevTime = ref.getTime(); // increment month by shift ref.setMonth( ref.getMonth() + shift ); // this is the trickiest since months vary in length return Math.round( (ref.getTime() - prevTime) / MILLISECONDS_PER_DAY ); } /** * @private * @param {Date} ref reference date * @return {number} number of days */ function daysPerMonth(ref) { var a = ref.getTime(); // increment month by 1 var b = new Date(a); b.setMonth( ref.getMonth() + 1 ); // this is the trickiest since months vary in length return Math.round( (b.getTime() - a) / MILLISECONDS_PER_DAY ); } /** * @private * @param {Date} ref reference date * @return {number} number of days */ function daysPerYear(ref) { var a = ref.getTime(); // increment year by 1 var b = new Date(a); b.setFullYear( ref.getFullYear() + 1 ); // this is the trickiest since years (periodically) vary in length return Math.round( (b.getTime() - a) / MILLISECONDS_PER_DAY ); } /** * Applies the Timespan to the given date. * * @private * @param {Timespan} ts * @param {Date=} date * @return {Date} */ function addToDate(ts, date) { date = (date instanceof Date) || ((date !== null) && isFinite(date)) ? new Date(+date) : new Date(); if (!ts) { return date; } // if there is a value field, use it directly var value = +ts.value || 0; if (value) { date.setTime(date.getTime() + value); return date; } value = +ts.milliseconds || 0; if (value) { date.setMilliseconds(date.getMilliseconds() + value); } value = +ts.seconds || 0; if (value) { date.setSeconds(date.getSeconds() + value); } value = +ts.minutes || 0; if (value) { date.setMinutes(date.getMinutes() + value); } value = +ts.hours || 0; if (value) { date.setHours(date.getHours() + value); } value = +ts.weeks || 0; if (value) { value *= DAYS_PER_WEEK; } value += +ts.days || 0; if (value) { date.setDate(date.getDate() + value); } value = +ts.months || 0; if (value) { date.setMonth(date.getMonth() + value); } value = +ts.millennia || 0; if (value) { value *= CENTURIES_PER_MILLENNIUM; } value += +ts.centuries || 0; if (value) { value *= DECADES_PER_CENTURY; } value += +ts.decades || 0; if (value) { value *= YEARS_PER_DECADE; } value += +ts.years || 0; if (value) { date.setFullYear(date.getFullYear() + value); } return date; } /** * @private * @const * @type {number} */ var LABEL_MILLISECONDS = 0; /** * @private * @const * @type {number} */ var LABEL_SECONDS = 1; /** * @private * @const * @type {number} */ var LABEL_MINUTES = 2; /** * @private * @const * @type {number} */ var LABEL_HOURS = 3; /** * @private * @const * @type {number} */ var LABEL_DAYS = 4; /** * @private * @const * @type {number} */ var LABEL_WEEKS = 5; /** * @private * @const * @type {number} */ var LABEL_MONTHS = 6; /** * @private * @const * @type {number} */ var LABEL_YEARS = 7; /** * @private * @const * @type {number} */ var LABEL_DECADES = 8; /** * @private * @const * @type {number} */ var LABEL_CENTURIES = 9; /** * @private * @const * @type {number} */ var LABEL_MILLENNIA = 10; /** * @private * @type {Array} */ var LABELS_SINGLUAR; /** * @private * @type {Array} */ var LABELS_PLURAL; /** * @private * @type {string} */ var LABEL_LAST; /** * @private * @type {string} */ var LABEL_DELIM; /** * @private * @type {string} */ var LABEL_NOW; /** * Formats a number & unit as a string * * @param {number} value * @param {number} unit * @return {string} */ var formatter; /** * Formats a number as a string * * @private * @param {number} value * @return {string} */ var formatNumber; /** * @private * @param {number} value * @param {number} unit unit index into label list * @return {string} */ function plurality(value, unit) { return formatNumber(value)+((value === 1) ? LABELS_SINGLUAR[unit] : LABELS_PLURAL[unit]); } /** * Formats the entries with singular or plural labels * * @private * @param {Timespan} ts * @return {Array} */ var formatList; /** * Timespan representation of a duration of time * * @private * @this {Timespan} * @constructor */ function Timespan() {} /** * Formats the Timespan as a sentence * * @param {string=} emptyLabel the string to use when no values returned * @return {string} */ Timespan.prototype.toString = function(emptyLabel) { var label = formatList(this); var count = label.length; if (!count) { return emptyLabel ? ''+emptyLabel : LABEL_NOW; } if (count === 1) { return label[0]; } var last = LABEL_LAST+label.pop(); return label.join(LABEL_DELIM)+last; }; /** * Formats the Timespan as a sentence in HTML * * @param {string=} tag HTML tag name to wrap each value * @param {string=} emptyLabel the string to use when no values returned * @return {string} */ Timespan.prototype.toHTML = function(tag, emptyLabel) { tag = tag || 'span'; var label = formatList(this); var count = label.length; if (!count) { emptyLabel = emptyLabel || LABEL_NOW; return emptyLabel ? '<'+tag+'>'+emptyLabel+'' : emptyLabel; } for (var i=0; i'+label[i]+''; } if (count === 1) { return label[0]; } var last = LABEL_LAST+label.pop(); return label.join(LABEL_DELIM)+last; }; /** * Applies the Timespan to the given date * * @param {Date=} date the date to which the timespan is added. * @return {Date} */ Timespan.prototype.addTo = function(date) { return addToDate(this, date); }; /** * Formats the entries as English labels * * @private * @param {Timespan} ts * @return {Array} */ formatList = function(ts) { var list = []; var value = ts.millennia; if (value) { list.push(formatter(value, LABEL_MILLENNIA)); } value = ts.centuries; if (value) { list.push(formatter(value, LABEL_CENTURIES)); } value = ts.decades; if (value) { list.push(formatter(value, LABEL_DECADES)); } value = ts.years; if (value) { list.push(formatter(value, LABEL_YEARS)); } value = ts.months; if (value) { list.push(formatter(value, LABEL_MONTHS)); } value = ts.weeks; if (value) { list.push(formatter(value, LABEL_WEEKS)); } value = ts.days; if (value) { list.push(formatter(value, LABEL_DAYS)); } value = ts.hours; if (value) { list.push(formatter(value, LABEL_HOURS)); } value = ts.minutes; if (value) { list.push(formatter(value, LABEL_MINUTES)); } value = ts.seconds; if (value) { list.push(formatter(value, LABEL_SECONDS)); } value = ts.milliseconds; if (value) { list.push(formatter(value, LABEL_MILLISECONDS)); } return list; }; /** * Borrow any underflow units, carry any overflow units * * @private * @param {Timespan} ts * @param {string} toUnit */ function rippleRounded(ts, toUnit) { switch (toUnit) { case 'seconds': if (ts.seconds !== SECONDS_PER_MINUTE || isNaN(ts.minutes)) { return; } // ripple seconds up to minutes ts.minutes++; ts.seconds = 0; /* falls through */ case 'minutes': if (ts.minutes !== MINUTES_PER_HOUR || isNaN(ts.hours)) { return; } // ripple minutes up to hours ts.hours++; ts.minutes = 0; /* falls through */ case 'hours': if (ts.hours !== HOURS_PER_DAY || isNaN(ts.days)) { return; } // ripple hours up to days ts.days++; ts.hours = 0; /* falls through */ case 'days': if (ts.days !== DAYS_PER_WEEK || isNaN(ts.weeks)) { return; } // ripple days up to weeks ts.weeks++; ts.days = 0; /* falls through */ case 'weeks': if (ts.weeks !== daysPerMonth(ts.refMonth)/DAYS_PER_WEEK || isNaN(ts.months)) { return; } // ripple weeks up to months ts.months++; ts.weeks = 0; /* falls through */ case 'months': if (ts.months !== MONTHS_PER_YEAR || isNaN(ts.years)) { return; } // ripple months up to years ts.years++; ts.months = 0; /* falls through */ case 'years': if (ts.years !== YEARS_PER_DECADE || isNaN(ts.decades)) { return; } // ripple years up to decades ts.decades++; ts.years = 0; /* falls through */ case 'decades': if (ts.decades !== DECADES_PER_CENTURY || isNaN(ts.centuries)) { return; } // ripple decades up to centuries ts.centuries++; ts.decades = 0; /* falls through */ case 'centuries': if (ts.centuries !== CENTURIES_PER_MILLENNIUM || isNaN(ts.millennia)) { return; } // ripple centuries up to millennia ts.millennia++; ts.centuries = 0; /* falls through */ } } /** * Ripple up partial units one place * * @private * @param {Timespan} ts timespan * @param {number} frac accumulated fractional value * @param {string} fromUnit source unit name * @param {string} toUnit target unit name * @param {number} conversion multiplier between units * @param {number} digits max number of decimal digits to output * @return {number} new fractional value */ function fraction(ts, frac, fromUnit, toUnit, conversion, digits) { if (ts[fromUnit] >= 0) { frac += ts[fromUnit]; delete ts[fromUnit]; } frac /= conversion; if (frac + 1 <= 1) { // drop if below machine epsilon return 0; } if (ts[toUnit] >= 0) { // ensure does not have more than specified number of digits ts[toUnit] = +(ts[toUnit] + frac).toFixed(digits); rippleRounded(ts, toUnit); return 0; } return frac; } /** * Ripple up partial units to next existing * * @private * @param {Timespan} ts * @param {number} digits max number of decimal digits to output */ function fractional(ts, digits) { var frac = fraction(ts, 0, 'milliseconds', 'seconds', MILLISECONDS_PER_SECOND, digits); if (!frac) { return; } frac = fraction(ts, frac, 'seconds', 'minutes', SECONDS_PER_MINUTE, digits); if (!frac) { return; } frac = fraction(ts, frac, 'minutes', 'hours', MINUTES_PER_HOUR, digits); if (!frac) { return; } frac = fraction(ts, frac, 'hours', 'days', HOURS_PER_DAY, digits); if (!frac) { return; } frac = fraction(ts, frac, 'days', 'weeks', DAYS_PER_WEEK, digits); if (!frac) { return; } frac = fraction(ts, frac, 'weeks', 'months', daysPerMonth(ts.refMonth)/DAYS_PER_WEEK, digits); if (!frac) { return; } frac = fraction(ts, frac, 'months', 'years', daysPerYear(ts.refMonth)/daysPerMonth(ts.refMonth), digits); if (!frac) { return; } frac = fraction(ts, frac, 'years', 'decades', YEARS_PER_DECADE, digits); if (!frac) { return; } frac = fraction(ts, frac, 'decades', 'centuries', DECADES_PER_CENTURY, digits); if (!frac) { return; } frac = fraction(ts, frac, 'centuries', 'millennia', CENTURIES_PER_MILLENNIUM, digits); // should never reach this with remaining fractional value if (frac) { throw new Error('Fractional unit overflow'); } } /** * Borrow any underflow units, carry any overflow units * * @private * @param {Timespan} ts */ function ripple(ts) { var x; if (ts.milliseconds < 0) { // ripple seconds down to milliseconds x = ceil(-ts.milliseconds / MILLISECONDS_PER_SECOND); ts.seconds -= x; ts.milliseconds += x * MILLISECONDS_PER_SECOND; } else if (ts.milliseconds >= MILLISECONDS_PER_SECOND) { // ripple milliseconds up to seconds ts.seconds += floor(ts.milliseconds / MILLISECONDS_PER_SECOND); ts.milliseconds %= MILLISECONDS_PER_SECOND; } if (ts.seconds < 0) { // ripple minutes down to seconds x = ceil(-ts.seconds / SECONDS_PER_MINUTE); ts.minutes -= x; ts.seconds += x * SECONDS_PER_MINUTE; } else if (ts.seconds >= SECONDS_PER_MINUTE) { // ripple seconds up to minutes ts.minutes += floor(ts.seconds / SECONDS_PER_MINUTE); ts.seconds %= SECONDS_PER_MINUTE; } if (ts.minutes < 0) { // ripple hours down to minutes x = ceil(-ts.minutes / MINUTES_PER_HOUR); ts.hours -= x; ts.minutes += x * MINUTES_PER_HOUR; } else if (ts.minutes >= MINUTES_PER_HOUR) { // ripple minutes up to hours ts.hours += floor(ts.minutes / MINUTES_PER_HOUR); ts.minutes %= MINUTES_PER_HOUR; } if (ts.hours < 0) { // ripple days down to hours x = ceil(-ts.hours / HOURS_PER_DAY); ts.days -= x; ts.hours += x * HOURS_PER_DAY; } else if (ts.hours >= HOURS_PER_DAY) { // ripple hours up to days ts.days += floor(ts.hours / HOURS_PER_DAY); ts.hours %= HOURS_PER_DAY; } while (ts.days < 0) { // NOTE: never actually seen this loop more than once // ripple months down to days ts.months--; ts.days += borrowMonths(ts.refMonth, 1); } // weeks is always zero here if (ts.days >= DAYS_PER_WEEK) { // ripple days up to weeks ts.weeks += floor(ts.days / DAYS_PER_WEEK); ts.days %= DAYS_PER_WEEK; } if (ts.months < 0) { // ripple years down to months x = ceil(-ts.months / MONTHS_PER_YEAR); ts.years -= x; ts.months += x * MONTHS_PER_YEAR; } else if (ts.months >= MONTHS_PER_YEAR) { // ripple months up to years ts.years += floor(ts.months / MONTHS_PER_YEAR); ts.months %= MONTHS_PER_YEAR; } // years is always non-negative here // decades, centuries and millennia are always zero here if (ts.years >= YEARS_PER_DECADE) { // ripple years up to decades ts.decades += floor(ts.years / YEARS_PER_DECADE); ts.years %= YEARS_PER_DECADE; if (ts.decades >= DECADES_PER_CENTURY) { // ripple decades up to centuries ts.centuries += floor(ts.decades / DECADES_PER_CENTURY); ts.decades %= DECADES_PER_CENTURY; if (ts.centuries >= CENTURIES_PER_MILLENNIUM) { // ripple centuries up to millennia ts.millennia += floor(ts.centuries / CENTURIES_PER_MILLENNIUM); ts.centuries %= CENTURIES_PER_MILLENNIUM; } } } } /** * Remove any units not requested * * @private * @param {Timespan} ts * @param {number} units the units to populate * @param {number} max number of labels to output * @param {number} digits max number of decimal digits to output */ function pruneUnits(ts, units, max, digits) { var count = 0; // Calc from largest unit to smallest to prevent underflow if (!(units & MILLENNIA) || (count >= max)) { // ripple millennia down to centuries ts.centuries += ts.millennia * CENTURIES_PER_MILLENNIUM; delete ts.millennia; } else if (ts.millennia) { count++; } if (!(units & CENTURIES) || (count >= max)) { // ripple centuries down to decades ts.decades += ts.centuries * DECADES_PER_CENTURY; delete ts.centuries; } else if (ts.centuries) { count++; } if (!(units & DECADES) || (count >= max)) { // ripple decades down to years ts.years += ts.decades * YEARS_PER_DECADE; delete ts.decades; } else if (ts.decades) { count++; } if (!(units & YEARS) || (count >= max)) { // ripple years down to months ts.months += ts.years * MONTHS_PER_YEAR; delete ts.years; } else if (ts.years) { count++; } if (!(units & MONTHS) || (count >= max)) { // ripple months down to days if (ts.months) { ts.days += borrowMonths(ts.refMonth, ts.months); } delete ts.months; if (ts.days >= DAYS_PER_WEEK) { // ripple day overflow back up to weeks ts.weeks += floor(ts.days / DAYS_PER_WEEK); ts.days %= DAYS_PER_WEEK; } } else if (ts.months) { count++; } if (!(units & WEEKS) || (count >= max)) { // ripple weeks down to days ts.days += ts.weeks * DAYS_PER_WEEK; delete ts.weeks; } else if (ts.weeks) { count++; } if (!(units & DAYS) || (count >= max)) { //ripple days down to hours ts.hours += ts.days * HOURS_PER_DAY; delete ts.days; } else if (ts.days) { count++; } if (!(units & HOURS) || (count >= max)) { // ripple hours down to minutes ts.minutes += ts.hours * MINUTES_PER_HOUR; delete ts.hours; } else if (ts.hours) { count++; } if (!(units & MINUTES) || (count >= max)) { // ripple minutes down to seconds ts.seconds += ts.minutes * SECONDS_PER_MINUTE; delete ts.minutes; } else if (ts.minutes) { count++; } if (!(units & SECONDS) || (count >= max)) { // ripple seconds down to milliseconds ts.milliseconds += ts.seconds * MILLISECONDS_PER_SECOND; delete ts.seconds; } else if (ts.seconds) { count++; } // nothing to ripple milliseconds down to // so ripple back up to smallest existing unit as a fractional value if (!(units & MILLISECONDS) || (count >= max)) { fractional(ts, digits); } } /** * Populates the Timespan object * * @private * @param {Timespan} ts * @param {?Date} start the starting date * @param {?Date} end the ending date * @param {number} units the units to populate * @param {number} max number of labels to output * @param {number} digits max number of decimal digits to output */ function populate(ts, start, end, units, max, digits) { var now = new Date(); ts.start = start = start || now; ts.end = end = end || now; ts.units = units; ts.value = end.getTime() - start.getTime(); if (ts.value < 0) { // swap if reversed var tmp = end; end = start; start = tmp; } // reference month for determining days in month ts.refMonth = new Date(start.getFullYear(), start.getMonth(), 15, 12, 0, 0); try { // reset to initial deltas ts.millennia = 0; ts.centuries = 0; ts.decades = 0; ts.years = end.getFullYear() - start.getFullYear(); ts.months = end.getMonth() - start.getMonth(); ts.weeks = 0; ts.days = end.getDate() - start.getDate(); ts.hours = end.getHours() - start.getHours(); ts.minutes = end.getMinutes() - start.getMinutes(); ts.seconds = end.getSeconds() - start.getSeconds(); ts.milliseconds = end.getMilliseconds() - start.getMilliseconds(); ripple(ts); pruneUnits(ts, units, max, digits); } finally { delete ts.refMonth; } return ts; } /** * Determine an appropriate refresh rate based upon units * * @private * @param {number} units the units to populate * @return {number} milliseconds to delay */ function getDelay(units) { if (units & MILLISECONDS) { // refresh very quickly return MILLISECONDS_PER_SECOND / 30; //30Hz } if (units & SECONDS) { // refresh every second return MILLISECONDS_PER_SECOND; //1Hz } if (units & MINUTES) { // refresh every minute return MILLISECONDS_PER_SECOND * SECONDS_PER_MINUTE; } if (units & HOURS) { // refresh hourly return MILLISECONDS_PER_SECOND * SECONDS_PER_MINUTE * MINUTES_PER_HOUR; } if (units & DAYS) { // refresh daily return MILLISECONDS_PER_SECOND * SECONDS_PER_MINUTE * MINUTES_PER_HOUR * HOURS_PER_DAY; } // refresh the rest weekly return MILLISECONDS_PER_SECOND * SECONDS_PER_MINUTE * MINUTES_PER_HOUR * HOURS_PER_DAY * DAYS_PER_WEEK; } /** * API entry point * * @public * @param {Date|number|Timespan|null|function(Timespan,number)} start the starting date * @param {Date|number|Timespan|null|function(Timespan,number)} end the ending date * @param {number=} units the units to populate * @param {number=} max number of labels to output * @param {number=} digits max number of decimal digits to output * @return {Timespan|number} */ function countdown(start, end, units, max, digits) { var callback; // ensure some units or use defaults units = +units || DEFAULTS; // max must be positive max = (max > 0) ? max : NaN; // clamp digits to an integer between [0, 20] digits = (digits > 0) ? (digits < 20) ? Math.round(digits) : 20 : 0; // ensure start date var startTS = null; if ('function' === typeof start) { callback = start; start = null; } else if (!(start instanceof Date)) { if ((start !== null) && isFinite(start)) { start = new Date(+start); } else { if ('object' === typeof startTS) { startTS = /** @type{Timespan} */(start); } start = null; } } // ensure end date var endTS = null; if ('function' === typeof end) { callback = end; end = null; } else if (!(end instanceof Date)) { if ((end !== null) && isFinite(end)) { end = new Date(+end); } else { if ('object' === typeof end) { endTS = /** @type{Timespan} */(end); } end = null; } } // must wait to interpret timespans until after resolving dates if (startTS) { start = addToDate(startTS, end); } if (endTS) { end = addToDate(endTS, start); } if (!start && !end) { // used for unit testing return new Timespan(); } if (!callback) { return populate(new Timespan(), /** @type{Date} */(start), /** @type{Date} */(end), /** @type{number} */(units), /** @type{number} */(max), /** @type{number} */(digits)); } // base delay off units var delay = getDelay(units), timerId, fn = function() { callback( populate(new Timespan(), /** @type{Date} */(start), /** @type{Date} */(end), /** @type{number} */(units), /** @type{number} */(max), /** @type{number} */(digits)), timerId ); }; fn(); return (timerId = setInterval(fn, delay)); } /** * @public * @const * @type {number} */ countdown.MILLISECONDS = MILLISECONDS; /** * @public * @const * @type {number} */ countdown.SECONDS = SECONDS; /** * @public * @const * @type {number} */ countdown.MINUTES = MINUTES; /** * @public * @const * @type {number} */ countdown.HOURS = HOURS; /** * @public * @const * @type {number} */ countdown.DAYS = DAYS; /** * @public * @const * @type {number} */ countdown.WEEKS = WEEKS; /** * @public * @const * @type {number} */ countdown.MONTHS = MONTHS; /** * @public * @const * @type {number} */ countdown.YEARS = YEARS; /** * @public * @const * @type {number} */ countdown.DECADES = DECADES; /** * @public * @const * @type {number} */ countdown.CENTURIES = CENTURIES; /** * @public * @const * @type {number} */ countdown.MILLENNIA = MILLENNIA; /** * @public * @const * @type {number} */ countdown.DEFAULTS = DEFAULTS; /** * @public * @const * @type {number} */ countdown.ALL = MILLENNIA|CENTURIES|DECADES|YEARS|MONTHS|WEEKS|DAYS|HOURS|MINUTES|SECONDS|MILLISECONDS; /** * Customize the format settings. * @public * @param {Object} format settings object */ var setFormat = countdown.setFormat = function(format) { if (!format) { return; } if ('singular' in format || 'plural' in format) { var singular = format.singular || []; if (singular.split) { singular = singular.split('|'); } var plural = format.plural || []; if (plural.split) { plural = plural.split('|'); } for (var i=LABEL_MILLISECONDS; i<=LABEL_MILLENNIA; i++) { // override any specified units LABELS_SINGLUAR[i] = singular[i] || LABELS_SINGLUAR[i]; LABELS_PLURAL[i] = plural[i] || LABELS_PLURAL[i]; } } if ('string' === typeof format.last) { LABEL_LAST = format.last; } if ('string' === typeof format.delim) { LABEL_DELIM = format.delim; } if ('string' === typeof format.empty) { LABEL_NOW = format.empty; } if ('function' === typeof format.formatNumber) { formatNumber = format.formatNumber; } if ('function' === typeof format.formatter) { formatter = format.formatter; } }; /** * Revert to the default formatting. * @public */ var resetFormat = countdown.resetFormat = function() { LABELS_SINGLUAR = ' millisecond| second| minute| hour| day| week| month| year| decade| century| millennium'.split('|'); LABELS_PLURAL = ' milliseconds| seconds| minutes| hours| days| weeks| months| years| decades| centuries| millennia'.split('|'); LABEL_LAST = ' and '; LABEL_DELIM = ', '; LABEL_NOW = ''; formatNumber = function(value) { return value; }; formatter = plurality; }; /** * Override the unit labels. * @public * @param {string|Array=} singular a pipe ('|') delimited list of singular unit name overrides * @param {string|Array=} plural a pipe ('|') delimited list of plural unit name overrides * @param {string=} last a delimiter before the last unit (default: ' and ') * @param {string=} delim a delimiter to use between all other units (default: ', ') * @param {string=} empty a label to use when all units are zero (default: '') * @param {function(number):string=} formatNumber a function which formats numbers as a string * @param {function(number,number):string=} formatter a function which formats a number/unit pair as a string * @deprecated since version 2.6.0 */ countdown.setLabels = function(singular, plural, last, delim, empty, formatNumber, formatter) { setFormat({ singular: singular, plural: plural, last: last, delim: delim, empty: empty, formatNumber: formatNumber, formatter: formatter }); }; /** * Revert to the default unit labels. * @public * @deprecated since version 2.6.0 */ countdown.resetLabels = resetFormat; resetFormat(); if (typeof module !== 'undefined' && module.exports) { module.exports = countdown; } else if (typeof window !== 'undefined' && typeof window.define === 'function' && typeof window.define.amd !== 'undefined') { window.define('countdown', [], function() { return countdown; }); } return countdown; })(); ================================================ FILE: demo.html ================================================ Countdown.js Demo

Countdown.js

Countdown.js Demo

Countdown date

 UTC

Display units

Timespan object


	
================================================ FILE: demo.js ================================================ (function() { function byId(id) { return document.getElementById(id); } function formatTens(n) { // format integers to have at least two digits return (n < 10) ? '0'+n : ''+n; } // initialize units for (var key in countdown) { if (countdown.hasOwnProperty(key)) { var unit = byId('units-'+key.toLowerCase()); if (unit) { unit.value = countdown[key]; unit.checked = countdown[key] & countdown.DEFAULTS; } } } // Mayan Calendar: 1356088271111 // https://groups.google.com/group/alt.hypertext/msg/06dad279804cb3ba?dmode=source var DEMO_MS = 681490580000, DEMO_PAST = 'The World Wide Web debuted', DEMO_FUTURE = 'The World Wide Web debuts'; // adjust initial demo time for local timezone byId('hours').value -= new Date(DEMO_MS).getTimezoneOffset() / 60; function update() { var units = ~countdown.ALL, chx = byId('countdown-units').getElementsByTagName('input'), empty = byId('empty-label').value || null, max = +(byId('max-units').value), digits = +(byId('frac-digits').value); for (var i=0, count=chx.length; i 0) ? DEMO_PAST+' '+msg+' ago!' : DEMO_FUTURE+' in '+msg+'!'; } counter.innerHTML = msg; timespan.innerHTML = JSON.stringify(ts, null, ' '); // update timezone label var tz = start.getTimezoneOffset(); if (tz) { var tzh = Math.floor(Math.abs(tz) / 60), tzm = (Math.abs(tz) % 60); byId('timezone').innerHTML = 'UTC'+((tz > 0) ? '-' : '+')+formatTens(tzh)+':'+formatTens(tzm); } else { byId('timezone').innerHTML = 'UTC'; } requestAnimationFrame(update, timespan.parentNode); } update(); })(); ================================================ FILE: externs.js ================================================ /** * @fileoverview External module declarations. * @externs */ /** * @type {Object|undefined} */ var module; /** * @type {Object|undefined} */ var exports; ================================================ FILE: index.html ================================================ Countdown.js

Countdown.js

A simple JavaScript API for producing an accurate, intuitive description of the timespan between two Date instances.

Docs

Downloads

Links

================================================ FILE: lib/closure/COPYING ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: lib/closure/README ================================================ /* * Copyright 2009 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // // Contents // The Closure Compiler performs checking, instrumentation, and optimizations on JavaScript code. The purpose of this README is to explain how to build and run the Closure Compiler. The Closure Compiler requires Java 6 or higher. http://www.java.com/ // // Building The Closure Compiler // There are three ways to get a Closure Compiler executable. 1) Use one we built for you. Pre-built Closure binaries can be found at http://code.google.com/p/closure-compiler/downloads/list 2) Check out the source and build it with Apache Ant. First, check out the full source tree of the Closure Compiler. There are instructions on how to do this at the project site. http://code.google.com/p/closure-compiler/source/checkout Apache Ant is a cross-platform build tool. http://ant.apache.org/ At the root of the source tree, there is an Ant file named build.xml. To use it, navigate to the same directory and type the command ant jar This will produce a jar file called "build/compiler.jar". 3) Check out the source and build it with Eclipse. Eclipse is a cross-platform IDE. http://www.eclipse.org/ Under Eclipse's File menu, click "New > Project ..." and create a "Java Project." You will see an options screen. Give the project a name, select "Create project from existing source," and choose the root of the checked-out source tree as the existing directory. Verify that you are using JRE version 6 or higher. Eclipse can use the build.xml file to discover rules. When you navigate to the build.xml file, you will see all the build rules in the "Outline" pane. Run the "jar" rule to build the compiler in build/compiler.jar. // // Running The Closure Compiler // Once you have the jar binary, running the Closure Compiler is straightforward. On the command line, type java -jar compiler.jar This starts the compiler in interactive mode. Type var x = 17 + 25; then hit "Enter", then hit "Ctrl-Z" (on Windows) or "Ctrl-D" (on Mac or Linux) and "Enter" again. The Compiler will respond: var x=42; The Closure Compiler has many options for reading input from a file, writing output to a file, checking your code, and running optimizations. To learn more, type java -jar compiler.jar --help You can read more detailed documentation about the many flags at http://code.google.com/closure/compiler/docs/gettingstarted_app.html // // Compiling Multiple Scripts // If you have multiple scripts, you should compile them all together with one compile command. java -jar compiler.jar --js=in1.js --js=in2.js ... --js_output_file=out.js The Closure Compiler will concatenate the files in the order they're passed at the command line. If you need to compile many, many scripts together, you may start to run into problems with managing dependencies between scripts. You should check out the Closure Library. It contains functions for enforcing dependencies between scripts, and a tool called calcdeps.py that knows how to give scripts to the Closure Compiler in the right order. http://code.google.com/p/closure-library/ // // Licensing // Unless otherwise stated, all source files are licensed under the Apache License, Version 2.0. ----- Code under: src/com/google/javascript/rhino test/com/google/javascript/rhino URL: http://www.mozilla.org/rhino Version: 1.5R3, with heavy modifications License: Netscape Public License and MPL / GPL dual license Description: A partial copy of Mozilla Rhino. Mozilla Rhino is an implementation of JavaScript for the JVM. The JavaScript parser and the parse tree data structures were extracted and modified significantly for use by Google's JavaScript compiler. Local Modifications: The packages have been renamespaced. All code not relevant to parsing has been removed. A JsDoc parser and static typing system have been added. ----- Code in: lib/rhino Rhino URL: http://www.mozilla.org/rhino Version: Trunk License: Netscape Public License and MPL / GPL dual license Description: Mozilla Rhino is an implementation of JavaScript for the JVM. Local Modifications: Minor changes to parsing JSDoc that usually get pushed up-stream to Rhino trunk. ----- Code in: lib/args4j.jar Args4j URL: https://args4j.dev.java.net/ Version: 2.0.16 License: MIT Description: args4j is a small Java class library that makes it easy to parse command line options/arguments in your CUI application. Local Modifications: None. ----- Code in: lib/guava.jar Guava Libraries URL: http://code.google.com/p/guava-libraries/ Version: 14.0 License: Apache License 2.0 Description: Google's core Java libraries. Local Modifications: None. ----- Code in: lib/jsr305.jar Annotations for software defect detection URL: http://code.google.com/p/jsr-305/ Version: svn revision 47 License: BSD License Description: Annotations for software defect detection. Local Modifications: None. ----- Code in: lib/jarjar.jar Jar Jar Links URL: http://jarjar.googlecode.com/ Version: 1.1 License: Apache License 2.0 Description: A utility for repackaging Java libraries. Local Modifications: None. ---- Code in: lib/junit.jar JUnit URL: http://sourceforge.net/projects/junit/ Version: 4.10 License: Common Public License 1.0 Description: A framework for writing and running automated tests in Java. Local Modifications: None. --- Code in: lib/protobuf-java.jar Protocol Buffers URL: http://code.google.com/p/protobuf/ Version: 2.4.1 License: New BSD License Description: Supporting libraries for protocol buffers, an encoding of structured data. Local Modifications: None --- Code in: lib/ant.jar lib/ant-launcher.jar URL: http://ant.apache.org/bindownload.cgi Version: 1.8.1 License: Apache License 2.0 Description: Ant is a Java based build tool. In theory it is kind of like "make" without make's wrinkles and with the full portability of pure java code. Local Modifications: None --- Code in: lib/json.jar URL: http://json.org/java/index.html Version: JSON version 20090211 License: MIT license Description: JSON is a set of java files for use in transmitting data in JSON format. Local Modifications: None --- Code in: tools/maven-ant-tasks-2.1.3.jar URL: http://maven.apache.org Version 2.1.3 License: Apache License 2.0 Description: Maven Ant tasks are used to manage dependencies and to install/deploy to maven repositories. Local Modifications: None ================================================ FILE: lib/closure/docs.url ================================================ [InternetShortcut] URL=http://code.google.com/closure/compiler/docs/js-for-compiler.html ================================================ FILE: lib/closure/download.url ================================================ [InternetShortcut] URL=http://code.google.com/closure/compiler/ ================================================ FILE: lib/jslint/download.url ================================================ [InternetShortcut] URL=http://www.jslint.com/lint.html ================================================ FILE: lib/jslint/jslint.js ================================================ // jslint.js // 2010-10-16 /* Copyright (c) 2002 Douglas Crockford (www.JSLint.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. The Software shall be used for Good, not Evil. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* JSLINT is a global function. It takes two parameters. var myResult = JSLINT(source, option); The first parameter is either a string or an array of strings. If it is a string, it will be split on '\n' or '\r'. If it is an array of strings, it is assumed that each string represents one line. The source can be a JavaScript text, or HTML text, or a Konfabulator text. The second parameter is an optional object of options which control the operation of JSLINT. Most of the options are booleans: They are all are optional and have a default value of false. If it checks out, JSLINT returns true. Otherwise, it returns false. If false, you can inspect JSLINT.errors to find out the problems. JSLINT.errors is an array of objects containing these members: { line : The line (relative to 0) at which the lint was found character : The character (relative to 0) at which the lint was found reason : The problem evidence : The text line in which the problem occurred raw : The raw message before the details were inserted a : The first detail b : The second detail c : The third detail d : The fourth detail } If a fatal error was found, a null will be the last element of the JSLINT.errors array. You can request a Function Report, which shows all of the functions and the parameters and vars that they use. This can be used to find implied global variables and other problems. The report is in HTML and can be inserted in an HTML . var myReport = JSLINT.report(limited); If limited is true, then the report will be limited to only errors. You can request a data structure which contains JSLint's results. var myData = JSLINT.data(); It returns a structure with this form: { errors: [ { line: NUMBER, character: NUMBER, reason: STRING, evidence: STRING } ], functions: [ name: STRING, line: NUMBER, last: NUMBER, param: [ STRING ], closure: [ STRING ], var: [ STRING ], exception: [ STRING ], outer: [ STRING ], unused: [ STRING ], global: [ STRING ], label: [ STRING ] ], globals: [ STRING ], member: { STRING: NUMBER }, unuseds: [ { name: STRING, line: NUMBER } ], implieds: [ { name: STRING, line: NUMBER } ], urls: [ STRING ], json: BOOLEAN } Empty arrays will not be included. */ /*jslint evil: true, nomen: false, onevar: false, regexp: false, strict: true */ /*members "\b", "\t", "\n", "\f", "\r", "!=", "!==", "\"", "%", "(begin)", "(breakage)", "(context)", "(error)", "(global)", "(identifier)", "(last)", "(line)", "(loopage)", "(name)", "(onevar)", "(params)", "(scope)", "(statement)", "(verb)", "*", "+", "++", "-", "--", "\/", "<", "<=", "==", "===", ">", ">=", ADSAFE, ActiveXObject, Array, Boolean, COM, CScript, Canvas, CustomAnimation, Date, Debug, E, Enumerator, Error, EvalError, FadeAnimation, Flash, FormField, Frame, Function, HotKey, Image, JSON, LN10, LN2, LOG10E, LOG2E, MAX_VALUE, MIN_VALUE, Math, MenuItem, MoveAnimation, NEGATIVE_INFINITY, Number, Object, Option, PI, POSITIVE_INFINITY, Point, RangeError, Rectangle, ReferenceError, RegExp, ResizeAnimation, RotateAnimation, SQRT1_2, SQRT2, ScrollBar, String, Style, SyntaxError, System, Text, TextArea, Timer, TypeError, URIError, URL, VBArray, WScript, Web, Window, XMLDOM, XMLHttpRequest, "\\", a, abbr, acronym, addEventListener, address, adsafe, alert, aliceblue, animator, antiquewhite, appleScript, applet, apply, approved, aqua, aquamarine, area, arguments, arity, article, aside, audio, autocomplete, azure, b, background, "background-attachment", "background-color", "background-image", "background-position", "background-repeat", base, bdo, beep, beige, big, bisque, bitwise, black, blanchedalmond, block, blockquote, blue, blueviolet, blur, body, border, "border-bottom", "border-bottom-color", "border-bottom-style", "border-bottom-width", "border-collapse", "border-color", "border-left", "border-left-color", "border-left-style", "border-left-width", "border-right", "border-right-color", "border-right-style", "border-right-width", "border-spacing", "border-style", "border-top", "border-top-color", "border-top-style", "border-top-width", "border-width", bottom, br, brown, browser, burlywood, button, bytesToUIString, c, cadetblue, call, callee, caller, canvas, cap, caption, "caption-side", cases, center, charAt, charCodeAt, character, chartreuse, chocolate, chooseColor, chooseFile, chooseFolder, cite, clear, clearInterval, clearTimeout, clip, close, closeWidget, closed, closure, cm, code, col, colgroup, color, command, comment, condition, confirm, console, constructor, content, convertPathToHFS, convertPathToPlatform, coral, cornflowerblue, cornsilk, "counter-increment", "counter-reset", create, crimson, css, cursor, cyan, d, darkblue, darkcyan, darkgoldenrod, darkgray, darkgreen, darkkhaki, darkmagenta, darkolivegreen, darkorange, darkorchid, darkred, darksalmon, darkseagreen, darkslateblue, darkslategray, darkturquoise, darkviolet, data, datalist, dd, debug, decodeURI, decodeURIComponent, deeppink, deepskyblue, defaultStatus, defineClass, del, deserialize, details, devel, dfn, dialog, dimension, dimgray, dir, direction, display, div, dl, document, dodgerblue, dt, edition, else, em, embed, empty, "empty-cells", encodeURI, encodeURIComponent, entityify, eqeqeq, errors, es5, escape, eval, event, evidence, evil, ex, exception, exec, exps, fieldset, figure, filesystem, firebrick, first, float, floor, floralwhite, focus, focusWidget, font, "font-face", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-weight", footer, forestgreen, forin, form, fragment, frame, frames, frameset, from, fromCharCode, fuchsia, fud, funct, function, functions, g, gainsboro, gc, getComputedStyle, ghostwhite, global, globals, gold, goldenrod, gray, green, greenyellow, h1, h2, h3, h4, h5, h6, hasOwnProperty, head, header, height, help, hgroup, history, honeydew, hotpink, hr, 'hta:application', html, i, iTunes, id, identifier, iframe, img, immed, implieds, in, include, indent, indexOf, indianred, indigo, init, input, ins, isAlpha, isApplicationRunning, isDigit, isFinite, isNaN, ivory, join, jslint, json, kbd, keygen, khaki, konfabulatorVersion, label, labelled, lang, last, lavender, lavenderblush, lawngreen, laxbreak, lbp, led, left, legend, lemonchiffon, length, "letter-spacing", li, lib, lightblue, lightcoral, lightcyan, lightgoldenrodyellow, lightgreen, lightpink, lightsalmon, lightseagreen, lightskyblue, lightslategray, lightsteelblue, lightyellow, lime, limegreen, line, "line-height", linen, link, "list-style", "list-style-image", "list-style-position", "list-style-type", load, loadClass, location, log, m, magenta, map, margin, "margin-bottom", "margin-left", "margin-right", "margin-top", mark, "marker-offset", maroon, match, "max-height", "max-width", maxerr, maxlen, md5, media, mediumaquamarine, mediumblue, mediumorchid, mediumpurple, mediumseagreen, mediumslateblue, mediumspringgreen, mediumturquoise, mediumvioletred, member, menu, message, meta, meter, midnightblue, "min-height", "min-width", mintcream, mistyrose, mm, moccasin, moveBy, moveTo, name, nav, navajowhite, navigator, navy, new, newcap, noframes, nomen, noscript, nud, object, ol, oldlace, olive, olivedrab, on, onbeforeunload, onblur, onerror, onevar, onfocus, onload, onresize, onunload, opacity, open, openURL, opener, opera, optgroup, option, orange, orangered, orchid, outer, outline, "outline-color", "outline-style", "outline-width", output, overflow, "overflow-x", "overflow-y", p, padding, "padding-bottom", "padding-left", "padding-right", "padding-top", page, "page-break-after", "page-break-before", palegoldenrod, palegreen, paleturquoise, palevioletred, papayawhip, param, parent, parseFloat, parseInt, passfail, pc, peachpuff, peru, pink, play, plum, plusplus, pop, popupMenu, position, powderblue, pre, predef, preferenceGroups, preferences, print, progress, prompt, prototype, pt, purple, push, px, q, quit, quotes, random, range, raw, reach, readFile, readUrl, reason, red, regexp, reloadWidget, removeEventListener, replace, report, reserved, resizeBy, resizeTo, resolvePath, resumeUpdates, rhino, right, rosybrown, royalblue, rp, rt, ruby, runCommand, runCommandInBg, saddlebrown, safe, salmon, samp, sandybrown, saveAs, savePreferences, screen, script, scroll, scrollBy, scrollTo, seagreen, seal, search, seashell, section, select, serialize, setInterval, setTimeout, shift, showWidgetPreferences, sienna, silver, skyblue, slateblue, slategray, sleep, slice, small, snow, sort, source, span, spawn, speak, split, springgreen, src, stack, statement, status, steelblue, strict, strong, style, styleproperty, sub, substr, sup, supplant, suppressUpdates, sync, system, table, "table-layout", tan, tbody, td, teal, tellWidget, test, "text-align", "text-decoration", "text-indent", "text-shadow", "text-transform", textarea, tfoot, th, thead, thistle, time, title, toLowerCase, toString, toUpperCase, toint32, token, tomato, top, tr, tt, turquoise, type, u, ul, undef, unescape, "unicode-bidi", unused, unwatch, updateNow, urls, value, valueOf, var, version, "vertical-align", video, violet, visibility, watch, wheat, white, "white-space", whitesmoke, widget, width, windows, "word-spacing", "word-wrap", yahooCheckLogin, yahooLogin, yahooLogout, yellow, yellowgreen, "z-index" */ // We build the application inside a function so that we produce only a single // global variable. The function will be invoked, its return value is the JSLINT // application itself. "use strict"; var JSLINT = (function () { var adsafe_id, // The widget's ADsafe id. adsafe_may, // The widget may load approved scripts. adsafe_went, // ADSAFE.go has been called. anonname, // The guessed name for anonymous functions. approved, // ADsafe approved urls. atrule = { media : true, 'font-face': true, page : true }, // These are operators that should not be used with the ! operator. bang = { '<': true, '<=': true, '==': true, '===': true, '!==': true, '!=': true, '>': true, '>=': true, '+': true, '-': true, '*': true, '/': true, '%': true }, // These are members that should not be permitted in the safe subset. banned = { // the member names that ADsafe prohibits. 'arguments' : true, callee : true, caller : true, constructor : true, 'eval' : true, prototype : true, stack : true, unwatch : true, valueOf : true, watch : true }, // These are the JSLint boolean options. boolOptions = { adsafe : true, // if ADsafe should be enforced bitwise : true, // if bitwise operators should not be allowed browser : true, // if the standard browser globals should be predefined cap : true, // if upper case HTML should be allowed css : true, // if CSS workarounds should be tolerated debug : true, // if debugger statements should be allowed devel : true, // if logging should be allowed (console, alert, etc.) eqeqeq : true, // if === should be required es5 : true, // if ES5 syntax should be allowed evil : true, // if eval should be allowed forin : true, // if for in statements must filter fragment : true, // if HTML fragments should be allowed immed : true, // if immediate invocations must be wrapped in parens laxbreak : true, // if line breaks should not be checked newcap : true, // if constructor names must be capitalized nomen : true, // if names should be checked on : true, // if HTML event handlers should be allowed onevar : true, // if only one var statement per function should be allowed passfail : true, // if the scan should stop on first error plusplus : true, // if increment/decrement should not be allowed regexp : true, // if the . should not be allowed in regexp literals rhino : true, // if the Rhino environment globals should be predefined undef : true, // if variables should be declared before used safe : true, // if use of some browser features should be restricted windows : true, // if MS Windows-specigic globals should be predefined strict : true, // require the "use strict"; pragma sub : true, // if all forms of subscript notation are tolerated white : true, // if strict whitespace rules apply widget : true // if the Yahoo Widgets globals should be predefined }, // browser contains a set of global names which are commonly provided by a // web browser environment. browser = { addEventListener: false, blur : false, clearInterval : false, clearTimeout : false, close : false, closed : false, defaultStatus : false, document : false, event : false, focus : false, frames : false, getComputedStyle: false, history : false, Image : false, length : false, location : false, moveBy : false, moveTo : false, name : false, navigator : false, onbeforeunload : true, onblur : true, onerror : true, onfocus : true, onload : true, onresize : true, onunload : true, open : false, opener : false, Option : false, parent : false, print : false, removeEventListener: false, resizeBy : false, resizeTo : false, screen : false, scroll : false, scrollBy : false, scrollTo : false, setInterval : false, setTimeout : false, status : false, top : false, XMLHttpRequest : false }, cssAttributeData, cssAny, cssColorData = { "aliceblue" : true, "antiquewhite" : true, "aqua" : true, "aquamarine" : true, "azure" : true, "beige" : true, "bisque" : true, "black" : true, "blanchedalmond" : true, "blue" : true, "blueviolet" : true, "brown" : true, "burlywood" : true, "cadetblue" : true, "chartreuse" : true, "chocolate" : true, "coral" : true, "cornflowerblue" : true, "cornsilk" : true, "crimson" : true, "cyan" : true, "darkblue" : true, "darkcyan" : true, "darkgoldenrod" : true, "darkgray" : true, "darkgreen" : true, "darkkhaki" : true, "darkmagenta" : true, "darkolivegreen" : true, "darkorange" : true, "darkorchid" : true, "darkred" : true, "darksalmon" : true, "darkseagreen" : true, "darkslateblue" : true, "darkslategray" : true, "darkturquoise" : true, "darkviolet" : true, "deeppink" : true, "deepskyblue" : true, "dimgray" : true, "dodgerblue" : true, "firebrick" : true, "floralwhite" : true, "forestgreen" : true, "fuchsia" : true, "gainsboro" : true, "ghostwhite" : true, "gold" : true, "goldenrod" : true, "gray" : true, "green" : true, "greenyellow" : true, "honeydew" : true, "hotpink" : true, "indianred" : true, "indigo" : true, "ivory" : true, "khaki" : true, "lavender" : true, "lavenderblush" : true, "lawngreen" : true, "lemonchiffon" : true, "lightblue" : true, "lightcoral" : true, "lightcyan" : true, "lightgoldenrodyellow" : true, "lightgreen" : true, "lightpink" : true, "lightsalmon" : true, "lightseagreen" : true, "lightskyblue" : true, "lightslategray" : true, "lightsteelblue" : true, "lightyellow" : true, "lime" : true, "limegreen" : true, "linen" : true, "magenta" : true, "maroon" : true, "mediumaquamarine" : true, "mediumblue" : true, "mediumorchid" : true, "mediumpurple" : true, "mediumseagreen" : true, "mediumslateblue" : true, "mediumspringgreen" : true, "mediumturquoise" : true, "mediumvioletred" : true, "midnightblue" : true, "mintcream" : true, "mistyrose" : true, "moccasin" : true, "navajowhite" : true, "navy" : true, "oldlace" : true, "olive" : true, "olivedrab" : true, "orange" : true, "orangered" : true, "orchid" : true, "palegoldenrod" : true, "palegreen" : true, "paleturquoise" : true, "palevioletred" : true, "papayawhip" : true, "peachpuff" : true, "peru" : true, "pink" : true, "plum" : true, "powderblue" : true, "purple" : true, "red" : true, "rosybrown" : true, "royalblue" : true, "saddlebrown" : true, "salmon" : true, "sandybrown" : true, "seagreen" : true, "seashell" : true, "sienna" : true, "silver" : true, "skyblue" : true, "slateblue" : true, "slategray" : true, "snow" : true, "springgreen" : true, "steelblue" : true, "tan" : true, "teal" : true, "thistle" : true, "tomato" : true, "turquoise" : true, "violet" : true, "wheat" : true, "white" : true, "whitesmoke" : true, "yellow" : true, "yellowgreen" : true }, cssBorderStyle, cssBreak, cssLengthData = { '%': true, 'cm': true, 'em': true, 'ex': true, 'in': true, 'mm': true, 'pc': true, 'pt': true, 'px': true }, cssOverflow, devel = { alert : false, confirm : false, console : false, Debug : false, opera : false, prompt : false }, escapes = { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '/' : '\\/', '\\': '\\\\' }, funct, // The current function functionicity = [ 'closure', 'exception', 'global', 'label', 'outer', 'unused', 'var' ], functions, // All of the functions global, // The global scope htmltag = { a: {}, abbr: {}, acronym: {}, address: {}, applet: {}, area: {empty: true, parent: ' map '}, article: {}, aside: {}, audio: {}, b: {}, base: {empty: true, parent: ' head '}, bdo: {}, big: {}, blockquote: {}, body: {parent: ' html noframes '}, br: {empty: true}, button: {}, canvas: {parent: ' body p div th td '}, caption: {parent: ' table '}, center: {}, cite: {}, code: {}, col: {empty: true, parent: ' table colgroup '}, colgroup: {parent: ' table '}, command: {parent: ' menu '}, datalist: {}, dd: {parent: ' dl '}, del: {}, details: {}, dialog: {}, dfn: {}, dir: {}, div: {}, dl: {}, dt: {parent: ' dl '}, em: {}, embed: {}, fieldset: {}, figure: {}, font: {}, footer: {}, form: {}, frame: {empty: true, parent: ' frameset '}, frameset: {parent: ' html frameset '}, h1: {}, h2: {}, h3: {}, h4: {}, h5: {}, h6: {}, head: {parent: ' html '}, header: {}, hgroup: {}, hr: {empty: true}, 'hta:application': {empty: true, parent: ' head '}, html: {parent: '*'}, i: {}, iframe: {}, img: {empty: true}, input: {empty: true}, ins: {}, kbd: {}, keygen: {}, label: {}, legend: {parent: ' details fieldset figure '}, li: {parent: ' dir menu ol ul '}, link: {empty: true, parent: ' head '}, map: {}, mark: {}, menu: {}, meta: {empty: true, parent: ' head noframes noscript '}, meter: {}, nav: {}, noframes: {parent: ' html body '}, noscript: {parent: ' body head noframes '}, object: {}, ol: {}, optgroup: {parent: ' select '}, option: {parent: ' optgroup select '}, output: {}, p: {}, param: {empty: true, parent: ' applet object '}, pre: {}, progress: {}, q: {}, rp: {}, rt: {}, ruby: {}, samp: {}, script: {empty: true, parent: ' body div frame head iframe p pre span '}, section: {}, select: {}, small: {}, span: {}, source: {}, strong: {}, style: {parent: ' head ', empty: true}, sub: {}, sup: {}, table: {}, tbody: {parent: ' table '}, td: {parent: ' tr '}, textarea: {}, tfoot: {parent: ' table '}, th: {parent: ' tr '}, thead: {parent: ' table '}, time: {}, title: {parent: ' head '}, tr: {parent: ' table tbody thead tfoot '}, tt: {}, u: {}, ul: {}, 'var': {}, video: {} }, ids, // HTML ids implied, // Implied globals inblock, indent, jsonmode, lines, lookahead, member, membersOnly, nexttoken, noreach, option, predefined, // Global variables defined by option prereg, prevtoken, rhino = { defineClass : false, deserialize : false, gc : false, help : false, load : false, loadClass : false, print : false, quit : false, readFile : false, readUrl : false, runCommand : false, seal : false, serialize : false, spawn : false, sync : false, toint32 : false, version : false }, scope, // The current scope windows = { ActiveXObject: false, CScript : false, Debug : false, Enumerator : false, System : false, VBArray : false, WScript : false }, src, stack, // standard contains the global names that are provided by the // ECMAScript standard. standard = { Array : false, Boolean : false, Date : false, decodeURI : false, decodeURIComponent : false, encodeURI : false, encodeURIComponent : false, Error : false, 'eval' : false, EvalError : false, Function : false, hasOwnProperty : false, isFinite : false, isNaN : false, JSON : false, Math : false, Number : false, Object : false, parseInt : false, parseFloat : false, RangeError : false, ReferenceError : false, RegExp : false, String : false, SyntaxError : false, TypeError : false, URIError : false }, standard_member = { E : true, LN2 : true, LN10 : true, LOG2E : true, LOG10E : true, PI : true, SQRT1_2 : true, SQRT2 : true, MAX_VALUE : true, MIN_VALUE : true, NEGATIVE_INFINITY : true, POSITIVE_INFINITY : true }, strict_mode, syntax = {}, tab, token, urls, warnings, // widget contains the global names which are provided to a Yahoo // (fna Konfabulator) widget. widget = { alert : true, animator : true, appleScript : true, beep : true, bytesToUIString : true, Canvas : true, chooseColor : true, chooseFile : true, chooseFolder : true, closeWidget : true, COM : true, convertPathToHFS : true, convertPathToPlatform : true, CustomAnimation : true, escape : true, FadeAnimation : true, filesystem : true, Flash : true, focusWidget : true, form : true, FormField : true, Frame : true, HotKey : true, Image : true, include : true, isApplicationRunning : true, iTunes : true, konfabulatorVersion : true, log : true, md5 : true, MenuItem : true, MoveAnimation : true, openURL : true, play : true, Point : true, popupMenu : true, preferenceGroups : true, preferences : true, print : true, prompt : true, random : true, Rectangle : true, reloadWidget : true, ResizeAnimation : true, resolvePath : true, resumeUpdates : true, RotateAnimation : true, runCommand : true, runCommandInBg : true, saveAs : true, savePreferences : true, screen : true, ScrollBar : true, showWidgetPreferences : true, sleep : true, speak : true, Style : true, suppressUpdates : true, system : true, tellWidget : true, Text : true, TextArea : true, Timer : true, unescape : true, updateNow : true, URL : true, Web : true, widget : true, Window : true, XMLDOM : true, XMLHttpRequest : true, yahooCheckLogin : true, yahooLogin : true, yahooLogout : true }, // xmode is used to adapt to the exceptions in html parsing. // It can have these states: // false .js script file // html // outer // script // style // scriptstring // styleproperty xmode, xquote, // unsafe comment or string ax = /@cc|<\/?|script|\]*s\]|<\s*!|</i, // unsafe characters that are silently deleted by one or more browsers cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/, // token tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/(\*(jslint|members?|global)?|=|\/)?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/, // html token hx = /^\s*(['"=>\/&#]|<(?:\/|\!(?:--)?)?|[a-zA-Z][a-zA-Z0-9_\-:]*|[0-9]+|--)/, // characters in strings that need escapement nx = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/, nxg = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, // outer html token ox = /[>&]|<[\/!]?|--/, // star slash lx = /\*\/|\/\*/, // identifier ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/, // javascript url jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i, // url badness ux = /&|\+|\u00AD|\.\.|\/\*|%[^;]|base64|url|expression|data|mailto/i, // style sx = /^\s*([{:#%.=,>+\[\]@()"';]|\*=?|\$=|\|=|\^=|~=|[a-zA-Z_][a-zA-Z0-9_\-]*|[0-9]+|<\/|\/\*)/, ssx = /^\s*([@#!"'};:\-%.=,+\[\]()*_]|[a-zA-Z][a-zA-Z0-9._\-]*|\/\*?|\d+(?:\.\d+)?|<\/)/, // attributes characters qx = /[^a-zA-Z0-9+\-_\/ ]/, // query characters for ids dx = /[\[\]\/\\"'*<>.&:(){}+=#]/, rx = { outer: hx, html: hx, style: sx, styleproperty: ssx }; function F() {} if (typeof Object.create !== 'function') { Object.create = function (o) { F.prototype = o; return new F(); }; } function is_own(object, name) { return Object.prototype.hasOwnProperty.call(object, name); } function combine(t, o) { var n; for (n in o) { if (is_own(o, n)) { t[n] = o[n]; } } } String.prototype.entityify = function () { return this .replace(/&/g, '&') .replace(//g, '>'); }; String.prototype.isAlpha = function () { return (this >= 'a' && this <= 'z\uffff') || (this >= 'A' && this <= 'Z\uffff'); }; String.prototype.isDigit = function () { return (this >= '0' && this <= '9'); }; String.prototype.supplant = function (o) { return this.replace(/\{([^{}]*)\}/g, function (a, b) { var r = o[b]; return typeof r === 'string' || typeof r === 'number' ? r : a; }); }; String.prototype.name = function () { // If the string looks like an identifier, then we can return it as is. // If the string contains no control characters, no quote characters, and no // backslash characters, then we can simply slap some quotes around it. // Otherwise we must also replace the offending characters with safe // sequences. if (ix.test(this)) { return this; } if (nx.test(this)) { return '"' + this.replace(nxg, function (a) { var c = escapes[a]; if (c) { return c; } return '\\u' + ('0000' + a.charCodeAt().toString(16)).slice(-4); }) + '"'; } return '"' + this + '"'; }; function assume() { if (!option.safe) { if (option.rhino) { combine(predefined, rhino); } if (option.devel) { combine(predefined, devel); } if (option.browser) { combine(predefined, browser); } if (option.windows) { combine(predefined, windows); } if (option.widget) { combine(predefined, widget); } } } // Produce an error warning. function quit(m, l, ch) { throw { name: 'JSLintError', line: l, character: ch, message: m + " (" + Math.floor((l / lines.length) * 100) + "% scanned)." }; } function warning(m, t, a, b, c, d) { var ch, l, w; t = t || nexttoken; if (t.id === '(end)') { // `~ t = token; } l = t.line || 0; ch = t.from || 0; w = { id: '(error)', raw: m, evidence: lines[l - 1] || '', line: l, character: ch, a: a, b: b, c: c, d: d }; w.reason = m.supplant(w); JSLINT.errors.push(w); if (option.passfail) { quit('Stopping. ', l, ch); } warnings += 1; if (warnings >= option.maxerr) { quit("Too many errors.", l, ch); } return w; } function warningAt(m, l, ch, a, b, c, d) { return warning(m, { line: l, from: ch }, a, b, c, d); } function error(m, t, a, b, c, d) { var w = warning(m, t, a, b, c, d); quit("Stopping, unable to continue.", w.line, w.character); } function errorAt(m, l, ch, a, b, c, d) { return error(m, { line: l, from: ch }, a, b, c, d); } // lexical analysis var lex = (function lex() { var character, from, line, s; // Private lex methods function nextLine() { var at; if (line >= lines.length) { return false; } character = 1; s = lines[line]; line += 1; at = s.search(/ \t/); if (at >= 0) { warningAt("Mixed spaces and tabs.", line, at + 1); } s = s.replace(/\t/g, tab); at = s.search(cx); if (at >= 0) { warningAt("Unsafe character.", line, at); } if (option.maxlen && option.maxlen < s.length) { warningAt("Line too long.", line, s.length); } return true; } // Produce a token object. The token inherits from a syntax symbol. function it(type, value) { var i, t; if (type === '(color)' || type === '(range)') { t = {type: type}; } else if (type === '(punctuator)' || (type === '(identifier)' && is_own(syntax, value))) { t = syntax[value] || syntax['(error)']; } else { t = syntax[type]; } t = Object.create(t); if (type === '(string)' || type === '(range)') { if (jx.test(value)) { warningAt("Script URL.", line, from); } } if (type === '(identifier)') { t.identifier = true; if (value === '__iterator__' || value === '__proto__') { errorAt("Reserved name '{a}'.", line, from, value); } else if (option.nomen && (value.charAt(0) === '_' || value.charAt(value.length - 1) === '_')) { warningAt("Unexpected {a} in '{b}'.", line, from, "dangling '_'", value); } } t.value = value; t.line = line; t.character = character; t.from = from; i = t.id; if (i !== '(endline)') { prereg = i && (('(,=:[!&|?{};'.indexOf(i.charAt(i.length - 1)) >= 0) || i === 'return'); } return t; } // Public lex methods return { init: function (source) { if (typeof source === 'string') { lines = source .replace(/\r\n/g, '\n') .replace(/\r/g, '\n') .split('\n'); } else { lines = source; } line = 0; nextLine(); from = 1; }, range: function (begin, end) { var c, value = ''; from = character; if (s.charAt(0) !== begin) { errorAt("Expected '{a}' and instead saw '{b}'.", line, character, begin, s.charAt(0)); } for (;;) { s = s.slice(1); character += 1; c = s.charAt(0); switch (c) { case '': errorAt("Missing '{a}'.", line, character, c); break; case end: s = s.slice(1); character += 1; return it('(range)', value); case xquote: case '\\': warningAt("Unexpected '{a}'.", line, character, c); } value += c; } }, // token -- this is called by advance to get the next token. token: function () { var b, c, captures, d, depth, high, i, l, low, q, t; function match(x) { var r = x.exec(s), r1; if (r) { l = r[0].length; r1 = r[1]; c = r1.charAt(0); s = s.substr(l); from = character + l - r1.length; character += l; return r1; } } function string(x) { var c, j, r = ''; if (jsonmode && x !== '"') { warningAt("Strings must use doublequote.", line, character); } if (xquote === x || (xmode === 'scriptstring' && !xquote)) { return it('(punctuator)', x); } function esc(n) { var i = parseInt(s.substr(j + 1, n), 16); j += n; if (i >= 32 && i <= 126 && i !== 34 && i !== 92 && i !== 39) { warningAt("Unnecessary escapement.", line, character); } character += n; c = String.fromCharCode(i); } j = 0; for (;;) { while (j >= s.length) { j = 0; if (xmode !== 'html' || !nextLine()) { errorAt("Unclosed string.", line, from); } } c = s.charAt(j); if (c === x) { character += 1; s = s.substr(j + 1); return it('(string)', r, x); } if (c < ' ') { if (c === '\n' || c === '\r') { break; } warningAt("Control character in string: {a}.", line, character + j, s.slice(0, j)); } else if (c === xquote) { warningAt("Bad HTML string", line, character + j); } else if (c === '<') { if (option.safe && xmode === 'html') { warningAt("ADsafe string violation.", line, character + j); } else if (s.charAt(j + 1) === '/' && (xmode || option.safe)) { warningAt("Expected '<\\/' and instead saw ' 0) { character += 1; s = s.slice(i); break; } else { if (!nextLine()) { return it('(end)', ''); } } } // t = match(rx[xmode] || tx); // if (!t) { // if (xmode === 'html') { // return it('(error)', s.charAt(0)); // } else { // t = ''; // c = ''; // while (s && s < '!') { // s = s.substr(1); // } // if (s) { // errorAt("Unexpected '{a}'.", // line, character, s.substr(0, 1)); // } // } t = match(rx[xmode] || tx); if (!t) { t = ''; c = ''; while (s && s < '!') { s = s.substr(1); } if (s) { if (xmode === 'html') { return it('(error)', s.charAt(0)); } else { errorAt("Unexpected '{a}'.", line, character, s.substr(0, 1)); } } } else { // identifier if (c.isAlpha() || c === '_' || c === '$') { return it('(identifier)', t); } // number if (c.isDigit()) { if (xmode !== 'style' && !isFinite(Number(t))) { warningAt("Bad number '{a}'.", line, character, t); } if (xmode !== 'style' && xmode !== 'styleproperty' && s.substr(0, 1).isAlpha()) { warningAt("Missing space after '{a}'.", line, character, t); } if (c === '0') { d = t.substr(1, 1); if (d.isDigit()) { if (token.id !== '.' && xmode !== 'styleproperty') { warningAt("Don't use extra leading zeros '{a}'.", line, character, t); } } else if (jsonmode && (d === 'x' || d === 'X')) { warningAt("Avoid 0x-. '{a}'.", line, character, t); } } if (t.substr(t.length - 1) === '.') { warningAt( "A trailing decimal point can be confused with a dot '{a}'.", line, character, t); } return it('(number)', t); } switch (t) { // string case '"': case "'": return string(t); // // comment case '//': if (src || (xmode && xmode !== 'script')) { warningAt("Unexpected comment.", line, character); } else if (xmode === 'script' && /<\s*\//i.test(s)) { warningAt("Unexpected <\/ in comment.", line, character); } else if ((option.safe || xmode === 'script') && ax.test(s)) { warningAt("Dangerous comment.", line, character); } s = ''; token.comment = true; break; // /* comment case '/*': if (src || (xmode && xmode !== 'script' && xmode !== 'style' && xmode !== 'styleproperty')) { warningAt("Unexpected comment.", line, character); } if (option.safe && ax.test(s)) { warningAt("ADsafe comment violation.", line, character); } for (;;) { i = s.search(lx); if (i >= 0) { break; } if (!nextLine()) { errorAt("Unclosed comment.", line, character); } else { if (option.safe && ax.test(s)) { warningAt("ADsafe comment violation.", line, character); } } } character += i + 2; if (s.substr(i, 1) === '/') { errorAt("Nested comment.", line, character); } s = s.substr(i + 2); token.comment = true; break; // /*members /*jslint /*global case '/*members': case '/*member': case '/*jslint': case '/*global': case '*/': return { value: t, type: 'special', line: line, character: character, from: from }; case '': break; // / case '/': if (token.id === '/=') { errorAt( "A regular expression literal can be confused with '/='.", line, from); } if (prereg) { depth = 0; captures = 0; l = 0; for (;;) { b = true; c = s.charAt(l); l += 1; switch (c) { case '': errorAt("Unclosed regular expression.", line, from); return; case '/': if (depth > 0) { warningAt("Unescaped '{a}'.", line, from + l, '/'); } c = s.substr(0, l - 1); q = { g: true, i: true, m: true }; while (q[s.charAt(l)] === true) { q[s.charAt(l)] = false; l += 1; } character += l; s = s.substr(l); q = s.charAt(0); if (q === '/' || q === '*') { errorAt("Confusing regular expression.", line, from); } return it('(regexp)', c); case '\\': c = s.charAt(l); if (c < ' ') { warningAt( "Unexpected control character in regular expression.", line, from + l); } else if (c === '<') { warningAt( "Unexpected escaped character '{a}' in regular expression.", line, from + l, c); } l += 1; break; case '(': depth += 1; b = false; if (s.charAt(l) === '?') { l += 1; switch (s.charAt(l)) { case ':': case '=': case '!': l += 1; break; default: warningAt( "Expected '{a}' and instead saw '{b}'.", line, from + l, ':', s.charAt(l)); } } else { captures += 1; } break; case '|': b = false; break; case ')': if (depth === 0) { warningAt("Unescaped '{a}'.", line, from + l, ')'); } else { depth -= 1; } break; case ' ': q = 1; while (s.charAt(l) === ' ') { l += 1; q += 1; } if (q > 1) { warningAt( "Spaces are hard to count. Use {{a}}.", line, from + l, q); } break; case '[': c = s.charAt(l); if (c === '^') { l += 1; if (option.regexp) { warningAt("Insecure '{a}'.", line, from + l, c); } else if (s.charAt(l) === ']') { errorAt("Unescaped '{a}'.", line, from, '^'); } } q = false; if (c === ']') { warningAt("Empty class.", line, from + l - 1); q = true; } klass: do { c = s.charAt(l); l += 1; switch (c) { case '[': case '^': warningAt("Unescaped '{a}'.", line, from + l, c); q = true; break; case '-': if (q) { q = false; } else { warningAt("Unescaped '{a}'.", line, from + l, '-'); q = true; } break; case ']': if (!q) { warningAt("Unescaped '{a}'.", line, from + l - 1, '-'); } break klass; case '\\': c = s.charAt(l); if (c < ' ') { warningAt( "Unexpected control character in regular expression.", line, from + l); } else if (c === '<') { warningAt( "Unexpected escaped character '{a}' in regular expression.", line, from + l, c); } l += 1; q = true; break; case '/': warningAt("Unescaped '{a}'.", line, from + l - 1, '/'); q = true; break; case '<': if (xmode === 'script') { c = s.charAt(l); if (c === '!' || c === '/') { warningAt( "HTML confusion in regular expression '<{a}'.", line, from + l, c); } } q = true; break; default: q = true; } } while (c); break; case '.': if (option.regexp) { warningAt("Insecure '{a}'.", line, from + l, c); } break; case ']': case '?': case '{': case '}': case '+': case '*': warningAt("Unescaped '{a}'.", line, from + l, c); break; case '<': if (xmode === 'script') { c = s.charAt(l); if (c === '!' || c === '/') { warningAt( "HTML confusion in regular expression '<{a}'.", line, from + l, c); } } } if (b) { switch (s.charAt(l)) { case '?': case '+': case '*': l += 1; if (s.charAt(l) === '?') { l += 1; } break; case '{': l += 1; c = s.charAt(l); if (c < '0' || c > '9') { warningAt( "Expected a number and instead saw '{a}'.", line, from + l, c); } l += 1; low = +c; for (;;) { c = s.charAt(l); if (c < '0' || c > '9') { break; } l += 1; low = +c + (low * 10); } high = low; if (c === ',') { l += 1; high = Infinity; c = s.charAt(l); if (c >= '0' && c <= '9') { l += 1; high = +c; for (;;) { c = s.charAt(l); if (c < '0' || c > '9') { break; } l += 1; high = +c + (high * 10); } } } if (s.charAt(l) !== '}') { warningAt( "Expected '{a}' and instead saw '{b}'.", line, from + l, '}', c); } else { l += 1; } if (s.charAt(l) === '?') { l += 1; } if (low > high) { warningAt( "'{a}' should not be greater than '{b}'.", line, from + l, low, high); } } } } c = s.substr(0, l - 1); character += l; s = s.substr(l); return it('(regexp)', c); } return it('(punctuator)', t); // punctuator case '.", line, character); } character += 3; s = s.slice(i + 3); break; case '#': if (xmode === 'html' || xmode === 'styleproperty') { for (;;) { c = s.charAt(0); if ((c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' || c > 'F')) { break; } character += 1; s = s.substr(1); t += c; } if (t.length !== 4 && t.length !== 7) { warningAt("Bad hex color '{a}'.", line, from + l, t); } return it('(color)', t); } return it('(punctuator)', t); default: if (xmode === 'outer' && c === '&') { character += 1; s = s.substr(1); for (;;) { c = s.charAt(0); character += 1; s = s.substr(1); if (c === ';') { break; } if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || c === '#')) { errorAt("Bad entity", line, from + l, character); } } break; } return it('(punctuator)', t); } } } } }; }()); function addlabel(t, type) { if (option.safe && funct['(global)'] && typeof predefined[t] !== 'boolean') { warning('ADsafe global: ' + t + '.', token); } else if (t === 'hasOwnProperty') { warning("'hasOwnProperty' is a really bad name."); } // Define t in the current function in the current scope. if (is_own(funct, t) && !funct['(global)']) { warning(funct[t] === true ? "'{a}' was used before it was defined." : "'{a}' is already defined.", nexttoken, t); } funct[t] = type; if (funct['(global)']) { global[t] = funct; if (is_own(implied, t)) { warning("'{a}' was used before it was defined.", nexttoken, t); delete implied[t]; } } else { scope[t] = funct; } } function doOption() { var b, obj, filter, o = nexttoken.value, t, v; switch (o) { case '*/': error("Unbegun comment."); break; case '/*members': case '/*member': o = '/*members'; if (!membersOnly) { membersOnly = {}; } obj = membersOnly; break; case '/*jslint': if (option.safe) { warning("ADsafe restriction."); } obj = option; filter = boolOptions; break; case '/*global': if (option.safe) { warning("ADsafe restriction."); } obj = predefined; break; default: } t = lex.token(); loop: for (;;) { for (;;) { if (t.type === 'special' && t.value === '*/') { break loop; } if (t.id !== '(endline)' && t.id !== ',') { break; } t = lex.token(); } if (t.type !== '(string)' && t.type !== '(identifier)' && o !== '/*members') { error("Bad option.", t); } v = lex.token(); if (v.id === ':') { v = lex.token(); if (obj === membersOnly) { error("Expected '{a}' and instead saw '{b}'.", t, '*/', ':'); } if (t.value === 'indent' && o === '/*jslint') { b = +v.value; if (typeof b !== 'number' || !isFinite(b) || b <= 0 || Math.floor(b) !== b) { error("Expected a small integer and instead saw '{a}'.", v, v.value); } obj.white = true; obj.indent = b; } else if (t.value === 'maxerr' && o === '/*jslint') { b = +v.value; if (typeof b !== 'number' || !isFinite(b) || b <= 0 || Math.floor(b) !== b) { error("Expected a small integer and instead saw '{a}'.", v, v.value); } obj.maxerr = b; } else if (t.value === 'maxlen' && o === '/*jslint') { b = +v.value; if (typeof b !== 'number' || !isFinite(b) || b <= 0 || Math.floor(b) !== b) { error("Expected a small integer and instead saw '{a}'.", v, v.value); } obj.maxlen = b; } else if (v.value === 'true') { obj[t.value] = true; } else if (v.value === 'false') { obj[t.value] = false; } else { error("Bad option value.", v); } t = lex.token(); } else { if (o === '/*jslint') { error("Missing option value.", t); } obj[t.value] = false; t = v; } } if (filter) { assume(); } } // We need a peek function. If it has an argument, it peeks that much farther // ahead. It is used to distinguish // for ( var i in ... // from // for ( var i = ... function peek(p) { var i = p || 0, j = 0, t; while (j <= i) { t = lookahead[j]; if (!t) { t = lookahead[j] = lex.token(); } j += 1; } return t; } // Produce the next token. It looks for programming errors. function advance(id, t) { switch (token.id) { case '(number)': if (nexttoken.id === '.') { warning( "A dot following a number can be confused with a decimal point.", token); } break; case '-': if (nexttoken.id === '-' || nexttoken.id === '--') { warning("Confusing minusses."); } break; case '+': if (nexttoken.id === '+' || nexttoken.id === '++') { warning("Confusing plusses."); } break; } if (token.type === '(string)' || token.identifier) { anonname = token.value; } if (id && nexttoken.id !== id) { if (t) { if (nexttoken.id === '(end)') { warning("Unmatched '{a}'.", t, t.id); } else { warning( "Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.", nexttoken, id, t.id, t.line, nexttoken.value); } } else if (nexttoken.type !== '(identifier)' || nexttoken.value !== id) { warning("Expected '{a}' and instead saw '{b}'.", nexttoken, id, nexttoken.value); } } prevtoken = token; token = nexttoken; for (;;) { nexttoken = lookahead.shift() || lex.token(); if (nexttoken.id === '(end)' || nexttoken.id === '(error)') { return; } if (nexttoken.type === 'special') { doOption(); } else { if (nexttoken.id !== '(endline)') { break; } } } } // This is the heart of JSLINT, the Pratt parser. In addition to parsing, it // is looking for ad hoc lint patterns. We add to Pratt's model .fud, which is // like nud except that it is only used on the first token of a statement. // Having .fud makes it much easier to define JavaScript. I retained Pratt's // nomenclature. // .nud Null denotation // .fud First null denotation // .led Left denotation // lbp Left binding power // rbp Right binding power // They are key to the parsing method called Top Down Operator Precedence. function parse(rbp, initial) { var left; if (nexttoken.id === '(end)') { error("Unexpected early end of program.", token); } advance(); if (option.safe && typeof predefined[token.value] === 'boolean' && (nexttoken.id !== '(' && nexttoken.id !== '.')) { warning('ADsafe violation.', token); } if (initial) { anonname = 'anonymous'; funct['(verb)'] = token.value; } if (initial === true && token.fud) { left = token.fud(); } else { if (token.nud) { left = token.nud(); } else { if (nexttoken.type === '(number)' && token.id === '.') { warning( "A leading decimal point can be confused with a dot: '.{a}'.", token, nexttoken.value); advance(); return token; } else { error("Expected an identifier and instead saw '{a}'.", token, token.id); } } while (rbp < nexttoken.lbp) { advance(); if (token.led) { left = token.led(left); } else { error("Expected an operator and instead saw '{a}'.", token, token.id); } } } return left; } // Functions for conformance of style. function adjacent(left, right) { left = left || token; right = right || nexttoken; if (option.white || xmode === 'styleproperty' || xmode === 'style') { if (left.character !== right.from && left.line === right.line) { warning("Unexpected space after '{a}'.", right, left.value); } } } function nobreak(left, right) { left = left || token; right = right || nexttoken; if (left.character !== right.from || left.line !== right.line) { warning("Unexpected space before '{a}'.", right, right.value); } } function nospace(left, right) { left = left || token; right = right || nexttoken; if (option.white && !left.comment) { if (left.line === right.line) { adjacent(left, right); } } } function nonadjacent(left, right) { if (option.white) { left = left || token; right = right || nexttoken; if (left.line === right.line && left.character === right.from) { warning("Missing space after '{a}'.", nexttoken, left.value); } } } function nobreaknonadjacent(left, right) { left = left || token; right = right || nexttoken; if (!option.laxbreak && left.line !== right.line) { warning("Bad line breaking before '{a}'.", right, right.id); } else if (option.white) { left = left || token; right = right || nexttoken; if (left.character === right.from) { warning("Missing space after '{a}'.", nexttoken, left.value); } } } function indentation(bias) { var i; if (option.white && nexttoken.id !== '(end)') { i = indent + (bias || 0); if (nexttoken.from !== i) { warning( "Expected '{a}' to have an indentation at {b} instead at {c}.", nexttoken, nexttoken.value, i, nexttoken.from); } } } function nolinebreak(t) { t = t || token; if (t.line !== nexttoken.line) { warning("Line breaking error '{a}'.", t, t.value); } } function comma() { if (token.line !== nexttoken.line) { if (!option.laxbreak) { warning("Bad line breaking before '{a}'.", token, nexttoken.id); } } else if (token.character !== nexttoken.from && option.white) { warning("Unexpected space after '{a}'.", nexttoken, token.value); } advance(','); nonadjacent(token, nexttoken); } // Functional constructors for making the symbols that will be inherited by // tokens. function symbol(s, p) { var x = syntax[s]; if (!x || typeof x !== 'object') { syntax[s] = x = { id: s, lbp: p, value: s }; } return x; } function delim(s) { return symbol(s, 0); } function stmt(s, f) { var x = delim(s); x.identifier = x.reserved = true; x.fud = f; return x; } function blockstmt(s, f) { var x = stmt(s, f); x.block = true; return x; } function reserveName(x) { var c = x.id.charAt(0); if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { x.identifier = x.reserved = true; } return x; } function prefix(s, f) { var x = symbol(s, 150); reserveName(x); x.nud = (typeof f === 'function') ? f : function () { this.right = parse(150); this.arity = 'unary'; if (this.id === '++' || this.id === '--') { if (option.plusplus) { warning("Unexpected use of '{a}'.", this, this.id); } else if ((!this.right.identifier || this.right.reserved) && this.right.id !== '.' && this.right.id !== '[') { warning("Bad operand.", this); } } return this; }; return x; } function type(s, f) { var x = delim(s); x.type = s; x.nud = f; return x; } function reserve(s, f) { var x = type(s, f); x.identifier = x.reserved = true; return x; } function reservevar(s, v) { return reserve(s, function () { if (typeof v === 'function') { v(this); } return this; }); } function infix(s, f, p, w) { var x = symbol(s, p); reserveName(x); x.led = function (left) { if (!w) { nobreaknonadjacent(prevtoken, token); nonadjacent(token, nexttoken); } if (typeof f === 'function') { return f(left, this); } else { this.left = left; this.right = parse(p); return this; } }; return x; } function relation(s, f) { var x = symbol(s, 100); x.led = function (left) { nobreaknonadjacent(prevtoken, token); nonadjacent(token, nexttoken); var right = parse(100); if ((left && left.id === 'NaN') || (right && right.id === 'NaN')) { warning("Use the isNaN function to compare with NaN.", this); } else if (f) { f.apply(this, [left, right]); } if (left.id === '!') { warning("Confusing use of '{a}'.", left, '!'); } if (right.id === '!') { warning("Confusing use of '{a}'.", left, '!'); } this.left = left; this.right = right; return this; }; return x; } function isPoorRelation(node) { return node && ((node.type === '(number)' && +node.value === 0) || (node.type === '(string)' && node.value === '') || node.type === 'true' || node.type === 'false' || node.type === 'undefined' || node.type === 'null'); } function assignop(s, f) { symbol(s, 20).exps = true; return infix(s, function (left, that) { var l; that.left = left; if (predefined[left.value] === false && scope[left.value]['(global)'] === true) { warning("Read only.", left); } else if (left['function']) { warning("'{a}' is a function.", left, left.value); } if (option.safe) { l = left; do { if (typeof predefined[l.value] === 'boolean') { warning('ADsafe violation.', l); } l = l.left; } while (l); } if (left) { if (left.id === '.' || left.id === '[') { if (!left.left || left.left.value === 'arguments') { warning('Bad assignment.', that); } that.right = parse(19); return that; } else if (left.identifier && !left.reserved) { if (funct[left.value] === 'exception') { warning("Do not assign to the exception parameter.", left); } that.right = parse(19); return that; } if (left === syntax['function']) { warning( "Expected an identifier in an assignment and instead saw a function invocation.", token); } } error("Bad assignment.", that); }, 20); } function bitwise(s, f, p) { var x = symbol(s, p); reserveName(x); x.led = (typeof f === 'function') ? f : function (left) { if (option.bitwise) { warning("Unexpected use of '{a}'.", this, this.id); } this.left = left; this.right = parse(p); return this; }; return x; } function bitwiseassignop(s) { symbol(s, 20).exps = true; return infix(s, function (left, that) { if (option.bitwise) { warning("Unexpected use of '{a}'.", that, that.id); } nonadjacent(prevtoken, token); nonadjacent(token, nexttoken); if (left) { if (left.id === '.' || left.id === '[' || (left.identifier && !left.reserved)) { parse(19); return that; } if (left === syntax['function']) { warning( "Expected an identifier in an assignment, and instead saw a function invocation.", token); } return that; } error("Bad assignment.", that); }, 20); } function suffix(s, f) { var x = symbol(s, 150); x.led = function (left) { if (option.plusplus) { warning("Unexpected use of '{a}'.", this, this.id); } else if ((!left.identifier || left.reserved) && left.id !== '.' && left.id !== '[') { warning("Bad operand.", this); } this.left = left; return this; }; return x; } function optionalidentifier() { if (nexttoken.identifier) { advance(); if (option.safe && banned[token.value]) { warning("ADsafe violation: '{a}'.", token, token.value); } else if (token.reserved && !option.es5) { warning("Expected an identifier and instead saw '{a}' (a reserved word).", token, token.id); } return token.value; } } function identifier() { var i = optionalidentifier(); if (i) { return i; } if (token.id === 'function' && nexttoken.id === '(') { warning("Missing name in function statement."); } else { error("Expected an identifier and instead saw '{a}'.", nexttoken, nexttoken.value); } } function reachable(s) { var i = 0, t; if (nexttoken.id !== ';' || noreach) { return; } for (;;) { t = peek(i); if (t.reach) { return; } if (t.id !== '(endline)') { if (t.id === 'function') { warning( "Inner functions should be listed at the top of the outer function.", t); break; } warning("Unreachable '{a}' after '{b}'.", t, t.value, s); break; } i += 1; } } function statement(noindent) { var i = indent, r, s = scope, t = nexttoken; // We don't like the empty statement. if (t.id === ';') { warning("Unnecessary semicolon.", t); advance(';'); return; } // Is this a labelled statement? if (t.identifier && !t.reserved && peek().id === ':') { advance(); advance(':'); scope = Object.create(s); addlabel(t.value, 'label'); if (!nexttoken.labelled) { warning("Label '{a}' on {b} statement.", nexttoken, t.value, nexttoken.value); } if (jx.test(t.value + ':')) { warning("Label '{a}' looks like a javascript url.", t, t.value); } nexttoken.label = t.value; t = nexttoken; } // Parse the statement. if (!noindent) { indentation(); } r = parse(0, true); // Look for the final semicolon. if (!t.block) { if (!r || !r.exps) { warning( "Expected an assignment or function call and instead saw an expression.", token); } else if (r.id === '(' && r.left.id === 'new') { warning("Do not use 'new' for side effects."); } if (nexttoken.id !== ';') { warningAt("Missing semicolon.", token.line, token.from + token.value.length); } else { adjacent(token, nexttoken); advance(';'); nonadjacent(token, nexttoken); } } // Restore the indentation. indent = i; scope = s; return r; } function use_strict() { if (nexttoken.value === 'use strict') { advance(); advance(';'); strict_mode = true; option.newcap = true; option.undef = true; return true; } else { return false; } } function statements(begin) { var a = [], f, p; if (begin && !use_strict() && option.strict) { warning('Missing "use strict" statement.', nexttoken); } if (option.adsafe) { switch (begin) { case 'script': if (!adsafe_may) { if (nexttoken.value !== 'ADSAFE' || peek(0).id !== '.' || (peek(1).value !== 'id' && peek(1).value !== 'go')) { error('ADsafe violation: Missing ADSAFE.id or ADSAFE.go.', nexttoken); } } if (nexttoken.value === 'ADSAFE' && peek(0).id === '.' && peek(1).value === 'id') { if (adsafe_may) { error('ADsafe violation.', nexttoken); } advance('ADSAFE'); advance('.'); advance('id'); advance('('); if (nexttoken.value !== adsafe_id) { error('ADsafe violation: id does not match.', nexttoken); } advance('(string)'); advance(')'); advance(';'); adsafe_may = true; } break; case 'lib': if (nexttoken.value === 'ADSAFE') { advance('ADSAFE'); advance('.'); advance('lib'); advance('('); advance('(string)'); comma(); f = parse(0); if (f.id !== 'function') { error('The second argument to lib must be a function.', f); } p = f.funct['(params)']; p = p && p.join(', '); if (p && p !== 'lib') { error("Expected '{a}' and instead saw '{b}'.", f, '(lib)', '(' + p + ')'); } advance(')'); advance(';'); return a; } else { error("ADsafe lib violation."); } } } while (!nexttoken.reach && nexttoken.id !== '(end)') { if (nexttoken.id === ';') { warning("Unnecessary semicolon."); advance(';'); } else { a.push(statement()); } } return a; } function block(f) { var a, b = inblock, old_indent = indent, s = scope, t; inblock = f; scope = Object.create(scope); nonadjacent(token, nexttoken); t = nexttoken; if (nexttoken.id === '{') { advance('{'); if (nexttoken.id !== '}' || token.line !== nexttoken.line) { indent += option.indent; while (!f && nexttoken.from > indent) { indent += option.indent; } if (!f) { use_strict(); } a = statements(); indent -= option.indent; indentation(); } advance('}', t); indent = old_indent; } else { warning("Expected '{a}' and instead saw '{b}'.", nexttoken, '{', nexttoken.value); noreach = true; a = [statement()]; noreach = false; } funct['(verb)'] = null; scope = s; inblock = b; if (f && (!a || a.length === 0)) { warning("Empty block."); } return a; } function countMember(m) { if (membersOnly && typeof membersOnly[m] !== 'boolean') { warning("Unexpected /*member '{a}'.", token, m); } if (typeof member[m] === 'number') { member[m] += 1; } else { member[m] = 1; } } function note_implied(token) { var name = token.value, line = token.line, a = implied[name]; if (typeof a === 'function') { a = false; } if (!a) { a = [line]; implied[name] = a; } else if (a[a.length - 1] !== line) { a.push(line); } } // CSS parsing. function cssName() { if (nexttoken.identifier) { advance(); return true; } } function cssNumber() { if (nexttoken.id === '-') { advance('-'); adjacent(); nolinebreak(); } if (nexttoken.type === '(number)') { advance('(number)'); return true; } } function cssString() { if (nexttoken.type === '(string)') { advance(); return true; } } function cssColor() { var i, number, value; if (nexttoken.identifier) { value = nexttoken.value; if (value === 'rgb' || value === 'rgba') { advance(); advance('('); for (i = 0; i < 3; i += 1) { if (i) { advance(','); } number = nexttoken.value; if (nexttoken.type !== '(number)' || number < 0) { warning("Expected a positive number and instead saw '{a}'", nexttoken, number); advance(); } else { advance(); if (nexttoken.id === '%') { advance('%'); if (number > 100) { warning("Expected a percentage and instead saw '{a}'", token, number); } } else { if (number > 255) { warning("Expected a small number and instead saw '{a}'", token, number); } } } } if (value === 'rgba') { advance(','); number = +nexttoken.value; if (nexttoken.type !== '(number)' || number < 0 || number > 1) { warning("Expected a number between 0 and 1 and instead saw '{a}'", nexttoken, number); } advance(); if (nexttoken.id === '%') { warning("Unexpected '%'."); advance('%'); } } advance(')'); return true; } else if (cssColorData[nexttoken.value] === true) { advance(); return true; } } else if (nexttoken.type === '(color)') { advance(); return true; } return false; } function cssLength() { if (nexttoken.id === '-') { advance('-'); adjacent(); nolinebreak(); } if (nexttoken.type === '(number)') { advance(); if (nexttoken.type !== '(string)' && cssLengthData[nexttoken.value] === true) { adjacent(); advance(); } else if (+token.value !== 0) { warning("Expected a linear unit and instead saw '{a}'.", nexttoken, nexttoken.value); } return true; } return false; } function cssLineHeight() { if (nexttoken.id === '-') { advance('-'); adjacent(); } if (nexttoken.type === '(number)') { advance(); if (nexttoken.type !== '(string)' && cssLengthData[nexttoken.value] === true) { adjacent(); advance(); } return true; } return false; } function cssWidth() { if (nexttoken.identifier) { switch (nexttoken.value) { case 'thin': case 'medium': case 'thick': advance(); return true; } } else { return cssLength(); } } function cssMargin() { if (nexttoken.identifier) { if (nexttoken.value === 'auto') { advance(); return true; } } else { return cssLength(); } } function cssAttr() { if (nexttoken.identifier && nexttoken.value === 'attr') { advance(); advance('('); if (!nexttoken.identifier) { warning("Expected a name and instead saw '{a}'.", nexttoken, nexttoken.value); } advance(); advance(')'); return true; } return false; } function cssCommaList() { while (nexttoken.id !== ';') { if (!cssName() && !cssString()) { warning("Expected a name and instead saw '{a}'.", nexttoken, nexttoken.value); } if (nexttoken.id !== ',') { return true; } comma(); } } function cssCounter() { if (nexttoken.identifier && nexttoken.value === 'counter') { advance(); advance('('); advance(); if (nexttoken.id === ',') { comma(); if (nexttoken.type !== '(string)') { warning("Expected a string and instead saw '{a}'.", nexttoken, nexttoken.value); } advance(); } advance(')'); return true; } if (nexttoken.identifier && nexttoken.value === 'counters') { advance(); advance('('); if (!nexttoken.identifier) { warning("Expected a name and instead saw '{a}'.", nexttoken, nexttoken.value); } advance(); if (nexttoken.id === ',') { comma(); if (nexttoken.type !== '(string)') { warning("Expected a string and instead saw '{a}'.", nexttoken, nexttoken.value); } advance(); } if (nexttoken.id === ',') { comma(); if (nexttoken.type !== '(string)') { warning("Expected a string and instead saw '{a}'.", nexttoken, nexttoken.value); } advance(); } advance(')'); return true; } return false; } function cssShape() { var i; if (nexttoken.identifier && nexttoken.value === 'rect') { advance(); advance('('); for (i = 0; i < 4; i += 1) { if (!cssLength()) { warning("Expected a number and instead saw '{a}'.", nexttoken, nexttoken.value); break; } } advance(')'); return true; } return false; } function cssUrl() { var c, url; if (nexttoken.identifier && nexttoken.value === 'url') { nexttoken = lex.range('(', ')'); url = nexttoken.value; c = url.charAt(0); if (c === '"' || c === '\'') { if (url.slice(-1) !== c) { warning("Bad url string."); } else { url = url.slice(1, -1); if (url.indexOf(c) >= 0) { warning("Bad url string."); } } } if (!url) { warning("Missing url."); } advance(); if (option.safe && ux.test(url)) { error("ADsafe URL violation."); } urls.push(url); return true; } return false; } cssAny = [cssUrl, function () { for (;;) { if (nexttoken.identifier) { switch (nexttoken.value.toLowerCase()) { case 'url': cssUrl(); break; case 'expression': warning("Unexpected expression '{a}'.", nexttoken, nexttoken.value); advance(); break; default: advance(); } } else { if (nexttoken.id === ';' || nexttoken.id === '!' || nexttoken.id === '(end)' || nexttoken.id === '}') { return true; } advance(); } } }]; cssBorderStyle = [ 'none', 'hidden', 'dotted', 'dashed', 'solid', 'double', 'ridge', 'inset', 'outset' ]; cssBreak = [ 'auto', 'always', 'avoid', 'left', 'right' ]; cssOverflow = [ 'auto', 'hidden', 'scroll', 'visible' ]; cssAttributeData = { background: [ true, 'background-attachment', 'background-color', 'background-image', 'background-position', 'background-repeat' ], 'background-attachment': ['scroll', 'fixed'], 'background-color': ['transparent', cssColor], 'background-image': ['none', cssUrl], 'background-position': [ 2, [cssLength, 'top', 'bottom', 'left', 'right', 'center'] ], 'background-repeat': [ 'repeat', 'repeat-x', 'repeat-y', 'no-repeat' ], 'border': [true, 'border-color', 'border-style', 'border-width'], 'border-bottom': [ true, 'border-bottom-color', 'border-bottom-style', 'border-bottom-width' ], 'border-bottom-color': cssColor, 'border-bottom-style': cssBorderStyle, 'border-bottom-width': cssWidth, 'border-collapse': ['collapse', 'separate'], 'border-color': ['transparent', 4, cssColor], 'border-left': [ true, 'border-left-color', 'border-left-style', 'border-left-width' ], 'border-left-color': cssColor, 'border-left-style': cssBorderStyle, 'border-left-width': cssWidth, 'border-right': [ true, 'border-right-color', 'border-right-style', 'border-right-width' ], 'border-right-color': cssColor, 'border-right-style': cssBorderStyle, 'border-right-width': cssWidth, 'border-spacing': [2, cssLength], 'border-style': [4, cssBorderStyle], 'border-top': [ true, 'border-top-color', 'border-top-style', 'border-top-width' ], 'border-top-color': cssColor, 'border-top-style': cssBorderStyle, 'border-top-width': cssWidth, 'border-width': [4, cssWidth], bottom: [cssLength, 'auto'], 'caption-side' : ['bottom', 'left', 'right', 'top'], clear: ['both', 'left', 'none', 'right'], clip: [cssShape, 'auto'], color: cssColor, content: [ 'open-quote', 'close-quote', 'no-open-quote', 'no-close-quote', cssString, cssUrl, cssCounter, cssAttr ], 'counter-increment': [ cssName, 'none' ], 'counter-reset': [ cssName, 'none' ], cursor: [ cssUrl, 'auto', 'crosshair', 'default', 'e-resize', 'help', 'move', 'n-resize', 'ne-resize', 'nw-resize', 'pointer', 's-resize', 'se-resize', 'sw-resize', 'w-resize', 'text', 'wait' ], direction: ['ltr', 'rtl'], display: [ 'block', 'compact', 'inline', 'inline-block', 'inline-table', 'list-item', 'marker', 'none', 'run-in', 'table', 'table-caption', 'table-cell', 'table-column', 'table-column-group', 'table-footer-group', 'table-header-group', 'table-row', 'table-row-group' ], 'empty-cells': ['show', 'hide'], 'float': ['left', 'none', 'right'], font: [ 'caption', 'icon', 'menu', 'message-box', 'small-caption', 'status-bar', true, 'font-size', 'font-style', 'font-weight', 'font-family' ], 'font-family': cssCommaList, 'font-size': [ 'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large', 'larger', 'smaller', cssLength ], 'font-size-adjust': ['none', cssNumber], 'font-stretch': [ 'normal', 'wider', 'narrower', 'ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', 'semi-expanded', 'expanded', 'extra-expanded' ], 'font-style': [ 'normal', 'italic', 'oblique' ], 'font-variant': [ 'normal', 'small-caps' ], 'font-weight': [ 'normal', 'bold', 'bolder', 'lighter', cssNumber ], height: [cssLength, 'auto'], left: [cssLength, 'auto'], 'letter-spacing': ['normal', cssLength], 'line-height': ['normal', cssLineHeight], 'list-style': [ true, 'list-style-image', 'list-style-position', 'list-style-type' ], 'list-style-image': ['none', cssUrl], 'list-style-position': ['inside', 'outside'], 'list-style-type': [ 'circle', 'disc', 'square', 'decimal', 'decimal-leading-zero', 'lower-roman', 'upper-roman', 'lower-greek', 'lower-alpha', 'lower-latin', 'upper-alpha', 'upper-latin', 'hebrew', 'katakana', 'hiragana-iroha', 'katakana-oroha', 'none' ], margin: [4, cssMargin], 'margin-bottom': cssMargin, 'margin-left': cssMargin, 'margin-right': cssMargin, 'margin-top': cssMargin, 'marker-offset': [cssLength, 'auto'], 'max-height': [cssLength, 'none'], 'max-width': [cssLength, 'none'], 'min-height': cssLength, 'min-width': cssLength, opacity: cssNumber, outline: [true, 'outline-color', 'outline-style', 'outline-width'], 'outline-color': ['invert', cssColor], 'outline-style': [ 'dashed', 'dotted', 'double', 'groove', 'inset', 'none', 'outset', 'ridge', 'solid' ], 'outline-width': cssWidth, overflow: cssOverflow, 'overflow-x': cssOverflow, 'overflow-y': cssOverflow, padding: [4, cssLength], 'padding-bottom': cssLength, 'padding-left': cssLength, 'padding-right': cssLength, 'padding-top': cssLength, 'page-break-after': cssBreak, 'page-break-before': cssBreak, position: ['absolute', 'fixed', 'relative', 'static'], quotes: [8, cssString], right: [cssLength, 'auto'], 'table-layout': ['auto', 'fixed'], 'text-align': ['center', 'justify', 'left', 'right'], 'text-decoration': [ 'none', 'underline', 'overline', 'line-through', 'blink' ], 'text-indent': cssLength, 'text-shadow': ['none', 4, [cssColor, cssLength]], 'text-transform': ['capitalize', 'uppercase', 'lowercase', 'none'], top: [cssLength, 'auto'], 'unicode-bidi': ['normal', 'embed', 'bidi-override'], 'vertical-align': [ 'baseline', 'bottom', 'sub', 'super', 'top', 'text-top', 'middle', 'text-bottom', cssLength ], visibility: ['visible', 'hidden', 'collapse'], 'white-space': [ 'normal', 'nowrap', 'pre', 'pre-line', 'pre-wrap', 'inherit' ], width: [cssLength, 'auto'], 'word-spacing': ['normal', cssLength], 'word-wrap': ['break-word', 'normal'], 'z-index': ['auto', cssNumber] }; function styleAttribute() { var v; while (nexttoken.id === '*' || nexttoken.id === '#' || nexttoken.value === '_') { if (!option.css) { warning("Unexpected '{a}'.", nexttoken, nexttoken.value); } advance(); } if (nexttoken.id === '-') { if (!option.css) { warning("Unexpected '{a}'.", nexttoken, nexttoken.value); } advance('-'); if (!nexttoken.identifier) { warning( "Expected a non-standard style attribute and instead saw '{a}'.", nexttoken, nexttoken.value); } advance(); return cssAny; } else { if (!nexttoken.identifier) { warning("Excepted a style attribute, and instead saw '{a}'.", nexttoken, nexttoken.value); } else { if (is_own(cssAttributeData, nexttoken.value)) { v = cssAttributeData[nexttoken.value]; } else { v = cssAny; if (!option.css) { warning("Unrecognized style attribute '{a}'.", nexttoken, nexttoken.value); } } } advance(); return v; } } function styleValue(v) { var i = 0, n, once, match, round, start = 0, vi; switch (typeof v) { case 'function': return v(); case 'string': if (nexttoken.identifier && nexttoken.value === v) { advance(); return true; } return false; } for (;;) { if (i >= v.length) { return false; } vi = v[i]; i += 1; if (vi === true) { break; } else if (typeof vi === 'number') { n = vi; vi = v[i]; i += 1; } else { n = 1; } match = false; while (n > 0) { if (styleValue(vi)) { match = true; n -= 1; } else { break; } } if (match) { return true; } } start = i; once = []; for (;;) { round = false; for (i = start; i < v.length; i += 1) { if (!once[i]) { if (styleValue(cssAttributeData[v[i]])) { match = true; round = true; once[i] = true; break; } } } if (!round) { return match; } } } function styleChild() { if (nexttoken.id === '(number)') { advance(); if (nexttoken.value === 'n' && nexttoken.identifier) { adjacent(); advance(); if (nexttoken.id === '+') { adjacent(); advance('+'); adjacent(); advance('(number)'); } } return; } else { switch (nexttoken.value) { case 'odd': case 'even': if (nexttoken.identifier) { advance(); return; } } } warning("Unexpected token '{a}'.", nexttoken, nexttoken.value); } function substyle() { var v; for (;;) { if (nexttoken.id === '}' || nexttoken.id === '(end)' || xquote && nexttoken.id === xquote) { return; } while (nexttoken.id === ';') { warning("Misplaced ';'."); advance(';'); } v = styleAttribute(); advance(':'); if (nexttoken.identifier && nexttoken.value === 'inherit') { advance(); } else { if (!styleValue(v)) { warning("Unexpected token '{a}'.", nexttoken, nexttoken.value); advance(); } } if (nexttoken.id === '!') { advance('!'); adjacent(); if (nexttoken.identifier && nexttoken.value === 'important') { advance(); } else { warning("Expected '{a}' and instead saw '{b}'.", nexttoken, 'important', nexttoken.value); } } if (nexttoken.id === '}' || nexttoken.id === xquote) { warning("Missing '{a}'.", nexttoken, ';'); } else { advance(';'); } } } function styleSelector() { if (nexttoken.identifier) { if (!is_own(htmltag, option.cap ? nexttoken.value.toLowerCase() : nexttoken.value)) { warning("Expected a tagName, and instead saw {a}.", nexttoken, nexttoken.value); } advance(); } else { switch (nexttoken.id) { case '>': case '+': advance(); styleSelector(); break; case ':': advance(':'); switch (nexttoken.value) { case 'active': case 'after': case 'before': case 'checked': case 'disabled': case 'empty': case 'enabled': case 'first-child': case 'first-letter': case 'first-line': case 'first-of-type': case 'focus': case 'hover': case 'last-child': case 'last-of-type': case 'link': case 'only-of-type': case 'root': case 'target': case 'visited': advance(); break; case 'lang': advance(); advance('('); if (!nexttoken.identifier) { warning("Expected a lang code, and instead saw :{a}.", nexttoken, nexttoken.value); } advance(')'); break; case 'nth-child': case 'nth-last-child': case 'nth-last-of-type': case 'nth-of-type': advance(); advance('('); styleChild(); advance(')'); break; case 'not': advance(); advance('('); if (nexttoken.id === ':' && peek(0).value === 'not') { warning("Nested not."); } styleSelector(); advance(')'); break; default: warning("Expected a pseudo, and instead saw :{a}.", nexttoken, nexttoken.value); } break; case '#': advance('#'); if (!nexttoken.identifier) { warning("Expected an id, and instead saw #{a}.", nexttoken, nexttoken.value); } advance(); break; case '*': advance('*'); break; case '.': advance('.'); if (!nexttoken.identifier) { warning("Expected a class, and instead saw #.{a}.", nexttoken, nexttoken.value); } advance(); break; case '[': advance('['); if (!nexttoken.identifier) { warning("Expected an attribute, and instead saw [{a}].", nexttoken, nexttoken.value); } advance(); if (nexttoken.id === '=' || nexttoken.value === '~=' || nexttoken.value === '$=' || nexttoken.value === '|=' || nexttoken.id === '*=' || nexttoken.id === '^=') { advance(); if (nexttoken.type !== '(string)') { warning("Expected a string, and instead saw {a}.", nexttoken, nexttoken.value); } advance(); } advance(']'); break; default: error("Expected a CSS selector, and instead saw {a}.", nexttoken, nexttoken.value); } } } function stylePattern() { var name; if (nexttoken.id === '{') { warning("Expected a style pattern, and instead saw '{a}'.", nexttoken, nexttoken.id); } else if (nexttoken.id === '@') { advance('@'); name = nexttoken.value; if (nexttoken.identifier && atrule[name] === true) { advance(); return name; } warning("Expected an at-rule, and instead saw @{a}.", nexttoken, name); } for (;;) { styleSelector(); if (nexttoken.id === ' fragments and .js files.", token); } if (option.fragment) { if (n !== 'div') { error("ADsafe violation: Wrap the widget in a div.", token); } } else { error("Use the fragment option.", token); } } option.browser = true; assume(); } function doAttribute(n, a, v) { var u, x; if (a === 'id') { u = typeof v === 'string' ? v.toUpperCase() : ''; if (ids[u] === true) { warning("Duplicate id='{a}'.", nexttoken, v); } if (!/^[A-Za-z][A-Za-z0-9._:\-]*$/.test(v)) { warning("Bad id: '{a}'.", nexttoken, v); } else if (option.adsafe) { if (adsafe_id) { if (v.slice(0, adsafe_id.length) !== adsafe_id) { warning("ADsafe violation: An id must have a '{a}' prefix", nexttoken, adsafe_id); } else if (!/^[A-Z]+_[A-Z]+$/.test(v)) { warning("ADSAFE violation: bad id."); } } else { adsafe_id = v; if (!/^[A-Z]+_$/.test(v)) { warning("ADSAFE violation: bad id."); } } } x = v.search(dx); if (x >= 0) { warning("Unexpected character '{a}' in {b}.", token, v.charAt(x), a); } ids[u] = true; } else if (a === 'class' || a === 'type' || a === 'name') { x = v.search(qx); if (x >= 0) { warning("Unexpected character '{a}' in {b}.", token, v.charAt(x), a); } ids[u] = true; } else if (a === 'href' || a === 'background' || a === 'content' || a === 'data' || a.indexOf('src') >= 0 || a.indexOf('url') >= 0) { if (option.safe && ux.test(v)) { error("ADsafe URL violation."); } urls.push(v); } else if (a === 'for') { if (option.adsafe) { if (adsafe_id) { if (v.slice(0, adsafe_id.length) !== adsafe_id) { warning("ADsafe violation: An id must have a '{a}' prefix", nexttoken, adsafe_id); } else if (!/^[A-Z]+_[A-Z]+$/.test(v)) { warning("ADSAFE violation: bad id."); } } else { warning("ADSAFE violation: bad id."); } } } else if (a === 'name') { if (option.adsafe && v.indexOf('_') >= 0) { warning("ADsafe name violation."); } } } function doTag(n, a) { var i, t = htmltag[n], x; src = false; if (!t) { error("Unrecognized tag '<{a}>'.", nexttoken, n === n.toLowerCase() ? n : n + ' (capitalization error)'); } if (stack.length > 0) { if (n === 'html') { error("Too many tags.", token); } x = t.parent; if (x) { if (x.indexOf(' ' + stack[stack.length - 1].name + ' ') < 0) { error("A '<{a}>' must be within '<{b}>'.", token, n, x); } } else if (!option.adsafe && !option.fragment) { i = stack.length; do { if (i <= 0) { error("A '<{a}>' must be within '<{b}>'.", token, n, 'body'); } i -= 1; } while (stack[i].name !== 'body'); } } switch (n) { case 'div': if (option.adsafe && stack.length === 1 && !adsafe_id) { warning("ADSAFE violation: missing ID_."); } break; case 'script': xmode = 'script'; advance('>'); indent = nexttoken.from; if (a.lang) { warning("lang is deprecated.", token); } if (option.adsafe && stack.length !== 1) { warning("ADsafe script placement violation.", token); } if (a.src) { if (option.adsafe && (!adsafe_may || !approved[a.src])) { warning("ADsafe unapproved script source.", token); } if (a.type) { warning("type is unnecessary.", token); } } else { if (adsafe_went) { error("ADsafe script violation.", token); } statements('script'); } xmode = 'html'; advance(''); styles(); xmode = 'html'; advance(''; } function html() { var a, attributes, e, n, q, t, v, w = option.white, wmode; xmode = 'html'; xquote = ''; stack = null; for (;;) { switch (nexttoken.value) { case '<': xmode = 'html'; advance('<'); attributes = {}; t = nexttoken; if (!t.identifier) { warning("Bad identifier {a}.", t, t.value); } n = t.value; if (option.cap) { n = n.toLowerCase(); } t.name = n; advance(); if (!stack) { stack = []; doBegin(n); } v = htmltag[n]; if (typeof v !== 'object') { error("Unrecognized tag '<{a}>'.", t, n); } e = v.empty; t.type = n; for (;;) { if (nexttoken.id === '/') { advance('/'); if (nexttoken.id !== '>') { warning("Expected '{a}' and instead saw '{b}'.", nexttoken, '>', nexttoken.value); } break; } if (nexttoken.id && nexttoken.id.substr(0, 1) === '>') { break; } if (!nexttoken.identifier) { if (nexttoken.id === '(end)' || nexttoken.id === '(error)') { error("Missing '>'.", nexttoken); } warning("Bad identifier."); } option.white = true; nonadjacent(token, nexttoken); a = nexttoken.value; option.white = w; advance(); if (!option.cap && a !== a.toLowerCase()) { warning("Attribute '{a}' not all lower case.", nexttoken, a); } a = a.toLowerCase(); xquote = ''; if (is_own(attributes, a)) { warning("Attribute '{a}' repeated.", nexttoken, a); } if (a.slice(0, 2) === 'on') { if (!option.on) { warning("Avoid HTML event handlers."); } xmode = 'scriptstring'; advance('='); q = nexttoken.id; if (q !== '"' && q !== "'") { error("Missing quote."); } xquote = q; wmode = option.white; option.white = false; advance(q); statements('on'); option.white = wmode; if (nexttoken.id !== q) { error("Missing close quote on script attribute."); } xmode = 'html'; xquote = ''; advance(q); v = false; } else if (a === 'style') { xmode = 'scriptstring'; advance('='); q = nexttoken.id; if (q !== '"' && q !== "'") { error("Missing quote."); } xmode = 'styleproperty'; xquote = q; advance(q); substyle(); xmode = 'html'; xquote = ''; advance(q); v = false; } else { if (nexttoken.id === '=') { advance('='); v = nexttoken.value; if (!nexttoken.identifier && nexttoken.id !== '"' && nexttoken.id !== '\'' && nexttoken.type !== '(string)' && nexttoken.type !== '(number)' && nexttoken.type !== '(color)') { warning("Expected an attribute value and instead saw '{a}'.", token, a); } advance(); } else { v = true; } } attributes[a] = v; doAttribute(n, a, v); } doTag(n, attributes); if (!e) { stack.push(t); } xmode = 'outer'; advance('>'); break; case '') { error("Missing '{a}'.", nexttoken, '>'); } xmode = 'outer'; advance('>'); break; case '' || nexttoken.id === '(end)') { break; } if (nexttoken.value.indexOf('--') >= 0) { error("Unexpected --."); } if (nexttoken.value.indexOf('<') >= 0) { error("Unexpected <."); } if (nexttoken.value.indexOf('>') >= 0) { error("Unexpected >."); } } xmode = 'outer'; advance('>'); break; case '(end)': return; default: if (nexttoken.id === '(end)') { error("Missing '{a}'.", nexttoken, ''); } else { advance(); } } if (stack && stack.length === 0 && (option.adsafe || !option.fragment || nexttoken.id === '(end)')) { break; } } if (nexttoken.id !== '(end)') { error("Unexpected material after the end."); } } // Build the syntax table by declaring the syntactic elements of the language. type('(number)', function () { return this; }); type('(string)', function () { return this; }); syntax['(identifier)'] = { type: '(identifier)', lbp: 0, identifier: true, nud: function () { var v = this.value, s = scope[v], f; if (typeof s === 'function') { // Protection against accidental inheritance. s = undefined; } else if (typeof s === 'boolean') { f = funct; funct = functions[0]; addlabel(v, 'var'); s = funct; funct = f; } // The name is in scope and defined in the current function. if (funct === s) { // Change 'unused' to 'var', and reject labels. switch (funct[v]) { case 'unused': funct[v] = 'var'; break; case 'unction': funct[v] = 'function'; this['function'] = true; break; case 'function': this['function'] = true; break; case 'label': warning("'{a}' is a statement label.", token, v); break; } // The name is not defined in the function. If we are in the global scope, // then we have an undefined variable. } else if (funct['(global)']) { if (option.undef && predefined[v] !== 'boolean') { warning("'{a}' is not defined.", token, v); } note_implied(token); // If the name is already defined in the current // function, but not as outer, then there is a scope error. } else { switch (funct[v]) { case 'closure': case 'function': case 'var': case 'unused': warning("'{a}' used out of scope.", token, v); break; case 'label': warning("'{a}' is a statement label.", token, v); break; case 'outer': case 'global': break; default: // If the name is defined in an outer function, make an outer entry, and if // it was unused, make it var. if (s === true) { funct[v] = true; } else if (s === null) { warning("'{a}' is not allowed.", token, v); note_implied(token); } else if (typeof s !== 'object') { if (option.undef) { warning("'{a}' is not defined.", token, v); } else { funct[v] = true; } note_implied(token); } else { switch (s[v]) { case 'function': case 'unction': this['function'] = true; s[v] = 'closure'; funct[v] = s['(global)'] ? 'global' : 'outer'; break; case 'var': case 'unused': s[v] = 'closure'; funct[v] = s['(global)'] ? 'global' : 'outer'; break; case 'closure': case 'parameter': funct[v] = s['(global)'] ? 'global' : 'outer'; break; case 'label': warning("'{a}' is a statement label.", token, v); } } } } return this; }, led: function () { error("Expected an operator and instead saw '{a}'.", nexttoken, nexttoken.value); } }; type('(regexp)', function () { return this; }); // ECMAScript parser delim('(endline)'); delim('(begin)'); delim('(end)').reach = true; delim(''); delim('(error)').reach = true; delim('}').reach = true; delim(')'); delim(']'); delim('"').reach = true; delim("'").reach = true; delim(';'); delim(':').reach = true; delim(','); delim('#'); delim('@'); reserve('else'); reserve('case').reach = true; reserve('catch'); reserve('default').reach = true; reserve('finally'); reservevar('arguments', function (x) { if (strict_mode && funct['(global)']) { warning("Strict violation.", x); } else if (option.safe) { warning("ADsafe violation.", x); } }); reservevar('eval', function (x) { if (option.safe) { warning("ADsafe violation.", x); } }); reservevar('false'); reservevar('Infinity'); reservevar('NaN'); reservevar('null'); reservevar('this', function (x) { if (strict_mode && ((funct['(statement)'] && funct['(name)'].charAt(0) > 'Z') || funct['(global)'])) { warning("Strict violation.", x); } else if (option.safe) { warning("ADsafe violation.", x); } }); reservevar('true'); reservevar('undefined'); assignop('=', 'assign', 20); assignop('+=', 'assignadd', 20); assignop('-=', 'assignsub', 20); assignop('*=', 'assignmult', 20); assignop('/=', 'assigndiv', 20).nud = function () { error("A regular expression literal can be confused with '/='."); }; assignop('%=', 'assignmod', 20); bitwiseassignop('&=', 'assignbitand', 20); bitwiseassignop('|=', 'assignbitor', 20); bitwiseassignop('^=', 'assignbitxor', 20); bitwiseassignop('<<=', 'assignshiftleft', 20); bitwiseassignop('>>=', 'assignshiftright', 20); bitwiseassignop('>>>=', 'assignshiftrightunsigned', 20); infix('?', function (left, that) { that.left = left; that.right = parse(10); advance(':'); that['else'] = parse(10); return that; }, 30); infix('||', 'or', 40); infix('&&', 'and', 50); bitwise('|', 'bitor', 70); bitwise('^', 'bitxor', 80); bitwise('&', 'bitand', 90); relation('==', function (left, right) { if (option.eqeqeq) { warning("Expected '{a}' and instead saw '{b}'.", this, '===', '=='); } else if (isPoorRelation(left)) { warning("Use '{a}' to compare with '{b}'.", this, '===', left.value); } else if (isPoorRelation(right)) { warning("Use '{a}' to compare with '{b}'.", this, '===', right.value); } return this; }); relation('==='); relation('!=', function (left, right) { if (option.eqeqeq) { warning("Expected '{a}' and instead saw '{b}'.", this, '!==', '!='); } else if (isPoorRelation(left)) { warning("Use '{a}' to compare with '{b}'.", this, '!==', left.value); } else if (isPoorRelation(right)) { warning("Use '{a}' to compare with '{b}'.", this, '!==', right.value); } return this; }); relation('!=='); relation('<'); relation('>'); relation('<='); relation('>='); bitwise('<<', 'shiftleft', 120); bitwise('>>', 'shiftright', 120); bitwise('>>>', 'shiftrightunsigned', 120); infix('in', 'in', 120); infix('instanceof', 'instanceof', 120); infix('+', function (left, that) { var right = parse(130); if (left && right && left.id === '(string)' && right.id === '(string)') { left.value += right.value; left.character = right.character; if (jx.test(left.value)) { warning("JavaScript URL.", left); } return left; } that.left = left; that.right = right; return that; }, 130); prefix('+', 'num'); prefix('+++', function () { warning("Confusing pluses."); this.right = parse(150); this.arity = 'unary'; return this; }); infix('+++', function (left) { warning("Confusing pluses."); this.left = left; this.right = parse(130); return this; }, 130); infix('-', 'sub', 130); prefix('-', 'neg'); prefix('---', function () { warning("Confusing minuses."); this.right = parse(150); this.arity = 'unary'; return this; }); infix('---', function (left) { warning("Confusing minuses."); this.left = left; this.right = parse(130); return this; }, 130); infix('*', 'mult', 140); infix('/', 'div', 140); infix('%', 'mod', 140); suffix('++', 'postinc'); prefix('++', 'preinc'); syntax['++'].exps = true; suffix('--', 'postdec'); prefix('--', 'predec'); syntax['--'].exps = true; prefix('delete', function () { var p = parse(0); if (!p || (p.id !== '.' && p.id !== '[')) { warning("Variables should not be deleted."); } this.first = p; return this; }).exps = true; prefix('~', function () { if (option.bitwise) { warning("Unexpected '{a}'.", this, '~'); } parse(150); return this; }); prefix('!', function () { this.right = parse(150); this.arity = 'unary'; if (bang[this.right.id] === true) { warning("Confusing use of '{a}'.", this, '!'); } return this; }); prefix('typeof', 'typeof'); prefix('new', function () { var c = parse(155), i; if (c && c.id !== 'function') { if (c.identifier) { c['new'] = true; switch (c.value) { case 'Object': warning("Use the object literal notation {}.", token); break; case 'Array': if (nexttoken.id !== '(') { warning("Use the array literal notation [].", token); } else { advance('('); if (nexttoken.id === ')') { warning("Use the array literal notation [].", token); } else { i = parse(0); c.dimension = i; if ((i.id === '(number)' && /[.+\-Ee]/.test(i.value)) || (i.id === '-' && !i.right) || i.id === '(string)' || i.id === '[' || i.id === '{' || i.id === 'true' || i.id === 'false' || i.id === 'null' || i.id === 'undefined' || i.id === 'Infinity') { warning("Use the array literal notation [].", token); } if (nexttoken.id !== ')') { error("Use the array literal notation [].", token); } } advance(')'); } this.first = c; return this; case 'Number': case 'String': case 'Boolean': case 'Math': case 'JSON': warning("Do not use {a} as a constructor.", token, c.value); break; case 'Function': if (!option.evil) { warning("The Function constructor is eval."); } break; case 'Date': case 'RegExp': break; default: if (c.id !== 'function') { i = c.value.substr(0, 1); if (option.newcap && (i < 'A' || i > 'Z')) { warning( "A constructor name should start with an uppercase letter.", token); } } } } else { if (c.id !== '.' && c.id !== '[' && c.id !== '(') { warning("Bad constructor.", token); } } } else { warning("Weird construction. Delete 'new'.", this); } adjacent(token, nexttoken); if (nexttoken.id !== '(') { warning("Missing '()' invoking a constructor."); } this.first = c; return this; }); syntax['new'].exps = true; infix('.', function (left, that) { adjacent(prevtoken, token); nobreak(); var m = identifier(); if (typeof m === 'string') { countMember(m); } that.left = left; that.right = m; if (left && left.value === 'arguments' && (m === 'callee' || m === 'caller')) { warning("Avoid arguments.{a}.", left, m); } else if (!option.evil && left && left.value === 'document' && (m === 'write' || m === 'writeln')) { warning("document.write can be a form of eval.", left); } else if (option.adsafe) { if (left && left.value === 'ADSAFE') { if (m === 'id' || m === 'lib') { warning("ADsafe violation.", that); } else if (m === 'go') { if (xmode !== 'script') { warning("ADsafe violation.", that); } else if (adsafe_went || nexttoken.id !== '(' || peek(0).id !== '(string)' || peek(0).value !== adsafe_id || peek(1).id !== ',') { error("ADsafe violation: go.", that); } adsafe_went = true; adsafe_may = false; } } } if (!option.evil && (m === 'eval' || m === 'execScript')) { warning('eval is evil.'); } else if (option.safe) { for (;;) { if (banned[m] === true) { warning("ADsafe restricted word '{a}'.", token, m); } if (typeof predefined[left.value] !== 'boolean' || nexttoken.id === '(') { break; } if (standard_member[m] === true) { if (nexttoken.id === '.') { warning("ADsafe violation.", that); } break; } if (nexttoken.id !== '.') { warning("ADsafe violation.", that); break; } advance('.'); token.left = that; token.right = m; that = token; m = identifier(); if (typeof m === 'string') { countMember(m); } } } return that; }, 160, true); infix('(', function (left, that) { nobreak(prevtoken, token); nospace(); if (option.immed && !left.immed && left.id === 'function') { warning("Wrap an immediate function invocation in parentheses " + "to assist the reader in understanding that the expression " + "is the result of a function, and not the function itself."); } var n = 0, p = []; if (left) { if (left.type === '(identifier)') { if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) { if (left.value !== 'Number' && left.value !== 'String' && left.value !== 'Boolean' && left.value !== 'Date') { if (left.value === 'Math') { warning("Math is not a function.", left); } else if (option.newcap) { warning( "Missing 'new' prefix when invoking a constructor.", left); } } } } else if (left.id === '.') { if (option.safe && left.left.value === 'Math' && left.right === 'random') { warning("ADsafe violation.", left); } } } if (nexttoken.id !== ')') { for (;;) { p[p.length] = parse(10); n += 1; if (nexttoken.id !== ',') { break; } comma(); } } advance(')'); nospace(prevtoken, token); if (typeof left === 'object') { if (left.value === 'parseInt' && n === 1) { warning("Missing radix parameter.", left); } if (!option.evil) { if (left.value === 'eval' || left.value === 'Function' || left.value === 'execScript') { warning("eval is evil.", left); } else if (p[0] && p[0].id === '(string)' && (left.value === 'setTimeout' || left.value === 'setInterval')) { warning( "Implied eval is evil. Pass a function instead of a string.", left); } } if (!left.identifier && left.id !== '.' && left.id !== '[' && left.id !== '(' && left.id !== '&&' && left.id !== '||' && left.id !== '?') { warning("Bad invocation.", left); } } that.left = left; return that; }, 155, true).exps = true; prefix('(', function () { nospace(); if (nexttoken.id === 'function') { nexttoken.immed = true; } var v = parse(0); advance(')', this); nospace(prevtoken, token); if (option.immed && v.id === 'function') { if (nexttoken.id === '(') { warning( "Move the invocation into the parens that contain the function.", nexttoken); } else { warning( "Do not wrap function literals in parens unless they are to be immediately invoked.", this); } } return v; }); infix('[', function (left, that) { nobreak(prevtoken, token); nospace(); var e = parse(0), s; if (e && e.type === '(string)') { if (option.safe && banned[e.value] === true) { warning("ADsafe restricted word '{a}'.", that, e.value); } else if (!option.evil && (e.value === 'eval' || e.value === 'execScript')) { warning("eval is evil.", that); } else if (option.safe && (e.value.charAt(0) === '_' || e.value.charAt(0) === '-')) { warning("ADsafe restricted subscript '{a}'.", that, e.value); } countMember(e.value); if (!option.sub && ix.test(e.value)) { s = syntax[e.value]; if (!s || !s.reserved) { warning("['{a}'] is better written in dot notation.", e, e.value); } } } else if (!e || e.type !== '(number)' || e.value < 0) { if (option.safe) { warning('ADsafe subscripting.'); } } advance(']', that); nospace(prevtoken, token); that.left = left; that.right = e; return that; }, 160, true); prefix('[', function () { var b = token.line !== nexttoken.line; this.first = []; if (b) { indent += option.indent; if (nexttoken.from === indent + option.indent) { indent += option.indent; } } while (nexttoken.id !== '(end)') { while (nexttoken.id === ',') { warning("Extra comma."); advance(','); } if (nexttoken.id === ']') { break; } if (b && token.line !== nexttoken.line) { indentation(); } this.first.push(parse(10)); if (nexttoken.id === ',') { comma(); if (nexttoken.id === ']' && !option.es5) { warning("Extra comma.", token); break; } } else { break; } } if (b) { indent -= option.indent; indentation(); } advance(']', this); return this; }, 160); function property_name() { var id = optionalidentifier(true); if (!id) { if (nexttoken.id === '(string)') { id = nexttoken.value; if (option.adsafe && (id.charAt(0) === '_' || id.charAt(id.length - 1) === '_')) { warning("Unexpected {a} in '{b}'.", token, "dangling '_'", id); } advance(); } else if (nexttoken.id === '(number)') { id = nexttoken.value.toString(); advance(); } } return id; } function functionparams() { var i, t = nexttoken, p = []; advance('('); nospace(); if (nexttoken.id === ')') { advance(')'); nospace(prevtoken, token); return; } for (;;) { i = identifier(); p.push(i); addlabel(i, 'parameter'); if (nexttoken.id === ',') { comma(); } else { advance(')', t); nospace(prevtoken, token); return p; } } } function doFunction(i, statement) { var f, s = scope; scope = Object.create(s); funct = { '(name)' : i || '"' + anonname + '"', '(line)' : nexttoken.line, '(context)' : funct, '(breakage)' : 0, '(loopage)' : 0, '(scope)' : scope, '(statement)': statement }; f = funct; token.funct = funct; functions.push(funct); if (i) { addlabel(i, 'function'); } funct['(params)'] = functionparams(); block(false); scope = s; funct['(last)'] = token.line; funct = funct['(context)']; return f; } (function (x) { x.nud = function () { var b, f, i, j, p, seen = {}, t; b = token.line !== nexttoken.line; if (b) { indent += option.indent; if (nexttoken.from === indent + option.indent) { indent += option.indent; } } for (;;) { if (nexttoken.id === '}') { break; } if (b) { indentation(); } if (nexttoken.value === 'get' && peek().id !== ':') { advance('get'); if (!option.es5) { error("get/set are ES5 features."); } i = property_name(); if (!i) { error("Missing property name."); } t = nexttoken; adjacent(token, nexttoken); f = doFunction(i); if (funct['(loopage)']) { warning("Don't make functions within a loop.", t); } p = f['(params)']; if (p) { warning("Unexpected parameter '{a}' in get {b} function.", t, p[0], i); } adjacent(token, nexttoken); advance(','); indentation(); advance('set'); j = property_name(); if (i !== j) { error("Expected {a} and instead saw {b}.", token, i, j); } t = nexttoken; adjacent(token, nexttoken); f = doFunction(i); p = f['(params)']; if (!p || p.length !== 1 || p[0] !== 'value') { warning("Expected (value) in set {a} function.", t, i); } } else { i = property_name(); if (typeof i !== 'string') { break; } advance(':'); nonadjacent(token, nexttoken); parse(10); } if (seen[i] === true) { warning("Duplicate member '{a}'.", nexttoken, i); } seen[i] = true; countMember(i); if (nexttoken.id === ',') { comma(); if (nexttoken.id === ',') { warning("Extra comma.", token); } else if (nexttoken.id === '}' && !option.es5) { warning("Extra comma.", token); } } else { break; } } if (b) { indent -= option.indent; indentation(); } advance('}', this); return this; }; x.fud = function () { error("Expected to see a statement and instead saw a block.", token); }; }(delim('{'))); var varstatement = function varstatement(prefix) { // JavaScript does not have block scope. It only has function scope. So, // declaring a variable in a block can have unexpected consequences. var id, name, value; if (funct['(onevar)'] && option.onevar) { warning("Too many var statements."); } else if (!funct['(global)']) { funct['(onevar)'] = true; } this.first = []; for (;;) { nonadjacent(token, nexttoken); id = identifier(); if (funct['(global)'] && predefined[id] === false) { warning("Redefinition of '{a}'.", token, id); } addlabel(id, 'unused'); if (prefix) { break; } name = token; this.first.push(token); if (nexttoken.id === '=') { nonadjacent(token, nexttoken); advance('='); nonadjacent(token, nexttoken); if (nexttoken.id === 'undefined') { warning("It is not necessary to initialize '{a}' to 'undefined'.", token, id); } if (peek(0).id === '=' && nexttoken.identifier) { error("Variable {a} was not declared correctly.", nexttoken, nexttoken.value); } value = parse(0); name.first = value; } if (nexttoken.id !== ',') { break; } comma(); } return this; }; stmt('var', varstatement).exps = true; blockstmt('function', function () { if (inblock) { warning( "Function statements cannot be placed in blocks. Use a function expression or move the statement to the top of the outer function.", token); } var i = identifier(); adjacent(token, nexttoken); addlabel(i, 'unction'); doFunction(i, true); if (nexttoken.id === '(' && nexttoken.line === token.line) { error( "Function statements are not invocable. Wrap the whole function invocation in parens."); } return this; }); prefix('function', function () { var i = optionalidentifier(); if (i) { adjacent(token, nexttoken); } else { nonadjacent(token, nexttoken); } doFunction(i); if (funct['(loopage)']) { warning("Don't make functions within a loop."); } return this; }); blockstmt('if', function () { var t = nexttoken; advance('('); nonadjacent(this, t); nospace(); parse(20); if (nexttoken.id === '=') { warning("Expected a conditional expression and instead saw an assignment."); advance('='); parse(20); } advance(')', t); nospace(prevtoken, token); block(true); if (nexttoken.id === 'else') { nonadjacent(token, nexttoken); advance('else'); if (nexttoken.id === 'if' || nexttoken.id === 'switch') { statement(true); } else { block(true); } } return this; }); blockstmt('try', function () { var b, e, s; if (option.adsafe) { warning("ADsafe try violation.", this); } block(false); if (nexttoken.id === 'catch') { advance('catch'); nonadjacent(token, nexttoken); advance('('); s = scope; scope = Object.create(s); e = nexttoken.value; if (nexttoken.type !== '(identifier)') { warning("Expected an identifier and instead saw '{a}'.", nexttoken, e); } else { addlabel(e, 'exception'); } advance(); advance(')'); block(false); b = true; scope = s; } if (nexttoken.id === 'finally') { advance('finally'); block(false); return; } else if (!b) { error("Expected '{a}' and instead saw '{b}'.", nexttoken, 'catch', nexttoken.value); } return this; }); blockstmt('while', function () { var t = nexttoken; funct['(breakage)'] += 1; funct['(loopage)'] += 1; advance('('); nonadjacent(this, t); nospace(); parse(20); if (nexttoken.id === '=') { warning("Expected a conditional expression and instead saw an assignment."); advance('='); parse(20); } advance(')', t); nospace(prevtoken, token); block(true); funct['(breakage)'] -= 1; funct['(loopage)'] -= 1; return this; }).labelled = true; reserve('with'); blockstmt('switch', function () { var t = nexttoken, g = false; funct['(breakage)'] += 1; advance('('); nonadjacent(this, t); nospace(); this.condition = parse(20); advance(')', t); nospace(prevtoken, token); nonadjacent(token, nexttoken); t = nexttoken; advance('{'); nonadjacent(token, nexttoken); indent += option.indent; this.cases = []; for (;;) { switch (nexttoken.id) { case 'case': switch (funct['(verb)']) { case 'break': case 'case': case 'continue': case 'return': case 'switch': case 'throw': break; default: warning( "Expected a 'break' statement before 'case'.", token); } indentation(-option.indent); advance('case'); this.cases.push(parse(20)); g = true; advance(':'); funct['(verb)'] = 'case'; break; case 'default': switch (funct['(verb)']) { case 'break': case 'continue': case 'return': case 'throw': break; default: warning( "Expected a 'break' statement before 'default'.", token); } indentation(-option.indent); advance('default'); g = true; advance(':'); break; case '}': indent -= option.indent; indentation(); advance('}', t); if (this.cases.length === 1 || this.condition.id === 'true' || this.condition.id === 'false') { warning("This 'switch' should be an 'if'.", this); } funct['(breakage)'] -= 1; funct['(verb)'] = undefined; return; case '(end)': error("Missing '{a}'.", nexttoken, '}'); return; default: if (g) { switch (token.id) { case ',': error("Each value should have its own case label."); return; case ':': statements(); break; default: error("Missing ':' on a case clause.", token); } } else { error("Expected '{a}' and instead saw '{b}'.", nexttoken, 'case', nexttoken.value); } } } }).labelled = true; stmt('debugger', function () { if (!option.debug) { warning("All 'debugger' statements should be removed."); } return this; }).exps = true; (function () { var x = stmt('do', function () { funct['(breakage)'] += 1; funct['(loopage)'] += 1; this.first = block(true); advance('while'); var t = nexttoken; nonadjacent(token, t); advance('('); nospace(); parse(20); if (nexttoken.id === '=') { warning("Expected a conditional expression and instead saw an assignment."); advance('='); parse(20); } advance(')', t); nospace(prevtoken, token); funct['(breakage)'] -= 1; funct['(loopage)'] -= 1; return this; }); x.labelled = true; x.exps = true; }()); blockstmt('for', function () { var f = option.forin, s, t = nexttoken; funct['(breakage)'] += 1; funct['(loopage)'] += 1; advance('('); nonadjacent(this, t); nospace(); if (peek(nexttoken.id === 'var' ? 1 : 0).id === 'in') { if (nexttoken.id === 'var') { advance('var'); varstatement(true); } else { switch (funct[nexttoken.value]) { case 'unused': funct[nexttoken.value] = 'var'; break; case 'var': break; default: warning("Bad for in variable '{a}'.", nexttoken, nexttoken.value); } advance(); } advance('in'); parse(20); advance(')', t); s = block(true); if (!f && (s.length > 1 || typeof s[0] !== 'object' || s[0].value !== 'if')) { warning("The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.", this); } funct['(breakage)'] -= 1; funct['(loopage)'] -= 1; return this; } else { if (nexttoken.id !== ';') { if (nexttoken.id === 'var') { advance('var'); varstatement(); } else { for (;;) { parse(0, 'for'); if (nexttoken.id !== ',') { break; } comma(); } } } nolinebreak(token); advance(';'); if (nexttoken.id !== ';') { parse(20); if (nexttoken.id === '=') { warning("Expected a conditional expression and instead saw an assignment."); advance('='); parse(20); } } nolinebreak(token); advance(';'); if (nexttoken.id === ';') { error("Expected '{a}' and instead saw '{b}'.", nexttoken, ')', ';'); } if (nexttoken.id !== ')') { for (;;) { parse(0, 'for'); if (nexttoken.id !== ',') { break; } comma(); } } advance(')', t); nospace(prevtoken, token); block(true); funct['(breakage)'] -= 1; funct['(loopage)'] -= 1; return this; } }).labelled = true; stmt('break', function () { var v = nexttoken.value; if (funct['(breakage)'] === 0) { warning("Unexpected '{a}'.", nexttoken, this.value); } nolinebreak(this); if (nexttoken.id !== ';') { if (token.line === nexttoken.line) { if (funct[v] !== 'label') { warning("'{a}' is not a statement label.", nexttoken, v); } else if (scope[v] !== funct) { warning("'{a}' is out of scope.", nexttoken, v); } this.first = nexttoken; advance(); } } reachable('break'); return this; }).exps = true; stmt('continue', function () { var v = nexttoken.value; if (funct['(breakage)'] === 0) { warning("Unexpected '{a}'.", nexttoken, this.value); } nolinebreak(this); if (nexttoken.id !== ';') { if (token.line === nexttoken.line) { if (funct[v] !== 'label') { warning("'{a}' is not a statement label.", nexttoken, v); } else if (scope[v] !== funct) { warning("'{a}' is out of scope.", nexttoken, v); } this.first = nexttoken; advance(); } } else if (!funct['(loopage)']) { warning("Unexpected '{a}'.", nexttoken, this.value); } reachable('continue'); return this; }).exps = true; stmt('return', function () { nolinebreak(this); if (nexttoken.id === '(regexp)') { warning("Wrap the /regexp/ literal in parens to disambiguate the slash operator."); } if (nexttoken.id !== ';' && !nexttoken.reach) { nonadjacent(token, nexttoken); this.first = parse(20); } reachable('return'); return this; }).exps = true; stmt('throw', function () { nolinebreak(this); nonadjacent(token, nexttoken); this.first = parse(20); reachable('throw'); return this; }).exps = true; reserve('void'); // Superfluous reserved words reserve('class'); reserve('const'); reserve('enum'); reserve('export'); reserve('extends'); reserve('import'); reserve('super'); reserve('let'); reserve('yield'); reserve('implements'); reserve('interface'); reserve('package'); reserve('private'); reserve('protected'); reserve('public'); reserve('static'); // Parse JSON function jsonValue() { function jsonObject() { var o = {}, t = nexttoken; advance('{'); if (nexttoken.id !== '}') { for (;;) { if (nexttoken.id === '(end)') { error("Missing '}' to match '{' from line {a}.", nexttoken, t.line); } else if (nexttoken.id === '}') { warning("Unexpected comma.", token); break; } else if (nexttoken.id === ',') { error("Unexpected comma.", nexttoken); } else if (nexttoken.id !== '(string)') { warning("Expected a string and instead saw {a}.", nexttoken, nexttoken.value); } if (o[nexttoken.value] === true) { warning("Duplicate key '{a}'.", nexttoken, nexttoken.value); } else if (nexttoken.value === '__proto__') { warning("Stupid key '{a}'.", nexttoken, nexttoken.value); } else { o[nexttoken.value] = true; } advance(); advance(':'); jsonValue(); if (nexttoken.id !== ',') { break; } advance(','); } } advance('}'); } function jsonArray() { var t = nexttoken; advance('['); if (nexttoken.id !== ']') { for (;;) { if (nexttoken.id === '(end)') { error("Missing ']' to match '[' from line {a}.", nexttoken, t.line); } else if (nexttoken.id === ']') { warning("Unexpected comma.", token); break; } else if (nexttoken.id === ',') { error("Unexpected comma.", nexttoken); } jsonValue(); if (nexttoken.id !== ',') { break; } advance(','); } } advance(']'); } switch (nexttoken.id) { case '{': jsonObject(); break; case '[': jsonArray(); break; case 'true': case 'false': case 'null': case '(number)': case '(string)': advance(); break; case '-': advance('-'); if (token.character !== nexttoken.from) { warning("Unexpected space after '-'.", token); } adjacent(token, nexttoken); advance('(number)'); break; default: error("Expected a JSON value.", nexttoken); } } // The actual JSLINT function itself. var itself = function (s, o) { var a, i; JSLINT.errors = []; predefined = Object.create(standard); if (o) { a = o.predef; if (a instanceof Array) { for (i = 0; i < a.length; i += 1) { predefined[a[i]] = true; } } if (o.adsafe) { o.safe = true; } if (o.safe) { o.browser = o.css = o.debug = o.devel = o.evil = o.forin = o.on = o.rhino = o.windows = o.sub = o.widget = false; o.eqeqeq = o.nomen = o.safe = o.strict = o.undef = true; predefined.Date = predefined['eval'] = predefined.Function = predefined.Object = null; predefined.ADSAFE = predefined.lib = false; } option = o; } else { option = {}; } option.indent = option.indent || 4; option.maxerr = option.maxerr || 50; adsafe_id = ''; adsafe_may = false; adsafe_went = false; approved = {}; if (option.approved) { for (i = 0; i < option.approved.length; i += 1) { approved[option.approved[i]] = option.approved[i]; } } else { approved.test = 'test'; } tab = ''; for (i = 0; i < option.indent; i += 1) { tab += ' '; } indent = 1; global = Object.create(predefined); scope = global; funct = { '(global)': true, '(name)': '(global)', '(scope)': scope, '(breakage)': 0, '(loopage)': 0 }; functions = [funct]; ids = {}; urls = []; src = false; xmode = false; stack = null; member = {}; membersOnly = null; implied = {}; inblock = false; lookahead = []; jsonmode = false; warnings = 0; lex.init(s); prereg = true; strict_mode = false; prevtoken = token = nexttoken = syntax['(begin)']; assume(); try { advance(); if (nexttoken.value.charAt(0) === '<') { html(); if (option.adsafe && !adsafe_went) { warning("ADsafe violation: Missing ADSAFE.go.", this); } } else { switch (nexttoken.id) { case '{': case '[': option.laxbreak = true; jsonmode = true; jsonValue(); break; case '@': case '*': case '#': case '.': case ':': xmode = 'style'; advance(); if (token.id !== '@' || !nexttoken.identifier || nexttoken.value !== 'charset' || token.line !== 1 || token.from !== 1) { error("A css file should begin with @charset 'UTF-8';"); } advance(); if (nexttoken.type !== '(string)' && nexttoken.value !== 'UTF-8') { error("A css file should begin with @charset 'UTF-8';"); } advance(); advance(';'); styles(); break; default: if (option.adsafe && option.fragment) { error("Expected '{a}' and instead saw '{b}'.", nexttoken, '
', nexttoken.value); } statements('lib'); } } advance('(end)'); } catch (e) { if (e) { JSLINT.errors.push({ reason : e.message, line : e.line || nexttoken.line, character : e.character || nexttoken.from }, null); } } return JSLINT.errors.length === 0; }; function is_array(o) { return Object.prototype.toString.apply(o) === '[object Array]'; } function to_array(o) { var a = [], k; for (k in o) { if (is_own(o, k)) { a.push(k); } } return a; } // Data summary. itself.data = function () { var data = {functions: []}, fu, globals, implieds = [], f, i, j, members = [], n, unused = [], v; if (itself.errors.length) { data.errors = itself.errors; } if (jsonmode) { data.json = true; } for (n in implied) { if (is_own(implied, n)) { implieds.push({ name: n, line: implied[n] }); } } if (implieds.length > 0) { data.implieds = implieds; } if (urls.length > 0) { data.urls = urls; } globals = to_array(scope); if (globals.length > 0) { data.globals = globals; } for (i = 1; i < functions.length; i += 1) { f = functions[i]; fu = {}; for (j = 0; j < functionicity.length; j += 1) { fu[functionicity[j]] = []; } for (n in f) { if (is_own(f, n) && n.charAt(0) !== '(') { v = f[n]; if (v === 'unction') { v = 'unused'; } if (is_array(fu[v])) { fu[v].push(n); if (v === 'unused') { unused.push({ name: n, line: f['(line)'], 'function': f['(name)'] }); } } } } for (j = 0; j < functionicity.length; j += 1) { if (fu[functionicity[j]].length === 0) { delete fu[functionicity[j]]; } } fu.name = f['(name)']; fu.param = f['(params)']; fu.line = f['(line)']; fu.last = f['(last)']; data.functions.push(fu); } if (unused.length > 0) { data.unused = unused; } members = []; for (n in member) { if (typeof member[n] === 'number') { data.member = member; break; } } return data; }; itself.report = function (option) { var data = itself.data(); var a = [], c, e, err, f, i, k, l, m = '', n, o = [], s; function detail(h, array) { var b, i, singularity; if (array) { o.push('
' + h + ' '); array = array.sort(); for (i = 0; i < array.length; i += 1) { if (array[i] !== singularity) { singularity = array[i]; o.push((b ? ', ' : '') + singularity); b = true; } } o.push('
'); } } if (data.errors || data.implieds || data.unused) { err = true; o.push('
Error:'); if (data.errors) { for (i = 0; i < data.errors.length; i += 1) { c = data.errors[i]; if (c) { e = c.evidence || ''; o.push('

Problem' + (isFinite(c.line) ? ' at line ' + c.line + ' character ' + c.character : '') + ': ' + c.reason.entityify() + '

' + (e && (e.length > 80 ? e.slice(0, 77) + '...' : e).entityify()) + '

'); } } } if (data.implieds) { s = []; for (i = 0; i < data.implieds.length; i += 1) { s[i] = '' + data.implieds[i].name + ' ' + data.implieds[i].line + ''; } o.push('

Implied global: ' + s.join(', ') + '

'); } if (data.unused) { s = []; for (i = 0; i < data.unused.length; i += 1) { s[i] = '' + data.unused[i].name + ' ' + data.unused[i].line + ' ' + data.unused[i]['function'] + ''; } o.push('

Unused variable: ' + s.join(', ') + '

'); } if (data.json) { o.push('

JSON: bad.

'); } o.push('
'); } if (!option) { o.push('
'); if (data.urls) { detail("URLs
", data.urls, '
'); } if (xmode === 'style') { o.push('

CSS.

'); } else if (data.json && !err) { o.push('

JSON: good.

'); } else if (data.globals) { o.push('
Global ' + data.globals.sort().join(', ') + '
'); } else { o.push('
No new global variables introduced.
'); } for (i = 0; i < data.functions.length; i += 1) { f = data.functions[i]; o.push('
' + f.line + '-' + f.last + ' ' + (f.name || '') + '(' + (f.param ? f.param.join(', ') : '') + ')
'); detail('Unused', f.unused); detail('Closure', f.closure); detail('Variable', f['var']); detail('Exception', f.exception); detail('Outer', f.outer); detail('Global', f.global); detail('Label', f.label); } if (data.member) { a = to_array(data.member); if (a.length) { a = a.sort(); m = '
/*members ';
                    l = 10;
                    for (i = 0; i < a.length; i += 1) {
                        k = a[i];
                        n = k.name();
                        if (l + n.length > 72) {
                            o.push(m + '
'); m = ' '; l = 1; } l += n.length + 2; if (data.member[k] === 1) { n = '' + n + ''; } if (i < a.length - 1) { n += ', '; } m += n; } o.push(m + '
*/
'); } o.push('
'); } } return o.join(''); }; itself.jslint = itself; itself.edition = '2010-10-16'; return itself; }()); ================================================ FILE: lib/jslint/rhino.js ================================================ // rhino.js // 2009-09-11 /* Copyright (c) 2002 Douglas Crockford (www.JSLint.com) Rhino Edition */ // This is the Rhino companion to fulljslint.js. /*global JSLINT */ /*jslint rhino: true, strict: false */ (function (a) { var e, i, input; if (!a[0]) { print("Usage: jslint.js file.js"); quit(1); } input = readFile(a[0]); if (!input) { print("jslint: Couldn't open file '" + a[0] + "'."); quit(1); } if (!JSLINT(input, {bitwise: true, eqeqeq: true, immed: true, newcap: true, nomen: true, onevar: true, plusplus: true, regexp: true, rhino: true, undef: true, white: true})) { for (i = 0; i < JSLINT.errors.length; i += 1) { e = JSLINT.errors[i]; if (e) { print('Lint at line ' + e.line + ' character ' + e.character + ': ' + e.reason); print((e.evidence || ''). replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1")); print(''); } } quit(2); } else { print("jslint: No problems found in " + a[0]); quit(); } }(arguments)); ================================================ FILE: lib/json/json2.js ================================================ /* http://www.JSON.org/json2.js 2011-02-23 Public Domain. NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. See http://www.JSON.org/js.html This code should be minified before deployment. See http://javascript.crockford.com/jsmin.html USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO NOT CONTROL. This file creates a global JSON object containing two methods: stringify and parse. JSON.stringify(value, replacer, space) value any JavaScript value, usually an object or array. replacer an optional parameter that determines how object values are stringified for objects. It can be a function or an array of strings. space an optional parameter that specifies the indentation of nested structures. If it is omitted, the text will be packed without extra whitespace. If it is a number, it will specify the number of spaces to indent at each level. If it is a string (such as '\t' or ' '), it contains the characters used to indent at each level. This method produces a JSON text from a JavaScript value. When an object value is found, if the object contains a toJSON method, its toJSON method will be called and the result will be stringified. A toJSON method does not serialize: it returns the value represented by the name/value pair that should be serialized, or undefined if nothing should be serialized. The toJSON method will be passed the key associated with the value, and this will be bound to the value For example, this would serialize Dates as ISO strings. Date.prototype.toJSON = function (key) { function f(n) { // Format integers to have at least two digits. return n < 10 ? '0' + n : n; } return this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z'; }; You can provide an optional replacer method. It will be passed the key and value of each member, with this bound to the containing object. The value that is returned from your method will be serialized. If your method returns undefined, then the member will be excluded from the serialization. If the replacer parameter is an array of strings, then it will be used to select the members to be serialized. It filters the results such that only members with keys listed in the replacer array are stringified. Values that do not have JSON representations, such as undefined or functions, will not be serialized. Such values in objects will be dropped; in arrays they will be replaced with null. You can use a replacer function to replace those with JSON values. JSON.stringify(undefined) returns undefined. The optional space parameter produces a stringification of the value that is filled with line breaks and indentation to make it easier to read. If the space parameter is a non-empty string, then that string will be used for indentation. If the space parameter is a number, then the indentation will be that many spaces. Example: text = JSON.stringify(['e', {pluribus: 'unum'}]); // text is '["e",{"pluribus":"unum"}]' text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' text = JSON.stringify([new Date()], function (key, value) { return this[key] instanceof Date ? 'Date(' + this[key] + ')' : value; }); // text is '["Date(---current time---)"]' JSON.parse(text, reviver) This method parses a JSON text to produce an object or array. It can throw a SyntaxError exception. The optional reviver parameter is a function that can filter and transform the results. It receives each of the keys and values, and its return value is used instead of the original value. If it returns what it received, then the structure is not modified. If it returns undefined then the member is deleted. Example: // Parse the text. Values that look like ISO date strings will // be converted to Date objects. myData = JSON.parse(text, function (key, value) { var a; if (typeof value === 'string') { a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); if (a) { return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6])); } } return value; }); myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { var d; if (typeof value === 'string' && value.slice(0, 5) === 'Date(' && value.slice(-1) === ')') { d = new Date(value.slice(5, -1)); if (d) { return d; } } return value; }); This is a reference implementation. You are free to copy, modify, or redistribute. */ /*jslint evil: true, strict: false, regexp: false */ /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, lastIndex, length, parse, prototype, push, replace, slice, stringify, test, toJSON, toString, valueOf */ // Create a JSON object only if one does not already exist. We create the // methods in a closure to avoid creating global variables. var JSON; if (!JSON) { JSON = {}; } (function () { "use strict"; function f(n) { // Format integers to have at least two digits. return n < 10 ? '0' + n : n; } if (typeof Date.prototype.toJSON !== 'function') { Date.prototype.toJSON = function (key) { return isFinite(this.valueOf()) ? this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z' : null; }; String.prototype.toJSON = Number.prototype.toJSON = Boolean.prototype.toJSON = function (key) { return this.valueOf(); }; } var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, gap, indent, meta = { // table of character substitutions '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\' }, rep; function quote(string) { // If the string contains no control characters, no quote characters, and no // backslash characters, then we can safely slap some quotes around it. // Otherwise we must also replace the offending characters with safe escape // sequences. escapable.lastIndex = 0; return escapable.test(string) ? '"' + string.replace(escapable, function (a) { var c = meta[a]; return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }) + '"' : '"' + string + '"'; } function str(key, holder) { // Produce a string from holder[key]. var i, // The loop counter. k, // The member key. v, // The member value. length, mind = gap, partial, value = holder[key]; // If the value has a toJSON method, call it to obtain a replacement value. if (value && typeof value === 'object' && typeof value.toJSON === 'function') { value = value.toJSON(key); } // If we were called with a replacer function, then call the replacer to // obtain a replacement value. if (typeof rep === 'function') { value = rep.call(holder, key, value); } // What happens next depends on the value's type. switch (typeof value) { case 'string': return quote(value); case 'number': // JSON numbers must be finite. Encode non-finite numbers as null. return isFinite(value) ? String(value) : 'null'; case 'boolean': case 'null': // If the value is a boolean or null, convert it to a string. Note: // typeof null does not produce 'null'. The case is included here in // the remote chance that this gets fixed someday. return String(value); // If the type is 'object', we might be dealing with an object or an array or // null. case 'object': // Due to a specification blunder in ECMAScript, typeof null is 'object', // so watch out for that case. if (!value) { return 'null'; } // Make an array to hold the partial results of stringifying this object value. gap += indent; partial = []; // Is the value an array? if (Object.prototype.toString.apply(value) === '[object Array]') { // The value is an array. Stringify every element. Use null as a placeholder // for non-JSON values. length = value.length; for (i = 0; i < length; i += 1) { partial[i] = str(i, value) || 'null'; } // Join all of the elements together, separated with commas, and wrap them in // brackets. v = partial.length === 0 ? '[]' : gap ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : '[' + partial.join(',') + ']'; gap = mind; return v; } // If the replacer is an array, use it to select the members to be stringified. if (rep && typeof rep === 'object') { length = rep.length; for (i = 0; i < length; i += 1) { if (typeof rep[i] === 'string') { k = rep[i]; v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } } else { // Otherwise, iterate through all of the keys in the object. for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } } // Join all of the member texts together, separated with commas, // and wrap them in braces. v = partial.length === 0 ? '{}' : gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : '{' + partial.join(',') + '}'; gap = mind; return v; } } // If the JSON object does not yet have a stringify method, give it one. if (typeof JSON.stringify !== 'function') { JSON.stringify = function (value, replacer, space) { // The stringify method takes a value and an optional replacer, and an optional // space parameter, and returns a JSON text. The replacer can be a function // that can replace values, or an array of strings that will select the keys. // A default replacer method can be provided. Use of the space parameter can // produce text that is more easily readable. var i; gap = ''; indent = ''; // If the space parameter is a number, make an indent string containing that // many spaces. if (typeof space === 'number') { for (i = 0; i < space; i += 1) { indent += ' '; } // If the space parameter is a string, it will be used as the indent string. } else if (typeof space === 'string') { indent = space; } // If there is a replacer, it must be a function or an array. // Otherwise, throw an error. rep = replacer; if (replacer && typeof replacer !== 'function' && (typeof replacer !== 'object' || typeof replacer.length !== 'number')) { throw new Error('JSON.stringify'); } // Make a fake root object containing our value under the key of ''. // Return the result of stringifying the value. return str('', {'': value}); }; } // If the JSON object does not yet have a parse method, give it one. if (typeof JSON.parse !== 'function') { JSON.parse = function (text, reviver) { // The parse method takes a text and an optional reviver function, and returns // a JavaScript value if the text is a valid JSON text. var j; function walk(holder, key) { // The walk method is used to recursively walk the resulting structure so // that modifications can be made. var k, v, value = holder[key]; if (value && typeof value === 'object') { for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { v = walk(value, k); if (v !== undefined) { value[k] = v; } else { delete value[k]; } } } } return reviver.call(holder, key, value); } // Parsing happens in four stages. In the first stage, we replace certain // Unicode characters with escape sequences. JavaScript handles many characters // incorrectly, either silently deleting them, or treating them as line endings. text = String(text); cx.lastIndex = 0; if (cx.test(text)) { text = text.replace(cx, function (a) { return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }); } // In the second stage, we run the text against regular expressions that look // for non-JSON patterns. We are especially concerned with '()' and 'new' // because they can cause invocation, and '=' because it can cause mutation. // But just to be safe, we want to reject all unexpected forms. // We split the second stage into 4 regexp operations in order to work around // crippling inefficiencies in IE's and Safari's regexp engines. First we // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we // replace all simple value tokens with ']' characters. Third, we delete all // open brackets that follow a colon or comma or that begin the text. Finally, // we look to see that the remaining characters are only whitespace or ']' or // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. if (/^[\],:{}\s]*$/ .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { // In the third stage we use the eval function to compile the text into a // JavaScript structure. The '{' operator is subject to a syntactic ambiguity // in JavaScript: it can begin a block or an object literal. We wrap the text // in parens to eliminate the ambiguity. j = eval('(' + text + ')'); // In the optional fourth stage, we recursively walk the new structure, passing // each name/value pair to a reviver function for possible transformation. return typeof reviver === 'function' ? walk({'': j}, '') : j; } // If the text is not JSON parseable, then a SyntaxError is thrown. throw new SyntaxError('JSON.parse'); }; } }()); ================================================ FILE: lib/qunit/MIT-LICENSE.txt ================================================ Copyright (c) 2010 John Resig, http://jquery.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: lib/qunit/download.url ================================================ [InternetShortcut] URL=http://github.com/jquery/qunit/ IDList= HotKey=0 [{000214A0-0000-0000-C000-000000000046}] Prop3=19,2 ================================================ FILE: lib/qunit/qunit.css ================================================ /** * QUnit - A JavaScript Unit Testing Framework * * http://docs.jquery.com/QUnit * * Copyright (c) 2011 John Resig, Jörn Zaefferer * Dual licensed under the MIT (MIT-LICENSE.txt) * or GPL (GPL-LICENSE.txt) licenses. */ /** Font Family and Sizes */ #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; } #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } #qunit-tests { font-size: smaller; } /** Resets */ #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { margin: 0; padding: 0; } /** Header */ #qunit-header { padding: 0.5em 0 0.5em 1em; color: #8699a4; background-color: #0d3349; font-size: 1.5em; line-height: 1em; font-weight: normal; border-radius: 15px 15px 0 0; -moz-border-radius: 15px 15px 0 0; -webkit-border-top-right-radius: 15px; -webkit-border-top-left-radius: 15px; } #qunit-header a { text-decoration: none; color: #c2ccd1; } #qunit-header a:hover, #qunit-header a:focus { color: #fff; } #qunit-banner { height: 5px; } #qunit-testrunner-toolbar { padding: 0.5em 0 0.5em 2em; color: #5E740B; background-color: #eee; } #qunit-userAgent { padding: 0.5em 0 0.5em 2.5em; background-color: #2b81af; color: #fff; text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; } /** Tests: Pass/Fail */ #qunit-tests { list-style-position: inside; } #qunit-tests li { padding: 0.4em 0.5em 0.4em 2.5em; border-bottom: 1px solid #fff; list-style-position: inside; } #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { display: none; } #qunit-tests li strong { cursor: pointer; } #qunit-tests li a { padding: 0.5em; color: #c2ccd1; text-decoration: none; } #qunit-tests li a:hover, #qunit-tests li a:focus { color: #000; } #qunit-tests ol { margin-top: 0.5em; padding: 0.5em; background-color: #fff; border-radius: 15px; -moz-border-radius: 15px; -webkit-border-radius: 15px; box-shadow: inset 0px 2px 13px #999; -moz-box-shadow: inset 0px 2px 13px #999; -webkit-box-shadow: inset 0px 2px 13px #999; } #qunit-tests table { border-collapse: collapse; margin-top: .2em; } #qunit-tests th { text-align: right; vertical-align: top; padding: 0 .5em 0 0; } #qunit-tests td { vertical-align: top; } #qunit-tests pre { margin: 0; white-space: pre-wrap; word-wrap: break-word; } #qunit-tests del { background-color: #e0f2be; color: #374e0c; text-decoration: none; } #qunit-tests ins { background-color: #ffcaca; color: #500; text-decoration: none; } /*** Test Counts */ #qunit-tests b.counts { color: black; } #qunit-tests b.passed { color: #5E740B; } #qunit-tests b.failed { color: #710909; } #qunit-tests li li { margin: 0.5em; padding: 0.4em 0.5em 0.4em 0.5em; background-color: #fff; border-bottom: none; list-style-position: inside; } /*** Passing Styles */ #qunit-tests li li.pass { color: #5E740B; background-color: #fff; border-left: 26px solid #C6E746; } #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } #qunit-tests .pass .test-name { color: #366097; } #qunit-tests .pass .test-actual, #qunit-tests .pass .test-expected { color: #999999; } #qunit-banner.qunit-pass { background-color: #C6E746; } /*** Failing Styles */ #qunit-tests li li.fail { color: #710909; background-color: #fff; border-left: 26px solid #EE5757; white-space: pre; } #qunit-tests > li:last-child { border-radius: 0 0 15px 15px; -moz-border-radius: 0 0 15px 15px; -webkit-border-bottom-right-radius: 15px; -webkit-border-bottom-left-radius: 15px; } #qunit-tests .fail { color: #000000; background-color: #EE5757; } #qunit-tests .fail .test-name, #qunit-tests .fail .module-name { color: #000000; } #qunit-tests .fail .test-actual { color: #EE5757; } #qunit-tests .fail .test-expected { color: green; } #qunit-banner.qunit-fail { background-color: #EE5757; } /** Result */ #qunit-testresult { padding: 0.5em 0.5em 0.5em 2.5em; color: #2b81af; background-color: #D2E0E6; border-bottom: 1px solid white; } /** Fixture */ #qunit-fixture { position: absolute; top: -10000px; left: -10000px; } ================================================ FILE: lib/qunit/qunit.js ================================================ /** * QUnit - A JavaScript Unit Testing Framework * * http://docs.jquery.com/QUnit * * Copyright (c) 2011 John Resig, Jörn Zaefferer * Dual licensed under the MIT (MIT-LICENSE.txt) * or GPL (GPL-LICENSE.txt) licenses. */ (function(window) { var defined = { setTimeout: typeof window.setTimeout !== "undefined", sessionStorage: (function() { try { return !!sessionStorage.getItem; } catch(e) { return false; } })() }; var testId = 0; var Test = function(name, testName, expected, testEnvironmentArg, async, callback) { this.name = name; this.testName = testName; this.expected = expected; this.testEnvironmentArg = testEnvironmentArg; this.async = async; this.callback = callback; this.assertions = []; }; Test.prototype = { init: function() { var tests = id("qunit-tests"); if (tests) { var b = document.createElement("strong"); b.innerHTML = "Running " + this.name; var li = document.createElement("li"); li.appendChild( b ); li.className = "running"; li.id = this.id = "test-output" + testId++; tests.appendChild( li ); } }, setup: function() { if (this.module != config.previousModule) { if ( config.previousModule ) { runLoggingCallbacks('moduleDone', QUnit, { name: config.previousModule, failed: config.moduleStats.bad, passed: config.moduleStats.all - config.moduleStats.bad, total: config.moduleStats.all } ); } config.previousModule = this.module; config.moduleStats = { all: 0, bad: 0 }; runLoggingCallbacks( 'moduleStart', QUnit, { name: this.module } ); } config.current = this; this.testEnvironment = extend({ setup: function() {}, teardown: function() {} }, this.moduleTestEnvironment); if (this.testEnvironmentArg) { extend(this.testEnvironment, this.testEnvironmentArg); } runLoggingCallbacks( 'testStart', QUnit, { name: this.testName, module: this.module }); // allow utility functions to access the current test environment // TODO why?? QUnit.current_testEnvironment = this.testEnvironment; try { if ( !config.pollution ) { saveGlobal(); } this.testEnvironment.setup.call(this.testEnvironment); } catch(e) { QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message ); } }, run: function() { if ( this.async ) { QUnit.stop(); } if ( config.notrycatch ) { this.callback.call(this.testEnvironment); return; } try { this.callback.call(this.testEnvironment); } catch(e) { fail("Test " + this.testName + " died, exception and test follows", e, this.callback); QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); // else next test will carry the responsibility saveGlobal(); // Restart the tests if they're blocking if ( config.blocking ) { start(); } } }, teardown: function() { try { this.testEnvironment.teardown.call(this.testEnvironment); checkPollution(); } catch(e) { QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message ); } }, finish: function() { if ( this.expected && this.expected != this.assertions.length ) { QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); } var good = 0, bad = 0, tests = id("qunit-tests"); config.stats.all += this.assertions.length; config.moduleStats.all += this.assertions.length; if ( tests ) { var ol = document.createElement("ol"); for ( var i = 0; i < this.assertions.length; i++ ) { var assertion = this.assertions[i]; var li = document.createElement("li"); li.className = assertion.result ? "pass" : "fail"; li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); ol.appendChild( li ); if ( assertion.result ) { good++; } else { bad++; config.stats.bad++; config.moduleStats.bad++; } } // store result when possible if ( QUnit.config.reorder && defined.sessionStorage ) { if (bad) { sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad); } else { sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName); } } if (bad == 0) { ol.style.display = "none"; } var b = document.createElement("strong"); b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; var a = document.createElement("a"); a.innerHTML = "Rerun"; a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); addEvent(b, "click", function() { var next = b.nextSibling.nextSibling, display = next.style.display; next.style.display = display === "none" ? "block" : "none"; }); addEvent(b, "dblclick", function(e) { var target = e && e.target ? e.target : window.event.srcElement; if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { target = target.parentNode; } if ( window.location && target.nodeName.toLowerCase() === "strong" ) { window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); } }); var li = id(this.id); li.className = bad ? "fail" : "pass"; li.removeChild( li.firstChild ); li.appendChild( b ); li.appendChild( a ); li.appendChild( ol ); } else { for ( var i = 0; i < this.assertions.length; i++ ) { if ( !this.assertions[i].result ) { bad++; config.stats.bad++; config.moduleStats.bad++; } } } try { QUnit.reset(); } catch(e) { fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset); } runLoggingCallbacks( 'testDone', QUnit, { name: this.testName, module: this.module, failed: bad, passed: this.assertions.length - bad, total: this.assertions.length } ); }, queue: function() { var test = this; synchronize(function() { test.init(); }); function run() { // each of these can by async synchronize(function() { test.setup(); }); synchronize(function() { test.run(); }); synchronize(function() { test.teardown(); }); synchronize(function() { test.finish(); }); } // defer when previous test run passed, if storage is available var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName); if (bad) { run(); } else { synchronize(run); }; } }; var QUnit = { // call on start of module test to prepend name to all tests module: function(name, testEnvironment) { config.currentModule = name; config.currentModuleTestEnviroment = testEnvironment; }, asyncTest: function(testName, expected, callback) { if ( arguments.length === 2 ) { callback = expected; expected = 0; } QUnit.test(testName, expected, callback, true); }, test: function(testName, expected, callback, async) { var name = '' + testName + '', testEnvironmentArg; if ( arguments.length === 2 ) { callback = expected; expected = null; } // is 2nd argument a testEnvironment? if ( expected && typeof expected === 'object') { testEnvironmentArg = expected; expected = null; } if ( config.currentModule ) { name = '' + config.currentModule + ": " + name; } if ( !validTest(config.currentModule + ": " + testName) ) { return; } var test = new Test(name, testName, expected, testEnvironmentArg, async, callback); test.module = config.currentModule; test.moduleTestEnvironment = config.currentModuleTestEnviroment; test.queue(); }, /** * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. */ expect: function(asserts) { config.current.expected = asserts; }, /** * Asserts true. * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); */ ok: function(a, msg) { a = !!a; var details = { result: a, message: msg }; msg = escapeInnerText(msg); runLoggingCallbacks( 'log', QUnit, details ); config.current.assertions.push({ result: a, message: msg }); }, /** * Checks that the first two arguments are equal, with an optional message. * Prints out both actual and expected values. * * Prefered to ok( actual == expected, message ) * * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); * * @param Object actual * @param Object expected * @param String message (optional) */ equal: function(actual, expected, message) { QUnit.push(expected == actual, actual, expected, message); }, notEqual: function(actual, expected, message) { QUnit.push(expected != actual, actual, expected, message); }, deepEqual: function(actual, expected, message) { QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); }, notDeepEqual: function(actual, expected, message) { QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); }, strictEqual: function(actual, expected, message) { QUnit.push(expected === actual, actual, expected, message); }, notStrictEqual: function(actual, expected, message) { QUnit.push(expected !== actual, actual, expected, message); }, raises: function(block, expected, message) { var actual, ok = false; if (typeof expected === 'string') { message = expected; expected = null; } try { block(); } catch (e) { actual = e; } if (actual) { // we don't want to validate thrown error if (!expected) { ok = true; // expected is a regexp } else if (QUnit.objectType(expected) === "regexp") { ok = expected.test(actual); // expected is a constructor } else if (actual instanceof expected) { ok = true; // expected is a validation function which returns true is validation passed } else if (expected.call({}, actual) === true) { ok = true; } } QUnit.ok(ok, message); }, start: function() { config.semaphore--; if (config.semaphore > 0) { // don't start until equal number of stop-calls return; } if (config.semaphore < 0) { // ignore if start is called more often then stop config.semaphore = 0; } // A slight delay, to avoid any current callbacks if ( defined.setTimeout ) { window.setTimeout(function() { if (config.semaphore > 0) { return; } if ( config.timeout ) { clearTimeout(config.timeout); } config.blocking = false; process(); }, 13); } else { config.blocking = false; process(); } }, stop: function(timeout) { config.semaphore++; config.blocking = true; if ( timeout && defined.setTimeout ) { clearTimeout(config.timeout); config.timeout = window.setTimeout(function() { QUnit.ok( false, "Test timed out" ); QUnit.start(); }, timeout); } } }; //We want access to the constructor's prototype (function() { function F(){}; F.prototype = QUnit; QUnit = new F(); //Make F QUnit's constructor so that we can add to the prototype later QUnit.constructor = F; })(); // Backwards compatibility, deprecated QUnit.equals = QUnit.equal; QUnit.same = QUnit.deepEqual; // Maintain internal state var config = { // The queue of tests to run queue: [], // block until document ready blocking: true, // when enabled, show only failing tests // gets persisted through sessionStorage and can be changed in UI via checkbox hidepassed: false, // by default, run previously failed tests first // very useful in combination with "Hide passed tests" checked reorder: true, // by default, modify document.title when suite is done altertitle: true, urlConfig: ['noglobals', 'notrycatch'], //logging callback queues begin: [], done: [], log: [], testStart: [], testDone: [], moduleStart: [], moduleDone: [] }; // Load paramaters (function() { var location = window.location || { search: "", protocol: "file:" }, params = location.search.slice( 1 ).split( "&" ), length = params.length, urlParams = {}, current; if ( params[ 0 ] ) { for ( var i = 0; i < length; i++ ) { current = params[ i ].split( "=" ); current[ 0 ] = decodeURIComponent( current[ 0 ] ); // allow just a key to turn on a flag, e.g., test.html?noglobals current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; urlParams[ current[ 0 ] ] = current[ 1 ]; } } QUnit.urlParams = urlParams; config.filter = urlParams.filter; // Figure out if we're running the tests from a server or not QUnit.isLocal = !!(location.protocol === 'file:'); })(); // Expose the API as global variables, unless an 'exports' // object exists, in that case we assume we're in CommonJS if ( typeof exports === "undefined" || typeof require === "undefined" ) { extend(window, QUnit); window.QUnit = QUnit; } else { extend(exports, QUnit); exports.QUnit = QUnit; } // define these after exposing globals to keep them in these QUnit namespace only extend(QUnit, { config: config, // Initialize the configuration options init: function() { extend(config, { stats: { all: 0, bad: 0 }, moduleStats: { all: 0, bad: 0 }, started: +new Date, updateRate: 1000, blocking: false, autostart: true, autorun: false, filter: "", queue: [], semaphore: 0 }); var tests = id( "qunit-tests" ), banner = id( "qunit-banner" ), result = id( "qunit-testresult" ); if ( tests ) { tests.innerHTML = ""; } if ( banner ) { banner.className = ""; } if ( result ) { result.parentNode.removeChild( result ); } if ( tests ) { result = document.createElement( "p" ); result.id = "qunit-testresult"; result.className = "result"; tests.parentNode.insertBefore( result, tests ); result.innerHTML = 'Running...
 '; } }, /** * Resets the test setup. Useful for tests that modify the DOM. * * If jQuery is available, uses jQuery's html(), otherwise just innerHTML. */ reset: function() { if ( window.jQuery ) { jQuery( "#qunit-fixture" ).html( config.fixture ); } else { var main = id( 'qunit-fixture' ); if ( main ) { main.innerHTML = config.fixture; } } }, /** * Trigger an event on an element. * * @example triggerEvent( document.body, "click" ); * * @param DOMElement elem * @param String type */ triggerEvent: function( elem, type, event ) { if ( document.createEvent ) { event = document.createEvent("MouseEvents"); event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null); elem.dispatchEvent( event ); } else if ( elem.fireEvent ) { elem.fireEvent("on"+type); } }, // Safe object type checking is: function( type, obj ) { return QUnit.objectType( obj ) == type; }, objectType: function( obj ) { if (typeof obj === "undefined") { return "undefined"; // consider: typeof null === object } if (obj === null) { return "null"; } var type = Object.prototype.toString.call( obj ) .match(/^\[object\s(.*)\]$/)[1] || ''; switch (type) { case 'Number': if (isNaN(obj)) { return "nan"; } else { return "number"; } case 'String': case 'Boolean': case 'Array': case 'Date': case 'RegExp': case 'Function': return type.toLowerCase(); } if (typeof obj === "object") { return "object"; } return undefined; }, push: function(result, actual, expected, message) { var details = { result: result, message: message, actual: actual, expected: expected }; message = escapeInnerText(message) || (result ? "okay" : "failed"); message = '' + message + ""; expected = escapeInnerText(QUnit.jsDump.parse(expected)); actual = escapeInnerText(QUnit.jsDump.parse(actual)); var output = message + ''; if (actual != expected) { output += ''; output += ''; } if (!result) { var source = sourceFromStacktrace(); if (source) { details.source = source; output += ''; } } output += "
Expected:
' + expected + '
Result:
' + actual + '
Diff:
' + QUnit.diff(expected, actual) +'
Source:
' + escapeInnerText(source) + '
"; runLoggingCallbacks( 'log', QUnit, details ); config.current.assertions.push({ result: !!result, message: output }); }, url: function( params ) { params = extend( extend( {}, QUnit.urlParams ), params ); var querystring = "?", key; for ( key in params ) { querystring += encodeURIComponent( key ) + "=" + encodeURIComponent( params[ key ] ) + "&"; } return window.location.pathname + querystring.slice( 0, -1 ); }, extend: extend, id: id, addEvent: addEvent }); //QUnit.constructor is set to the empty F() above so that we can add to it's prototype later //Doing this allows us to tell if the following methods have been overwritten on the actual //QUnit object, which is a deprecated way of using the callbacks. extend(QUnit.constructor.prototype, { // Logging callbacks; all receive a single argument with the listed properties // run test/logs.html for any related changes begin: registerLoggingCallback('begin'), // done: { failed, passed, total, runtime } done: registerLoggingCallback('done'), // log: { result, actual, expected, message } log: registerLoggingCallback('log'), // testStart: { name } testStart: registerLoggingCallback('testStart'), // testDone: { name, failed, passed, total } testDone: registerLoggingCallback('testDone'), // moduleStart: { name } moduleStart: registerLoggingCallback('moduleStart'), // moduleDone: { name, failed, passed, total } moduleDone: registerLoggingCallback('moduleDone') }); if ( typeof document === "undefined" || document.readyState === "complete" ) { config.autorun = true; } QUnit.load = function() { runLoggingCallbacks( 'begin', QUnit, {} ); // Initialize the config, saving the execution queue var oldconfig = extend({}, config); QUnit.init(); extend(config, oldconfig); config.blocking = false; var urlConfigHtml = '', len = config.urlConfig.length; for ( var i = 0, val; i < len, val = config.urlConfig[i]; i++ ) { config[val] = QUnit.urlParams[val]; urlConfigHtml += ''; } var userAgent = id("qunit-userAgent"); if ( userAgent ) { userAgent.innerHTML = navigator.userAgent; } var banner = id("qunit-header"); if ( banner ) { banner.innerHTML = ' ' + banner.innerHTML + ' ' + urlConfigHtml; addEvent( banner, "change", function( event ) { var params = {}; params[ event.target.name ] = event.target.checked ? true : undefined; window.location = QUnit.url( params ); }); } var toolbar = id("qunit-testrunner-toolbar"); if ( toolbar ) { var filter = document.createElement("input"); filter.type = "checkbox"; filter.id = "qunit-filter-pass"; addEvent( filter, "click", function() { var ol = document.getElementById("qunit-tests"); if ( filter.checked ) { ol.className = ol.className + " hidepass"; } else { var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; ol.className = tmp.replace(/ hidepass /, " "); } if ( defined.sessionStorage ) { if (filter.checked) { sessionStorage.setItem("qunit-filter-passed-tests", "true"); } else { sessionStorage.removeItem("qunit-filter-passed-tests"); } } }); if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) { filter.checked = true; var ol = document.getElementById("qunit-tests"); ol.className = ol.className + " hidepass"; } toolbar.appendChild( filter ); var label = document.createElement("label"); label.setAttribute("for", "qunit-filter-pass"); label.innerHTML = "Hide passed tests"; toolbar.appendChild( label ); } var main = id('qunit-fixture'); if ( main ) { config.fixture = main.innerHTML; } if (config.autostart) { QUnit.start(); } }; addEvent(window, "load", QUnit.load); function done() { config.autorun = true; // Log the last module results if ( config.currentModule ) { runLoggingCallbacks( 'moduleDone', QUnit, { name: config.currentModule, failed: config.moduleStats.bad, passed: config.moduleStats.all - config.moduleStats.bad, total: config.moduleStats.all } ); } var banner = id("qunit-banner"), tests = id("qunit-tests"), runtime = +new Date - config.started, passed = config.stats.all - config.stats.bad, html = [ 'Tests completed in ', runtime, ' milliseconds.
', '', passed, ' tests of ', config.stats.all, ' passed, ', config.stats.bad, ' failed.' ].join(''); if ( banner ) { banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); } if ( tests ) { id( "qunit-testresult" ).innerHTML = html; } if ( config.altertitle && typeof document !== "undefined" && document.title ) { // show ✖ for good, ✔ for bad suite result in title // use escape sequences in case file gets loaded with non-utf-8-charset document.title = [ (config.stats.bad ? "\u2716" : "\u2714"), document.title.replace(/^[\u2714\u2716] /i, "") ].join(" "); } runLoggingCallbacks( 'done', QUnit, { failed: config.stats.bad, passed: passed, total: config.stats.all, runtime: runtime } ); } function validTest( name ) { var filter = config.filter, run = false; if ( !filter ) { return true; } var not = filter.charAt( 0 ) === "!"; if ( not ) { filter = filter.slice( 1 ); } if ( name.indexOf( filter ) !== -1 ) { return !not; } if ( not ) { run = true; } return run; } // so far supports only Firefox, Chrome and Opera (buggy) // could be extended in the future to use something like https://github.com/csnover/TraceKit function sourceFromStacktrace() { try { throw new Error(); } catch ( e ) { if (e.stacktrace) { // Opera return e.stacktrace.split("\n")[6]; } else if (e.stack) { // Firefox, Chrome return e.stack.split("\n")[4]; } else if (e.sourceURL) { // Safari, PhantomJS // TODO sourceURL points at the 'throw new Error' line above, useless //return e.sourceURL + ":" + e.line; } } } function escapeInnerText(s) { if (!s) { return ""; } s = s + ""; return s.replace(/[\&<>]/g, function(s) { switch(s) { case "&": return "&"; case "<": return "<"; case ">": return ">"; default: return s; } }); } function synchronize( callback ) { config.queue.push( callback ); if ( config.autorun && !config.blocking ) { process(); } } function process() { var start = (new Date()).getTime(); while ( config.queue.length && !config.blocking ) { if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { config.queue.shift()(); } else { window.setTimeout( process, 13 ); break; } } if (!config.blocking && !config.queue.length) { done(); } } function saveGlobal() { config.pollution = []; if ( config.noglobals ) { for ( var key in window ) { config.pollution.push( key ); } } } function checkPollution( name ) { var old = config.pollution; saveGlobal(); var newGlobals = diff( config.pollution, old ); if ( newGlobals.length > 0 ) { ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); } var deletedGlobals = diff( old, config.pollution ); if ( deletedGlobals.length > 0 ) { ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); } } // returns a new Array with the elements that are in a but not in b function diff( a, b ) { var result = a.slice(); for ( var i = 0; i < result.length; i++ ) { for ( var j = 0; j < b.length; j++ ) { if ( result[i] === b[j] ) { result.splice(i, 1); i--; break; } } } return result; } function fail(message, exception, callback) { if ( typeof console !== "undefined" && console.error && console.warn ) { console.error(message); console.error(exception); console.warn(callback.toString()); } else if ( window.opera && opera.postError ) { opera.postError(message, exception, callback.toString); } } function extend(a, b) { for ( var prop in b ) { if ( b[prop] === undefined ) { delete a[prop]; } else { a[prop] = b[prop]; } } return a; } function addEvent(elem, type, fn) { if ( elem.addEventListener ) { elem.addEventListener( type, fn, false ); } else if ( elem.attachEvent ) { elem.attachEvent( "on" + type, fn ); } else { fn(); } } function id(name) { return !!(typeof document !== "undefined" && document && document.getElementById) && document.getElementById( name ); } function registerLoggingCallback(key){ return function(callback){ config[key].push( callback ); }; } // Supports deprecated method of completely overwriting logging callbacks function runLoggingCallbacks(key, scope, args) { //debugger; var callbacks; if ( QUnit.hasOwnProperty(key) ) { QUnit[key].call(scope, args); } else { callbacks = config[key]; for( var i = 0; i < callbacks.length; i++ ) { callbacks[i].call( scope, args ); } } } // Test for equality any JavaScript type. // Author: Philippe Rathé QUnit.equiv = function () { var innerEquiv; // the real equiv function var callers = []; // stack to decide between skip/abort functions var parents = []; // stack to avoiding loops from circular referencing // Call the o related callback with the given arguments. function bindCallbacks(o, callbacks, args) { var prop = QUnit.objectType(o); if (prop) { if (QUnit.objectType(callbacks[prop]) === "function") { return callbacks[prop].apply(callbacks, args); } else { return callbacks[prop]; // or undefined } } } var callbacks = function () { // for string, boolean, number and null function useStrictEquality(b, a) { if (b instanceof a.constructor || a instanceof b.constructor) { // to catch short annotaion VS 'new' annotation of a // declaration // e.g. var i = 1; // var j = new Number(1); return a == b; } else { return a === b; } } return { "string" : useStrictEquality, "boolean" : useStrictEquality, "number" : useStrictEquality, "null" : useStrictEquality, "undefined" : useStrictEquality, "nan" : function(b) { return isNaN(b); }, "date" : function(b, a) { return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf(); }, "regexp" : function(b, a) { return QUnit.objectType(b) === "regexp" && a.source === b.source && // the regex itself a.global === b.global && // and its modifers // (gmi) ... a.ignoreCase === b.ignoreCase && a.multiline === b.multiline; }, // - skip when the property is a method of an instance (OOP) // - abort otherwise, // initial === would have catch identical references anyway "function" : function() { var caller = callers[callers.length - 1]; return caller !== Object && typeof caller !== "undefined"; }, "array" : function(b, a) { var i, j, loop; var len; // b could be an object literal here if (!(QUnit.objectType(b) === "array")) { return false; } len = a.length; if (len !== b.length) { // safe and faster return false; } // track reference to avoid circular references parents.push(a); for (i = 0; i < len; i++) { loop = false; for (j = 0; j < parents.length; j++) { if (parents[j] === a[i]) { loop = true;// dont rewalk array } } if (!loop && !innerEquiv(a[i], b[i])) { parents.pop(); return false; } } parents.pop(); return true; }, "object" : function(b, a) { var i, j, loop; var eq = true; // unless we can proove it var aProperties = [], bProperties = []; // collection of // strings // comparing constructors is more strict than using // instanceof if (a.constructor !== b.constructor) { return false; } // stack constructor before traversing properties callers.push(a.constructor); // track reference to avoid circular references parents.push(a); for (i in a) { // be strict: don't ensures hasOwnProperty // and go deep loop = false; for (j = 0; j < parents.length; j++) { if (parents[j] === a[i]) loop = true; // don't go down the same path // twice } aProperties.push(i); // collect a's properties if (!loop && !innerEquiv(a[i], b[i])) { eq = false; break; } } callers.pop(); // unstack, we are done parents.pop(); for (i in b) { bProperties.push(i); // collect b's properties } // Ensures identical properties name return eq && innerEquiv(aProperties.sort(), bProperties .sort()); } }; }(); innerEquiv = function() { // can take multiple arguments var args = Array.prototype.slice.apply(arguments); if (args.length < 2) { return true; // end transition } return (function(a, b) { if (a === b) { return true; // catch the most you can } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || QUnit.objectType(a) !== QUnit.objectType(b)) { return false; // don't lose time with error prone cases } else { return bindCallbacks(a, callbacks, [ b, a ]); } // apply transition with (1..n) arguments })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length - 1)); }; return innerEquiv; }(); /** * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | * http://flesler.blogspot.com Licensed under BSD * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 * * @projectDescription Advanced and extensible data dumping for Javascript. * @version 1.0.0 * @author Ariel Flesler * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} */ QUnit.jsDump = (function() { function quote( str ) { return '"' + str.toString().replace(/"/g, '\\"') + '"'; }; function literal( o ) { return o + ''; }; function join( pre, arr, post ) { var s = jsDump.separator(), base = jsDump.indent(), inner = jsDump.indent(1); if ( arr.join ) arr = arr.join( ',' + s + inner ); if ( !arr ) return pre + post; return [ pre, inner + arr, base + post ].join(s); }; function array( arr, stack ) { var i = arr.length, ret = Array(i); this.up(); while ( i-- ) ret[i] = this.parse( arr[i] , undefined , stack); this.down(); return join( '[', ret, ']' ); }; var reName = /^function (\w+)/; var jsDump = { parse:function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance stack = stack || [ ]; var parser = this.parsers[ type || this.typeOf(obj) ]; type = typeof parser; var inStack = inArray(obj, stack); if (inStack != -1) { return 'recursion('+(inStack - stack.length)+')'; } //else if (type == 'function') { stack.push(obj); var res = parser.call( this, obj, stack ); stack.pop(); return res; } // else return (type == 'string') ? parser : this.parsers.error; }, typeOf:function( obj ) { var type; if ( obj === null ) { type = "null"; } else if (typeof obj === "undefined") { type = "undefined"; } else if (QUnit.is("RegExp", obj)) { type = "regexp"; } else if (QUnit.is("Date", obj)) { type = "date"; } else if (QUnit.is("Function", obj)) { type = "function"; } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") { type = "window"; } else if (obj.nodeType === 9) { type = "document"; } else if (obj.nodeType) { type = "node"; } else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) { type = "array"; } else { type = typeof obj; } return type; }, separator:function() { return this.multiline ? this.HTML ? '
' : '\n' : this.HTML ? ' ' : ' '; }, indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing if ( !this.multiline ) return ''; var chr = this.indentChar; if ( this.HTML ) chr = chr.replace(/\t/g,' ').replace(/ /g,' '); return Array( this._depth_ + (extra||0) ).join(chr); }, up:function( a ) { this._depth_ += a || 1; }, down:function( a ) { this._depth_ -= a || 1; }, setParser:function( name, parser ) { this.parsers[name] = parser; }, // The next 3 are exposed so you can use them quote:quote, literal:literal, join:join, // _depth_: 1, // This is the list of parsers, to modify them, use jsDump.setParser parsers:{ window: '[Window]', document: '[Document]', error:'[ERROR]', //when no parser is found, shouldn't happen unknown: '[Unknown]', 'null':'null', 'undefined':'undefined', 'function':function( fn ) { var ret = 'function', name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE if ( name ) ret += ' ' + name; ret += '('; ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); }, array: array, nodelist: array, arguments: array, object:function( map, stack ) { var ret = [ ]; QUnit.jsDump.up(); for ( var key in map ) { var val = map[key]; ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(val, undefined, stack)); } QUnit.jsDump.down(); return join( '{', ret, '}' ); }, node:function( node ) { var open = QUnit.jsDump.HTML ? '<' : '<', close = QUnit.jsDump.HTML ? '>' : '>'; var tag = node.nodeName.toLowerCase(), ret = open + tag; for ( var a in QUnit.jsDump.DOMAttrs ) { var val = node[QUnit.jsDump.DOMAttrs[a]]; if ( val ) ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); } return ret + close + open + '/' + tag + close; }, functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function var l = fn.length; if ( !l ) return ''; var args = Array(l); while ( l-- ) args[l] = String.fromCharCode(97+l);//97 is 'a' return ' ' + args.join(', ') + ' '; }, key:quote, //object calls it internally, the key part of an item in a map functionCode:'[code]', //function calls it internally, it's the content of the function attribute:quote, //node calls it internally, it's an html attribute value string:quote, date:quote, regexp:literal, //regex number:literal, 'boolean':literal }, DOMAttrs:{//attributes to dump from nodes, name=>realName id:'id', name:'name', 'class':'className' }, HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) indentChar:' ',//indentation unit multiline:true //if true, items in a collection, are separated by a \n, else just a space. }; return jsDump; })(); // from Sizzle.js function getText( elems ) { var ret = "", elem; for ( var i = 0; elems[i]; i++ ) { elem = elems[i]; // Get the text from text nodes and CDATA nodes if ( elem.nodeType === 3 || elem.nodeType === 4 ) { ret += elem.nodeValue; // Traverse everything else, except comment nodes } else if ( elem.nodeType !== 8 ) { ret += getText( elem.childNodes ); } } return ret; }; //from jquery.js function inArray( elem, array ) { if ( array.indexOf ) { return array.indexOf( elem ); } for ( var i = 0, length = array.length; i < length; i++ ) { if ( array[ i ] === elem ) { return i; } } return -1; } /* * Javascript Diff Algorithm * By John Resig (http://ejohn.org/) * Modified by Chu Alan "sprite" * * Released under the MIT license. * * More Info: * http://ejohn.org/projects/javascript-diff-algorithm/ * * Usage: QUnit.diff(expected, actual) * * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" */ QUnit.diff = (function() { function diff(o, n) { var ns = {}; var os = {}; for (var i = 0; i < n.length; i++) { if (ns[n[i]] == null) ns[n[i]] = { rows: [], o: null }; ns[n[i]].rows.push(i); } for (var i = 0; i < o.length; i++) { if (os[o[i]] == null) os[o[i]] = { rows: [], n: null }; os[o[i]].rows.push(i); } for (var i in ns) { if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { n[ns[i].rows[0]] = { text: n[ns[i].rows[0]], row: os[i].rows[0] }; o[os[i].rows[0]] = { text: o[os[i].rows[0]], row: ns[i].rows[0] }; } } for (var i = 0; i < n.length - 1; i++) { if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && n[i + 1] == o[n[i].row + 1]) { n[i + 1] = { text: n[i + 1], row: n[i].row + 1 }; o[n[i].row + 1] = { text: o[n[i].row + 1], row: i + 1 }; } } for (var i = n.length - 1; i > 0; i--) { if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && n[i - 1] == o[n[i].row - 1]) { n[i - 1] = { text: n[i - 1], row: n[i].row - 1 }; o[n[i].row - 1] = { text: o[n[i].row - 1], row: i - 1 }; } } return { o: o, n: n }; } return function(o, n) { o = o.replace(/\s+$/, ''); n = n.replace(/\s+$/, ''); var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); var str = ""; var oSpace = o.match(/\s+/g); if (oSpace == null) { oSpace = [" "]; } else { oSpace.push(" "); } var nSpace = n.match(/\s+/g); if (nSpace == null) { nSpace = [" "]; } else { nSpace.push(" "); } if (out.n.length == 0) { for (var i = 0; i < out.o.length; i++) { str += '' + out.o[i] + oSpace[i] + ""; } } else { if (out.n[0].text == null) { for (n = 0; n < out.o.length && out.o[n].text == null; n++) { str += '' + out.o[n] + oSpace[n] + ""; } } for (var i = 0; i < out.n.length; i++) { if (out.n[i].text == null) { str += '' + out.n[i] + nSpace[i] + ""; } else { var pre = ""; for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { pre += '' + out.o[n] + oSpace[n] + ""; } str += " " + out.n[i].text + nSpace[i] + pre; } } } return str; }; })(); })(this); ================================================ FILE: lib/raf/requestAnimationFrame.js ================================================ // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating // requestAnimationFrame polyfill by Erik Möller // fixes from Paul Irish and Tino Zijdel (function() { var lastTime = 0; var vendors = ['ms', 'moz', 'webkit', 'o']; for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; } if (!window.requestAnimationFrame) window.requestAnimationFrame = function(callback, element) { var currTime = new Date().getTime(); var timeToCall = Math.max(0, 16 - (currTime - lastTime)); var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall); lastTime = currTime + timeToCall; return id; }; if (!window.cancelAnimationFrame) window.cancelAnimationFrame = function(id) { clearTimeout(id); }; }()); ================================================ FILE: lib/rhino/LICENSE.txt ================================================ The majority of Rhino is MPL 1.1 / GPL 2.0 dual licensed: The Mozilla Public License (http://www.mozilla.org/MPL/MPL-1.1.txt): ============================================================================ MOZILLA PUBLIC LICENSE Version 1.1 --------------- 1. Definitions. 1.0.1. "Commercial Use" means distribution or otherwise making the Covered Code available to a third party. 1.1. "Contributor" means each entity that creates or contributes to the creation of Modifications. 1.2. "Contributor Version" means the combination of the Original Code, prior Modifications used by a Contributor, and the Modifications made by that particular Contributor. 1.3. "Covered Code" means the Original Code or Modifications or the combination of the Original Code and Modifications, in each case including portions thereof. 1.4. "Electronic Distribution Mechanism" means a mechanism generally accepted in the software development community for the electronic transfer of data. 1.5. "Executable" means Covered Code in any form other than Source Code. 1.6. "Initial Developer" means the individual or entity identified as the Initial Developer in the Source Code notice required by Exhibit A. 1.7. "Larger Work" means a work which combines Covered Code or portions thereof with code not governed by the terms of this License. 1.8. "License" means this document. 1.8.1. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently acquired, any and all of the rights conveyed herein. 1.9. "Modifications" means any addition to or deletion from the substance or structure of either the Original Code or any previous Modifications. When Covered Code is released as a series of files, a Modification is: A. Any addition to or deletion from the contents of a file containing Original Code or previous Modifications. B. Any new file that contains any part of the Original Code or previous Modifications. 1.10. "Original Code" means Source Code of computer software code which is described in the Source Code notice required by Exhibit A as Original Code, and which, at the time of its release under this License is not already Covered Code governed by this License. 1.10.1. "Patent Claims" means any patent claim(s), now owned or hereafter acquired, including without limitation, method, process, and apparatus claims, in any patent Licensable by grantor. 1.11. "Source Code" means the preferred form of the Covered Code for making modifications to it, including all modules it contains, plus any associated interface definition files, scripts used to control compilation and installation of an Executable, or source code differential comparisons against either the Original Code or another well known, available Covered Code of the Contributor's choice. The Source Code can be in a compressed or archival form, provided the appropriate decompression or de-archiving software is widely available for no charge. 1.12. "You" (or "Your") means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License or a future version of this License issued under Section 6.1. For legal entities, "You" includes any entity which controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. Source Code License. 2.1. The Initial Developer Grant. The Initial Developer hereby grants You a world-wide, royalty-free, non-exclusive license, subject to third party intellectual property claims: (a) under intellectual property rights (other than patent or trademark) Licensable by Initial Developer to use, reproduce, modify, display, perform, sublicense and distribute the Original Code (or portions thereof) with or without Modifications, and/or as part of a Larger Work; and (b) under Patents Claims infringed by the making, using or selling of Original Code, to make, have made, use, practice, sell, and offer for sale, and/or otherwise dispose of the Original Code (or portions thereof). (c) the licenses granted in this Section 2.1(a) and (b) are effective on the date Initial Developer first distributes Original Code under the terms of this License. (d) Notwithstanding Section 2.1(b) above, no patent license is granted: 1) for code that You delete from the Original Code; 2) separate from the Original Code; or 3) for infringements caused by: i) the modification of the Original Code or ii) the combination of the Original Code with other software or devices. 2.2. Contributor Grant. Subject to third party intellectual property claims, each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license (a) under intellectual property rights (other than patent or trademark) Licensable by Contributor, to use, reproduce, modify, display, perform, sublicense and distribute the Modifications created by such Contributor (or portions thereof) either on an unmodified basis, with other Modifications, as Covered Code and/or as part of a Larger Work; and (b) under Patent Claims infringed by the making, using, or selling of Modifications made by that Contributor either alone and/or in combination with its Contributor Version (or portions of such combination), to make, use, sell, offer for sale, have made, and/or otherwise dispose of: 1) Modifications made by that Contributor (or portions thereof); and 2) the combination of Modifications made by that Contributor with its Contributor Version (or portions of such combination). (c) the licenses granted in Sections 2.2(a) and 2.2(b) are effective on the date Contributor first makes Commercial Use of the Covered Code. (d) Notwithstanding Section 2.2(b) above, no patent license is granted: 1) for any code that Contributor has deleted from the Contributor Version; 2) separate from the Contributor Version; 3) for infringements caused by: i) third party modifications of Contributor Version or ii) the combination of Modifications made by that Contributor with other software (except as part of the Contributor Version) or other devices; or 4) under Patent Claims infringed by Covered Code in the absence of Modifications made by that Contributor. 3. Distribution Obligations. 3.1. Application of License. The Modifications which You create or to which You contribute are governed by the terms of this License, including without limitation Section 2.2. The Source Code version of Covered Code may be distributed only under the terms of this License or a future version of this License released under Section 6.1, and You must include a copy of this License with every copy of the Source Code You distribute. You may not offer or impose any terms on any Source Code version that alters or restricts the applicable version of this License or the recipients' rights hereunder. However, You may include an additional document offering the additional rights described in Section 3.5. 3.2. Availability of Source Code. Any Modification which You create or to which You contribute must be made available in Source Code form under the terms of this License either on the same media as an Executable version or via an accepted Electronic Distribution Mechanism to anyone to whom you made an Executable version available; and if made available via Electronic Distribution Mechanism, must remain available for at least twelve (12) months after the date it initially became available, or at least six (6) months after a subsequent version of that particular Modification has been made available to such recipients. You are responsible for ensuring that the Source Code version remains available even if the Electronic Distribution Mechanism is maintained by a third party. 3.3. Description of Modifications. You must cause all Covered Code to which You contribute to contain a file documenting the changes You made to create that Covered Code and the date of any change. You must include a prominent statement that the Modification is derived, directly or indirectly, from Original Code provided by the Initial Developer and including the name of the Initial Developer in (a) the Source Code, and (b) in any notice in an Executable version or related documentation in which You describe the origin or ownership of the Covered Code. 3.4. Intellectual Property Matters (a) Third Party Claims. If Contributor has knowledge that a license under a third party's intellectual property rights is required to exercise the rights granted by such Contributor under Sections 2.1 or 2.2, Contributor must include a text file with the Source Code distribution titled "LEGAL" which describes the claim and the party making the claim in sufficient detail that a recipient will know whom to contact. If Contributor obtains such knowledge after the Modification is made available as described in Section 3.2, Contributor shall promptly modify the LEGAL file in all copies Contributor makes available thereafter and shall take other steps (such as notifying appropriate mailing lists or newsgroups) reasonably calculated to inform those who received the Covered Code that new knowledge has been obtained. (b) Contributor APIs. If Contributor's Modifications include an application programming interface and Contributor has knowledge of patent licenses which are reasonably necessary to implement that API, Contributor must also include this information in the LEGAL file. (c) Representations. Contributor represents that, except as disclosed pursuant to Section 3.4(a) above, Contributor believes that Contributor's Modifications are Contributor's original creation(s) and/or Contributor has sufficient rights to grant the rights conveyed by this License. 3.5. Required Notices. You must duplicate the notice in Exhibit A in each file of the Source Code. If it is not possible to put such notice in a particular Source Code file due to its structure, then You must include such notice in a location (such as a relevant directory) where a user would be likely to look for such a notice. If You created one or more Modification(s) You may add your name as a Contributor to the notice described in Exhibit A. You must also duplicate this License in any documentation for the Source Code where You describe recipients' rights or ownership rights relating to Covered Code. You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Code. However, You may do so only on Your own behalf, and not on behalf of the Initial Developer or any Contributor. You must make it absolutely clear than any such warranty, support, indemnity or liability obligation is offered by You alone, and You hereby agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer or such Contributor as a result of warranty, support, indemnity or liability terms You offer. 3.6. Distribution of Executable Versions. You may distribute Covered Code in Executable form only if the requirements of Section 3.1-3.5 have been met for that Covered Code, and if You include a notice stating that the Source Code version of the Covered Code is available under the terms of this License, including a description of how and where You have fulfilled the obligations of Section 3.2. The notice must be conspicuously included in any notice in an Executable version, related documentation or collateral in which You describe recipients' rights relating to the Covered Code. You may distribute the Executable version of Covered Code or ownership rights under a license of Your choice, which may contain terms different from this License, provided that You are in compliance with the terms of this License and that the license for the Executable version does not attempt to limit or alter the recipient's rights in the Source Code version from the rights set forth in this License. If You distribute the Executable version under a different license You must make it absolutely clear that any terms which differ from this License are offered by You alone, not by the Initial Developer or any Contributor. You hereby agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer or such Contributor as a result of any such terms You offer. 3.7. Larger Works. You may create a Larger Work by combining Covered Code with other code not governed by the terms of this License and distribute the Larger Work as a single product. In such a case, You must make sure the requirements of this License are fulfilled for the Covered Code. 4. Inability to Comply Due to Statute or Regulation. If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Code due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be included in the LEGAL file described in Section 3.4 and must be included with all distributions of the Source Code. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Application of this License. This License applies to code to which the Initial Developer has attached the notice in Exhibit A and to related Covered Code. 6. Versions of the License. 6.1. New Versions. Netscape Communications Corporation ("Netscape") may publish revised and/or new versions of the License from time to time. Each version will be given a distinguishing version number. 6.2. Effect of New Versions. Once Covered Code has been published under a particular version of the License, You may always continue to use it under the terms of that version. You may also choose to use such Covered Code under the terms of any subsequent version of the License published by Netscape. No one other than Netscape has the right to modify the terms applicable to Covered Code created under this License. 6.3. Derivative Works. If You create or use a modified version of this License (which you may only do in order to apply it to code which is not already Covered Code governed by this License), You must (a) rename Your license so that the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", "MPL", "NPL" or any confusingly similar phrase do not appear in your license (except to note that your license differs from this License) and (b) otherwise make it clear that Your version of the license contains terms which differ from the Mozilla Public License and Netscape Public License. (Filling in the name of the Initial Developer, Original Code or Contributor in the notice described in Exhibit A shall not of themselves be deemed to be modifications of this License.) 7. DISCLAIMER OF WARRANTY. COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. 8. TERMINATION. 8.1. This License and the rights granted hereunder will terminate automatically if You fail to comply with terms herein and fail to cure such breach within 30 days of becoming aware of the breach. All sublicenses to the Covered Code which are properly granted shall survive any termination of this License. Provisions which, by their nature, must remain in effect beyond the termination of this License shall survive. 8.2. If You initiate litigation by asserting a patent infringement claim (excluding declatory judgment actions) against Initial Developer or a Contributor (the Initial Developer or Contributor against whom You file such action is referred to as "Participant") alleging that: (a) such Participant's Contributor Version directly or indirectly infringes any patent, then any and all rights granted by such Participant to You under Sections 2.1 and/or 2.2 of this License shall, upon 60 days notice from Participant terminate prospectively, unless if within 60 days after receipt of notice You either: (i) agree in writing to pay Participant a mutually agreeable reasonable royalty for Your past and future use of Modifications made by such Participant, or (ii) withdraw Your litigation claim with respect to the Contributor Version against such Participant. If within 60 days of notice, a reasonable royalty and payment arrangement are not mutually agreed upon in writing by the parties or the litigation claim is not withdrawn, the rights granted by Participant to You under Sections 2.1 and/or 2.2 automatically terminate at the expiration of the 60 day notice period specified above. (b) any software, hardware, or device, other than such Participant's Contributor Version, directly or indirectly infringes any patent, then any rights granted to You by such Participant under Sections 2.1(b) and 2.2(b) are revoked effective as of the date You first made, used, sold, distributed, or had made, Modifications made by that Participant. 8.3. If You assert a patent infringement claim against Participant alleging that such Participant's Contributor Version directly or indirectly infringes any patent where such claim is resolved (such as by license or settlement) prior to the initiation of patent infringement litigation, then the reasonable value of the licenses granted by such Participant under Sections 2.1 or 2.2 shall be taken into account in determining the amount or value of any payment or license. 8.4. In the event of termination under Sections 8.1 or 8.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or any distributor hereunder prior to termination shall survive termination. 9. LIMITATION OF LIABILITY. UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. 10. U.S. GOVERNMENT END USERS. The Covered Code is a "commercial item," as that term is defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer software" and "commercial computer software documentation," as such terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all U.S. Government End Users acquire Covered Code with only those rights set forth herein. 11. MISCELLANEOUS. This License represents the complete agreement concerning subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. This License shall be governed by California law provisions (except to the extent applicable law, if any, provides otherwise), excluding its conflict-of-law provisions. With respect to disputes in which at least one party is a citizen of, or an entity chartered or registered to do business in the United States of America, any litigation relating to this License shall be subject to the jurisdiction of the Federal Courts of the Northern District of California, with venue lying in Santa Clara County, California, with the losing party responsible for costs, including without limitation, court costs and reasonable attorneys' fees and expenses. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not apply to this License. 12. RESPONSIBILITY FOR CLAIMS. As between Initial Developer and the Contributors, each party is responsible for claims and damages arising, directly or indirectly, out of its utilization of rights under this License and You agree to work with Initial Developer and Contributors to distribute such responsibility on an equitable basis. Nothing herein is intended or shall be deemed to constitute any admission of liability. 13. MULTIPLE-LICENSED CODE. Initial Developer may designate portions of the Covered Code as "Multiple-Licensed". "Multiple-Licensed" means that the Initial Developer permits you to utilize portions of the Covered Code under Your choice of the NPL or the alternative licenses, if any, specified by the Initial Developer in the file described in Exhibit A. EXHIBIT A -Mozilla Public License. ``The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is ______________________________________. The Initial Developer of the Original Code is ________________________. Portions created by ______________________ are Copyright (C) ______ _______________________. All Rights Reserved. Contributor(s): ______________________________________. Alternatively, the contents of this file may be used under the terms of the _____ license (the "[___] License"), in which case the provisions of [______] License are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of the [____] License and not to allow others to use your version of this file under the MPL, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the [___] License. If you do not delete the provisions above, a recipient may use your version of this file under either the MPL or the [___] License." [NOTE: The text of this Exhibit A may differ slightly from the text of the notices in the Source Code files of the Original Code. You should use the text of this Exhibit A rather than the text found in the Original Code Source Code for Your Modifications.] ============================================================================ ============================================================================ GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. ============================================================================ Additionally, some files (currently the contents of toolsrc/org/mozilla/javascript/tools/debugger/treetable/) are available only under the following license: ============================================================================ * Copyright 1997, 1998 Sun Microsystems, Inc. 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 Sun Microsystems 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 THE COPYRIGHT OWNER OR * CONTRIBUTORS 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. ============================================================================ ================================================ FILE: lib/rhino/download.url ================================================ [InternetShortcut] URL=http://www.mozilla.org/rhino/download.html ================================================ FILE: package.json ================================================ { "name": "countdown", "version": "2.6.1", "description": "A simple JavaScript API for producing an accurate, intuitive description of the timespan between two Date instances.", "homepage": "http://countdownjs.org", "author": "Stephen McKamey (http://mck.me)", "license": "MIT", "repository": { "type": "git", "url": "https://github.com/mckamey/countdownjs.git" }, "files": [ "countdown.js", "README.md", "LICENSE.txt" ], "main": "countdown.js", "keywords": [ "countdown", "timer", "clock", "date", "time", "timespan", "year", "month", "week", "day", "hour", "minute", "second" ] } ================================================ FILE: readme.html ================================================ Countdown.js

Countdown.js

A simple JavaScript API for producing an accurate, intuitive description of the timespan between two Date instances.


The Motivation

While seemingly a trivial problem, the human descriptions for a span of time tend to be fuzzier than a computer naturally computes. More specifically, months are an inherently messed up unit of time. For instance, when a human says "in 1 month" how long do they mean? Banks often interpret this as thirty days but that is only correct one third of the time. People casually talk about a month being four weeks long but there is only one month in a year which is four weeks long and it is only that long about three quarters of the time. Even intuitively defining these terms can be problematic. For instance, what is the date one month after January 31st, 2001? JavaScript will happily call this March 3rd, 2001. Humans will typically debate either February 28th, 2001 or March 1st, 2001. It seems there isn't a "right" answer, per se.

The Algorithm

Countdown.js emphasizes producing intuitively correct description of timespans which are consistent as time goes on. To do this, Countdown.js uses the concept of "today's date next month" to mean "a month from now". As the days go by, Countdown.js produces consecutively increasing or decreasing counts without inconsistent jumps. The range of accuracy is only limited by the underlying system clock.

Countdown.js approaches finding the difference between two times like an elementary school subtraction problem. Each unit acts like a base-10 place where any overflow is carried to the next highest unit, and any underflow is borrowed from the next highest unit. In base-10 subtraction, every column is worth 10 times the previous column. With time, it is a little more complex since the conversions between the units of time are not the same and months are an inconsistent number of days. Internally, Countdown.js maintains the concept of a "reference month" which determines how many days a given month or year represents. In the final step of the algorithm, Countdown.js then prunes the set of time units down to only those requested, forcing larger units down to smaller.

Time Zones & Daylight Savings Time

As of v2.4, Countdown.js performs all calculations with respect to the viewer's local time zone. Earlier versions performed difference calculations in UTC, which is generally the correct way to do math on time. In this situation, however, an issue with using UTC happens when either of the two dates being worked with is within one time zone offset of a month boundary. If the UTC interpretation of that instant in time is in a different month than that of the local time zone, then the viewer's perception is that the calculated time span is incorrect. This is the heart of the problem that Countdown.js attempts to solve: talking about spans of time can be ambiguous. Nearly all bugs reported for Countdown.js have been because the viewer expects something different due to their particular time zone.

JavaScript (ECMA-262) only works with dates as UTC or the local time zone, not arbitrary time zones. By design, all JS Date objects represent an instant in time (milliseconds since midnight Jan 1, 1970 in UTC) interpreted as the user's local time. Since most humans think about local time not UTC, it the most makes sense to perform this time span algorithm in reference to local time.

Daylight Savings Time further complicates things, creating hours which get repeated and hours which cannot exist. Countdown.js effectively ignores these edge cases and talks about time preferring human intuition about time over surprise exactness. Example: A viewer asks for the description from noon the day before a daylight savings begins to noon the day after. A computer would answer "23 hours" whereas a human would confidently answer "1 day" even after being reminded to "Spring Forward". The computer is technically more accurate but this is not the value that humans actually expect or desire. Humans pretend that time is simple and makes sense. Unfortunately, humans made time far more complex than it needed to be with time zones and daylight savings. UTC simplifies time but at the cost of being inconsistent with human experience.


The API

A simple but flexible API is the goal of Countdown.js. There is one global function with a set of static constants:

var timespan = countdown(start|callback, end|callback, units, max, digits);

The parameters are a starting Date, ending Date, an optional set of units, an optional maximum number of units, and an optional maximum number of decimal places on the smallest unit. units defaults to countdown.DEFAULTS, max defaults to NaN (all specified units), digits defaults to 0.

countdown.ALL =
    countdown.MILLENNIA |
    countdown.CENTURIES |
    countdown.DECADES |
    countdown.YEARS |
    countdown.MONTHS |
    countdown.WEEKS |
    countdown.DAYS |
    countdown.HOURS |
    countdown.MINUTES |
    countdown.SECONDS |
    countdown.MILLISECONDS;

countdown.DEFAULTS =
    countdown.YEARS |
    countdown.MONTHS |
    countdown.DAYS |
    countdown.HOURS |
    countdown.MINUTES |
    countdown.SECONDS;

This allows a very minimal call to accept the defaults and get the time since/until a single date. For example:

countdown( new Date(2000, 0, 1) ).toString();

This will produce a human readable description like:

11 years, 8 months, 4 days, 10 hours, 12 minutes and 43 seconds

The start / end arguments

The parameters start and end can be one of several values:

  1. null which indicates "now".
  2. a JavaScript Date object.
  3. a number specifying the number of milliseconds since midnight Jan 1, 1970 UTC (i.e., the "UNIX epoch").
  4. a callback function accepting one timespan argument.

To reference a specific instant in time, either use a number offset from the epoch, or a JavaScript Date object instantiated with the specific offset from the epoch. In JavaScript, if a Date object is instantiated using year/month/date/etc values, then those values are interpreted in reference to the browser's local time zone and daylight savings settings.

If start and end are both specified, then repeated calls to countdown(…) will always return the same result. If one date argument is left null while the other is provided, then repeated calls will count up if the provided date is in the past, and it will count down if the provided date is in the future. For example,

var daysSinceLastWorkplaceAccident = countdown(507314280000, null, countdown.DAYS);

If a callback function is supplied, then an interval timer will be started with a frequency based upon the smallest unit (e.g., if countdown.SECONDS is the smallest unit, the callback will be invoked once per second). Rather than returning a Timespan object, the timer's ID will be returned to allow canceling by passing into window.clearInterval(id). For example, to show a timer since the page first loaded:

var timerId =
  countdown(
    new Date(),
    function(ts) {
      document.getElementById('pageTimer').innerHTML = ts.toHTML("strong");
    },
    countdown.HOURS|countdown.MINUTES|countdown.SECONDS);

// later on this timer may be stopped
window.clearInterval(timerId);

The units argument

The static units constants can be combined using standard bitwise operators. For example, to explicitly include "months or days" use bitwise-OR:

countdown.MONTHS | countdown.DAYS

To explicitly exclude units like "not weeks and not milliseconds" combine bitwise-NOT and bitwise-AND:

~countdown.WEEKS & ~countdown.MILLISECONDS

Equivalently, to specify everything but "not weeks or milliseconds" wrap bitwise-NOT around bitwise-OR:

~(countdown.WEEKS | countdown.MILLISECONDS)

The max argument

The next optional argument max specifies a maximum number of unit labels to display. This allows specifying which units are interesting but only displaying the max most significant units.

countdown(start, end, units).toString() => "5 years, 1 month, 19 days, 12 hours and 17 minutes"

Specifying max as 2 ensures that only the two most significant units are displayed (note the rounding of the least significant unit):

countdown(start, end, units, 2).toString() => "5 years and 2 months"

Negative or zero values of max are ignored.

Breaking change in v2.3.0!

Previously, the max number of unit labels argument used to be specified when formatting in timespan.toString(…) and timespan.toHTML(…). v2.3.0 moves it to countdown(…), which improves efficiency as well as enabling fractional units (see below).

The digits argument

The final optional argument digits allows fractional values on the smallest unit.

countdown(start, end, units, max).toString() => "5 years and 2 months"

Specifying digits as 2 allows up to 2 digits beyond the decimal point to be displayed (note the rounding of the least significant unit):

countdown(start, end, units, max, 2).toString() => "5 years and 1.65 months"

digits must be between 0 and 20, inclusive.

Rounding

With the calculations of fractional units in v2.3.0, the smallest displayed unit now properly rounds. Previously, the equivalent of "1.99 years" would be truncated to "1 year", as of v2.3.0 it will display as "2 years".

Typically, this is the intended interpretation but there are a few circumstances where people expect the truncated behavior. For example, people often talk about their age as the lowest possible interpretation. e.g., they claim "39-years-old" right up until the morning of their 40th birthday (some people do even for years after!). In these cases, after calling countdown(start,end,units,max,20) with the largest possible number of digits, you might want to set ts.years = Math.floor(ts.years) before calling ts.toString(). The vain might want you to set ts.years = Math.min(ts.years, 39)!

Timespan result

The return value is a Timespan object which always contains the following fields:

  • Date start: the starting date object used for the calculation
  • Date end: the ending date object used for the calculation
  • Number units: the units specified
  • Number value: total milliseconds difference (i.e., end - start). If end < start then value will be negative.

Typically the end occurs after start, but if the arguments were reversed, the only difference is Timespan.value will be negative. The sign of value can be used to determine if the event occurs in the future or in the past.

The following time unit fields are only present if their corresponding units were requested:

  • Number millennia
  • Number centuries
  • Number decades
  • Number years
  • Number months
  • Number days
  • Number hours
  • Number minutes
  • Number seconds
  • Number milliseconds

Finally, Timespan has two formatting methods each with some optional parameters. If the difference between start and end is less than the requested granularity of units, then toString(…) and toHTML(…) will return the empty label (defaults to an empty string).

  • String toString(emptyLabel): formats the Timespan object as an English sentence. e.g., using the same input:
    ts.toString() => "5 years, 1 month, 19 days, 12 hours and 17 minutes"
  • String toHTML(tagName, emptyLabel): formats the Timespan object as an English sentence, with the specified HTML tag wrapped around each unit. If no tag name is provided, "span" is used. e.g., using the same input:
    ts.toHTML() => "<span>5 years</span>, <span>1 month</span>, <span>19 days</span>, <span>12 hours</span> and <span>17 minutes</span>"
    
    ts.toHTML("em") => "<em>5 years</em>, <em>1 month</em>, <em>19 days</em>, <em>12 hours</em> and <em>17 minutes</em>"

Localization

Very basic localization is supported via the static setLabels and resetLabels methods. These change the functionality for all timespans on the page.

countdown.resetLabels();

countdown.setLabels(singular, plural, last, delim, empty, formatter);

The arguments:

  • singular is a pipe ('|') delimited ascending list of singular unit name overrides
  • plural is a pipe ('|') delimited ascending list of plural unit name overrides
  • last is a delimiter before the last unit (default: ' and ')
  • delim is a delimiter to use between all other units (default: ', ')
  • empty is a label to use when all units are zero (default: '')
  • formatter is a function which takes a number and returns a string (default uses Number.toString()),
    allowing customization of the way numbers are formatted, e.g., commas every 3 digits or some unique style that is specific to your locale.
Note that the spacing is part of the labels.

The following examples would translate the output into Brazilian Portuguese and French, respectively:

countdown.setLabels(
	' milissegundo| segundo| minuto| hora| dia| semana| mês| ano| década| século| milênio',
	' milissegundos| segundos| minutos| horas| dias| semanas| meses| anos| décadas| séculos| milênios',
	' e ',
	' + ',
	'agora');

countdown.setLabels(
	' milliseconde| seconde| minute| heure| jour| semaine| mois| année| décennie| siècle| millénaire',
	' millisecondes| secondes| minutes| heures| jours| semaines| mois| années| décennies| siècles| millénaires',
	' et ',
	', ',
	'maintenant');

If you only wanted to override some of the labels just leave the other pipe-delimited places empty. Similarly, leave off any of the delimiter arguments which do not need overriding.

countdown.setLabels(
	'||| hr| d',
	'ms| sec|||| wks|| yrs',
	', and finally ');

ts.toString() => "1 millennium, 2 centuries, 5 yrs, 1 month, 7 wks, 19 days, 1 hr, 2 minutes, 17 sec, and finally 1 millisecond"

If you only wanted to override the empty label:

countdown.setLabels(
		null,
		null,
		null,
		null,
		'Now.');

ts.toString() => "Now."

The following would be effectively the same as calling countdown.resetLabels():

countdown.setLabels(
	' millisecond| second| minute| hour| day| week| month| year| decade| century| millennium',
	' milliseconds| seconds| minutes| hours| days| weeks| months| years| decades| centuries| millennia',
	' and ',
	', ',
	'',
	function(n){ return n.toString(); });

License

Distributed under the terms of The MIT license.

================================================ FILE: test/fonts/SIL Open Font License 1.1.txt ================================================ This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ----------------------------------------------------------- PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement(s). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. ================================================ FILE: test/formatTests.js ================================================ try{ module('Timespan.toString()'); test('Default empty label override', function() { var input = countdown(0, 0, countdown.ALL); countdown.setFormat({ empty: 'Now.' }); var expected = 'Now.'; var actual = input.toString(); countdown.resetFormat(); same(actual, expected, ''); }); test('Empty label override', function() { var input = countdown(0, 0, countdown.ALL); var expected = 'Now!'; var actual = input.toString('Now!'); same(actual, expected, ''); }); test('Default empty label override, overridden', function() { var input = countdown(0, 0, countdown.ALL); countdown.setFormat({ empty: 'Now.' }); var expected = 'Right now!'; var actual = input.toString('Right now!'); countdown.resetFormat(); same(actual, expected, ''); }); test('Zero', function() { var input = countdown(0, 0, countdown.ALL); var expected = ''; var actual = input.toString(); same(actual, expected, ''); }); test('1 ms', function() { var input = countdown(0, 1, countdown.ALL); var expected = '1 millisecond'; var actual = input.toString(); same(actual, expected, ''); }); test('2 ms', function() { var input = countdown(0, 2, countdown.ALL); var expected = '2 milliseconds'; var actual = input.toString(); same(actual, expected, ''); }); test('1 sec, 2 ms', function() { var input = countdown(1000, 2002, countdown.ALL); var expected = '1 second and 2 milliseconds'; var actual = input.toString(); same(actual, expected, ''); }); test('2 sec, 1 ms', function() { var input = countdown(10000, 12001, countdown.ALL); var expected = '2 seconds and 1 millisecond'; var actual = input.toString(); same(actual, expected, ''); }); test('1 day, reversed', function() { var start = new Date(1970, 0, 1, 0, 0, 0, 0); var end = new Date(1970, 0, 1, 0, 0, 0, 0); end.setDate( end.getDate()+1 ); var input = countdown(end, start, countdown.ALL); var expected = '1 day'; var actual = input.toString(); same(actual, expected, ''); }); test('15 days', function() { var start = new Date(1970, 0, 1, 0, 0, 0, 0); var end = new Date(1970, 0, 1, 0, 0, 0, 0); end.setDate( end.getDate()+15 ); var input = countdown(end, start, countdown.ALL); var expected = '2 weeks and 1 day'; var actual = input.toString(); same(actual, expected, ''); }); test('32 days', function() { var start = new Date(1970, 0, 1, 0, 0, 0, 0); var end = new Date(1970, 0, 1, 0, 0, 0, 0); end.setDate( end.getDate()+32 ); var input = countdown(end, start, countdown.ALL); var expected = '1 month and 1 day'; var actual = input.toString(); same(actual, expected, ''); }); test('Millennium, week', function() { var start = new Date(0); var end = new Date(10 * 100 * 365.25 * 24 * 60 * 60 * 1000);// millennium, week // account for local timezone start.setMinutes(start.getMinutes() + start.getTimezoneOffset()); end.setMinutes(end.getMinutes() + end.getTimezoneOffset()); var input = countdown(start, end, countdown.ALL); var expected = '1 millennium and 1 week'; var actual = input.toString(); same(actual, expected, ''); }); test('One of each', function() { var start = new Date(0); var end = new Date( (11 * 100) * (365.25 * 24 * 60 * 60 * 1000) + // millennium, century, week, day (365 * 24 * 60 * 60 * 1000) + // year (31 * 24 * 60 * 60 * 1000) + // month (60 * 60 * 1000) + // hour (60 * 1000) + // min 1000 + // sec 1); // ms // account for local timezone start.setMinutes(start.getMinutes() + start.getTimezoneOffset()); end.setMinutes(end.getMinutes() + end.getTimezoneOffset()); var input = countdown(start, end, countdown.ALL); var expected = '1 millennium, 1 century, 1 year, 1 month, 1 week, 1 day, 1 hour, 1 minute, 1 second and 1 millisecond'; var actual = input.toString(); same(actual, expected, ''); }); test('Two of each', function() { var start = new Date(0); var end = new Date( (22 * 100) * (365.25 * 24 * 60 * 60 * 1000) + // millennium, century, week, day (2 * 365 * 24 * 60 * 60 * 1000) + // year (31 + 29) * (24 * 60 * 60 * 1000) + // month (2 * 60 * 60 * 1000) + // hour (2 * 60 * 1000) + // min (2 * 1000) + // sec 2); // ms // account for local timezone start.setMinutes(start.getMinutes() + start.getTimezoneOffset()); end.setMinutes(end.getMinutes() + end.getTimezoneOffset()); var input = countdown(start, end, countdown.ALL); var expected = '2 millennia, 2 centuries, 2 years, 2 months, 2 weeks, 2 days, 2 hours, 2 minutes, 2 seconds and 2 milliseconds'; var actual = input.toString(); same(actual, expected, ''); }); module('Timespan.toString(number)'); test('Millennium, week; 1 max', function() { var start = new Date(0); var end = new Date(10 * 100 * 365.25 * 24 * 60 * 60 * 1000); // account for local timezone start.setMinutes(start.getMinutes() + start.getTimezoneOffset()); end.setMinutes(end.getMinutes() + end.getTimezoneOffset()); var input = countdown(start, end, countdown.ALL, 1); var expected = '1 millennium'; var actual = input.toString(); same(actual, expected, ''); }); test('Millennium, week; 2 max', function() { var start = new Date(0); var end = new Date(10 * 100 * 365.25 * 24 * 60 * 60 * 1000); // account for local timezone start.setMinutes(start.getMinutes() + start.getTimezoneOffset()); end.setMinutes(end.getMinutes() + end.getTimezoneOffset()); var input = countdown(start, end, countdown.ALL, 2); var expected = '1 millennium and 1 week'; var actual = input.toString(); same(actual, expected, ''); }); test('One of each; 3 max', function() { var start = new Date(0); var end = new Date( (11 * 100) * (365.25 * 24 * 60 * 60 * 1000) + // millennium, century, week, day (365 * 24 * 60 * 60 * 1000) + // year (31 * 24 * 60 * 60 * 1000) + // month (60 * 60 * 1000) + // hour (60 * 1000) + // min 1000 + // sec 1); // ms // account for local timezone start.setMinutes(start.getMinutes() + start.getTimezoneOffset()); end.setMinutes(end.getMinutes() + end.getTimezoneOffset()); var input = countdown(start, end, countdown.ALL, 3); var expected = '1 millennium, 1 century and 1 year'; var actual = input.toString(); same(actual, expected, ''); }); test('One of each; zero max', function() { var start = new Date(0); var end = new Date( (11 * 100) * (365.25 * 24 * 60 * 60 * 1000) + // millennium, century, week, day (365 * 24 * 60 * 60 * 1000) + // year (31 * 24 * 60 * 60 * 1000) + // month (60 * 60 * 1000) + // hour (60 * 1000) + // min 1000 + // sec 1); // ms // account for local timezone start.setMinutes(start.getMinutes() + start.getTimezoneOffset()); end.setMinutes(end.getMinutes() + end.getTimezoneOffset()); var input = countdown(start, end, countdown.ALL, 0); var expected = '1 millennium, 1 century, 1 year, 1 month, 1 week, 1 day, 1 hour, 1 minute, 1 second and 1 millisecond'; var actual = input.toString(); same(actual, expected, ''); }); test('One of each; -2 max', function() { var start = new Date(0); var end = new Date( (11 * 100) * (365.25 * 24 * 60 * 60 * 1000) + // millennium, century, week, day (365 * 24 * 60 * 60 * 1000) + // year (31 * 24 * 60 * 60 * 1000) + // month (60 * 60 * 1000) + // hour (60 * 1000) + // min 1000 + // sec 1); // ms // account for local timezone start.setMinutes(start.getMinutes() + start.getTimezoneOffset()); end.setMinutes(end.getMinutes() + end.getTimezoneOffset()); var input = countdown(start, end, countdown.ALL, -2); var expected = '1 millennium, 1 century, 1 year, 1 month, 1 week, 1 day, 1 hour, 1 minute, 1 second and 1 millisecond'; var actual = input.toString(); same(actual, expected, ''); }); test('Almost 2 minutes, full 3 digits', function() { var input = countdown(new Date(915220800000), new Date(915220919999), countdown.DEFAULTS, 0, 3); var expected = "1 minute and 59.999 seconds"; var actual = input.toString(); same(actual, expected, ''); }); test('Almost 2 minutes, rounded 2 digits', function() { var input = countdown(new Date(915220800000), new Date(915220919999), countdown.DEFAULTS, 0, 2); var expected = "2 minutes"; var actual = input.toString(); same(actual, expected, ''); }); module('Timespan.toHTML(tag)'); test('Default empty label override', function() { var input = countdown(0, 0, countdown.ALL); countdown.setFormat({ empty: 'Now.' }); var expected = 'Now.'; var actual = input.toHTML(); countdown.resetFormat(); same(actual, expected, 'Now.'); }); test('Empty label override', function() { var input = countdown(0, 0, countdown.ALL); var expected = 'Now!'; var actual = input.toHTML(null, 'Now!'); same(actual, expected, ''); }); test('Default empty label override, overridden', function() { var input = countdown(0, 0, countdown.ALL); countdown.setFormat({ empty: 'Now.' }); var expected = 'Right now!'; var actual = input.toHTML(null, 'Right now!'); countdown.resetFormat(); same(actual, expected, ''); }); test('Zero', function() { var input = countdown(0, 0, countdown.ALL); var expected = ''; var actual = input.toHTML(); same(actual, expected, ''); }); test('Empty label and tag override', function() { var input = countdown(0, 0, countdown.ALL); var expected = 'Now!'; var actual = input.toHTML('em', 'Now!'); same(actual, expected, ''); }); test('1 ms', function() { var start = new Date(0); var end = new Date(1); // account for local timezone start.setMinutes(start.getMinutes() + start.getTimezoneOffset()); end.setMinutes(end.getMinutes() + end.getTimezoneOffset()); var input = countdown(start, end, countdown.ALL); var expected = '1 millisecond'; var actual = input.toHTML(); same(actual, expected, ''); }); test('2 days, reversed', function() { var start = new Date(2 * 24 * 60 * 60 * 1000); var end = new Date(0); // account for local timezone start.setMinutes(start.getMinutes() + start.getTimezoneOffset()); end.setMinutes(end.getMinutes() + end.getTimezoneOffset()); var input = countdown(start, end, countdown.ALL); var expected = '2 days'; var actual = input.toHTML(); same(actual, expected, ''); }); test('8 days', function() { var start = new Date(0); var end = new Date(8 * 24 * 60 * 60 * 1000); // account for local timezone start.setMinutes(start.getMinutes() + start.getTimezoneOffset()); end.setMinutes(end.getMinutes() + end.getTimezoneOffset()); var input = countdown(start, end, countdown.ALL); var expected = '1 week and 1 day'; var actual = input.toHTML(); same(actual, expected, ''); }); test('70 days', function() { var start = new Date(0); var end = new Date(70 * 24 * 60 * 60 * 1000); // account for local timezone start.setMinutes(start.getMinutes() + start.getTimezoneOffset()); end.setMinutes(end.getMinutes() + end.getTimezoneOffset()); var input = countdown(start, end, countdown.ALL); var expected = '2 months, 1 week and 4 days'; var actual = input.toHTML(); same(actual, expected, ''); }); test('366 days, non-leap year', function() { var start = new Date(0); var end = new Date(366 * 24 * 60 * 60 * 1000); // account for local timezone start.setMinutes(start.getMinutes() + start.getTimezoneOffset()); end.setMinutes(end.getMinutes() + end.getTimezoneOffset()); var input = countdown(start, end, countdown.ALL); var expected = '1 year and 1 day'; var actual = input.toHTML(); same(actual, expected, ''); }); test('366 days, leap year', function() { var start = new Date(2000, 0, 1); var end = new Date(start.getTime() + 366 * 24 * 60 * 60 * 1000); // account for local timezone start.setMinutes(start.getMinutes() + start.getTimezoneOffset()); end.setMinutes(end.getMinutes() + end.getTimezoneOffset()); var input = countdown(start, end, countdown.ALL); var expected = '1 year'; var actual = input.toHTML(); same(actual, expected, ''); }); test('One of each', function() { var start = new Date(0); var end = new Date( (11 * 100) * (365.25 * 24 * 60 * 60 * 1000) + // millennium, century, week, day (365 * 24 * 60 * 60 * 1000) + // year (31 * 24 * 60 * 60 * 1000) + // month (60 * 60 * 1000) + // hour (60 * 1000) + // min 1000 + // sec 1); // ms // account for local timezone start.setMinutes(start.getMinutes() + start.getTimezoneOffset()); end.setMinutes(end.getMinutes() + end.getTimezoneOffset()); var input = countdown(start, end, countdown.ALL); var expected = '1 millennium, ' + '1 century, ' + '1 year, ' + '1 month, ' + '1 week, ' + '1 day, ' + '1 hour, ' + '1 minute, ' + '1 second and ' + '1 millisecond'; var actual = input.toHTML('em'); same(actual, expected, ''); }); test('Singular overrides', function() { var start = new Date(0); var end = new Date( (11 * 100) * (365.25 * 24 * 60 * 60 * 1000) + // millennium, century, week, day (365 * 24 * 60 * 60 * 1000) + // year (31 * 24 * 60 * 60 * 1000) + // month (60 * 60 * 1000) + // hour (60 * 1000) + // min 1000 + // sec 1); // ms // account for local timezone start.setMinutes(start.getMinutes() + start.getTimezoneOffset()); end.setMinutes(end.getMinutes() + end.getTimezoneOffset()); var input = countdown(start, end, countdown.ALL); countdown.setFormat({ singular: ' a| b| c| d| e| f| g| h| i| j| k' }); var expected = '1 k, ' + '1 j, ' + '1 h, ' + '1 g, ' + '1 f, ' + '1 e, ' + '1 d, ' + '1 c, ' + '1 b and ' + '1 a'; var actual = input.toHTML('em'); countdown.resetFormat(); same(actual, expected, ''); expected = '1 millennium, ' + '1 century, ' + '1 year, ' + '1 month, ' + '1 week, ' + '1 day, ' + '1 hour, ' + '1 minute, ' + '1 second and ' + '1 millisecond'; actual = input.toHTML('em'); same(actual, expected, ''); }); test('Plural overrides', function() { var start = new Date(0); var end = new Date( (22 * 100) * (365.25 * 24 * 60 * 60 * 1000) + // millennium, century, week, day (2 * 365 * 24 * 60 * 60 * 1000) + // year (31 + 29) * (24 * 60 * 60 * 1000) + // month (2 * 60 * 60 * 1000) + // hour (2 * 60 * 1000) + // min (2 * 1000) + // sec 2); // ms // account for local timezone start.setMinutes(start.getMinutes() + start.getTimezoneOffset()); end.setMinutes(end.getMinutes() + end.getTimezoneOffset()); var input = countdown(start, end, countdown.ALL); countdown.setFormat({ plural: ' A| B| C| D| E| F| G| H| I| J| K', last: ' & ', delim: ' & ' }); var expected = '2 K & ' + '2 J & ' + '2 H & ' + '2 G & ' + '2 F & ' + '2 E & ' + '2 D & ' + '2 C & ' + '2 B & ' + '2 A'; var actual = input.toHTML('em'); countdown.resetFormat(); same(actual, expected, ''); expected = '2 millennia, ' + '2 centuries, ' + '2 years, ' + '2 months, ' + '2 weeks, ' + '2 days, ' + '2 hours, ' + '2 minutes, ' + '2 seconds and ' + '2 milliseconds'; actual = input.toHTML('em'); same(actual, expected, ''); }); test('Partial singular overrides', function() { var start = new Date(0); var end = new Date( (11 * 100) * (365.25 * 24 * 60 * 60 * 1000) + // millennium, century, week, day (365 * 24 * 60 * 60 * 1000) + // year (31 * 24 * 60 * 60 * 1000) + // month (60 * 60 * 1000) + // hour (60 * 1000) + // min 1000 + // sec 1); // ms // account for local timezone start.setMinutes(start.getMinutes() + start.getTimezoneOffset()); end.setMinutes(end.getMinutes() + end.getTimezoneOffset()); var input = countdown(start, end, countdown.ALL); countdown.setFormat({ singular: ' a|| c|| e|| g|| i|| k', plural: '| B|| D|| F|| H|| J|', last: ' + finally ', delim: ' + ' }); var expected = '1 k + ' + '1 century + ' + '1 year + ' + '1 g + ' + '1 week + ' + '1 e + ' + '1 hour + ' + '1 c + ' + '1 second + finally ' + '1 a'; var actual = input.toHTML('em'); countdown.resetFormat(); same(actual, expected, ''); expected = '1 millennium, ' + '1 century, ' + '1 year, ' + '1 month, ' + '1 week, ' + '1 day, ' + '1 hour, ' + '1 minute, ' + '1 second and ' + '1 millisecond'; actual = input.toHTML('em'); same(actual, expected, ''); }); test('Partial plural overrides', function() { var start = new Date(0); var end = new Date( (22 * 100) * (365.25 * 24 * 60 * 60 * 1000) + // millennium, century, week, day (2 * 365 * 24 * 60 * 60 * 1000) + // year (31 + 29) * (24 * 60 * 60 * 1000) + // month (2 * 60 * 60 * 1000) + // hour (2 * 60 * 1000) + // min (2 * 1000) + // sec 2); // ms // account for local timezone start.setMinutes(start.getMinutes() + start.getTimezoneOffset()); end.setMinutes(end.getMinutes() + end.getTimezoneOffset()); var input = countdown(start, end, countdown.ALL); countdown.setFormat({ singular: ' a|| c|| e|| g|| i|| k', plural: '| B|| D|| F|| H|| J|', last: ', ' }); var expected = '2 millennia, ' + '2 J, ' + '2 H, ' + '2 months, ' + '2 F, ' + '2 days, ' + '2 D, ' + '2 minutes, ' + '2 B, ' + '2 milliseconds'; var actual = input.toHTML('em'); countdown.resetFormat(); same(actual, expected, ''); expected = '2 millennia, ' + '2 centuries, ' + '2 years, ' + '2 months, ' + '2 weeks, ' + '2 days, ' + '2 hours, ' + '2 minutes, ' + '2 seconds and ' + '2 milliseconds'; actual = input.toHTML('em'); same(actual, expected, ''); }); test('Custom number formatter', function() { var sillyFormatter = function(num) { num = ''+num; var str = ''; for (var i=0, len=num.length; iseven centuries, ' + 'two years, ' + 'six months, ' + 'one week, ' + 'four hours, ' + 'threeone minutes, ' + 'fivefive seconds and ' + 'ninezerozero milliseconds'; var actual = input.toHTML('em'); countdown.resetFormat(); same(actual, expected, ''); expected = '8 millennia, ' + '7 centuries, ' + '2 years, ' + '6 months, ' + '1 week, ' + '4 hours, ' + '31 minutes, ' + '55 seconds and ' + '900 milliseconds'; actual = input.toHTML('em'); same(actual, expected, ''); }); test('Custom unit formatter', function() { var ONE = ' миллисекунда| секунда| минута| час| день| неделя| месяц| год| декада| столетие| тысячелетие'.split('|'); var TWO = ' миллисекунды| секунды| минуты| часа| дня| недели| месяца| года| декады| столетия| тысячелетия'.split('|'); var MANY = ' миллисекунд| секунд| минут| часов| дней| недель| месяцев| лет| декад| столетий| тысячелетий'.split('|'); var russianUnits = function(value, unit) { if (Math.floor((value % 100) / 10) !== 1) { var mod = value % 10; if (mod === 1) { // singular or plurals ending in 1 except 11 return value+ONE[unit]; } if (mod >= 2 && mod <= 4) { // plurals ending in 2-4 except 12-14 return value+TWO[unit]; } } // general plurals return value+MANY[unit]; }; var start = new Date(0); var end = new Date( (87 * 100) * (365.25 * 24 * 60 * 60 * 1000) + // millennia, centuries, weeks, days (2 * 365 * 24 * 60 * 60 * 1000) + // years (4 * 31 * 24 * 60 * 60 * 1000) + // months (4 * 60 * 60 * 1000) + // hours (13 * 60 * 1000) + // mins (55 * 1000) + // secs 900); // ms // account for local timezone start.setMinutes(start.getMinutes() + start.getTimezoneOffset()); end.setMinutes(end.getMinutes() + end.getTimezoneOffset()); var input = countdown(start, end, countdown.ALL); countdown.setFormat({ last: ' и ', delim: ', ', formatter: russianUnits }); var expected = '8 тысячелетий, ' + '7 столетий, ' + '2 года, ' + '6 месяцев, ' + '1 неделя, ' + '4 часа, ' + '13 минут, ' + '55 секунд и ' + '900 миллисекунд'; var actual = input.toHTML('em'); countdown.resetFormat(); same(actual, expected, ''); expected = '8 millennia, ' + '7 centuries, ' + '2 years, ' + '6 months, ' + '1 week, ' + '4 hours, ' + '13 minutes, ' + '55 seconds and ' + '900 milliseconds'; actual = input.toHTML('em'); same(actual, expected, ''); }); }catch(ex){alert(ex);} ================================================ FILE: test/lint.js ================================================ load("lib/jslint/jslint.js"); var src = readFile("countdown.js"); JSLINT(src, { browser: true, undef: true, eqeqeq: true, regexp: true, newcap: true, maxerr: 100 }); var ok = { "Use '===' to compare with 'null'.": true, "Use '!==' to compare with 'null'.": true }; var e = JSLINT.errors, found = 0, w; for (var i = 0; i < e.length; i++) { w = e[i]; if (!ok[ w.reason ]) { found++; print("\n" + w.evidence + "\n"); print(" Problem at line " + w.line + " character " + w.character + ": " + w.reason); } } if (found > 0) { print("\n" + found + " Error(s) found."); } else { print("JSLint check passed."); } ================================================ FILE: test/styles.css ================================================ article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;}audio,canvas,video{display:inline-block;*display:inline;*zoom:1;}audio:not([controls]){display:none;}[hidden]{display:none;}html{font-size:100%;overflow-y:scroll;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}body{margin:0;}body,button,input,select,textarea{font-family:sans-serif;}a{color:#00e;}a:visited{color:#551a8b;}a:focus{outline:thin dotted;}a:hover,a:active{outline:0;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:bold;}blockquote{margin:1em 40px;}dfn{font-style:italic;}mark{background:#ff0;color:#000;}pre,code,kbd,samp{font-family:monospace,serif;_font-family:Consolas,'courier new',monospace;font-size:1em;}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word;}q{quotes:none;}q:before,q:after{content:'';content:none;}small{font-size:75%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-0.5em;}sub{bottom:-0.25em;}ul,ol{margin:1em 0;padding:0 0 0 40px;}dd{margin:0 0 0 40px;}nav ul,nav ol{list-style:none;list-style-image:none;}img{border:0;-ms-interpolation-mode:bicubic;}svg:not(:root){overflow:hidden;}figure{margin:0;}form{margin:0;}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em;}legend{border:0;*margin-left:-7px;}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;}button,input{line-height:normal;*overflow:visible;}table button,table input{*overflow:auto;}button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}textarea{overflow:auto;vertical-align:top;}table{border-collapse:collapse;border-spacing:0;} @font-face { font-family: 'LeagueGothicRegular'; src: url('fonts/League_Gothic-webfont.eot'); /* IE9 Compat Modes */ src: url('fonts/League_Gothic-webfont.eot?iefix') format('eot'), /* IE6-IE8 */ url('fonts/League_Gothic-webfont.woff') format('woff'), /* Modern Browsers */ url('fonts/League_Gothic-webfont.ttf') format('truetype'), /* Safari, Android, iOS */ url('fonts/League_Gothic-webfont.svg#svgFontName') format('svg'); /* Legacy iOS */ font-weight: normal; font-style: normal; } body, input { font-size: 100%; } body { background-color: #F8F8F8; font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; margin: auto; width: 80%; min-width: 600px; } h1, h1 a:link, h1 a:hover, h1 a:active, h1 a:visited { color: #999; font-family: LeagueGothicRegular, 'League Gothic', "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Tahoma, Arial, sans-serif; text-decoration: none; } h1 { font-size: 1000%; margin: 0 auto 0.25em; padding: 0; text-align: center; text-shadow: 0px -1px 1px silver; white-space: nowrap; } h2 { font-size: 150%; } h3 { font-size: 125%; } a:link, a:hover, a:active, a:visited { color: #006699; } dl { margin: 0; padding: 0.75em 0.25em; } dl dt { font-weight: bold; margin: 0 0 0 0.5em; padding: 0.25em 0 0.25em 0.5em; } dl dd { list-style-type: none; margin: 0 0 0 1.5em; padding: 0.25em 0 0.25em 1.5em; } pre { background: #F7F7F7; border: 1px solid #DDD; border-radius: 3px; -mox-border-radius: 3px; -webkit-border-radius: 3px; padding: 9px 12px; } footer { border-top: 1px solid #F1F1F1; clear: both; margin: 4em auto; padding-top: 1em; text-align: right; } .breaking-change { border: 1px solid #990000; border-radius: 3px; -mox-border-radius: 3px; -webkit-border-radius: 3px; font-style: italic; padding: 0 1em; margin: 1em 0; } .breaking-change:target { -webkit-animation: target-fade 5s 1; -moz-animation: target-fade 5s 1; -o-animation: target-fade 5s 1; animation: target-fade 5s 1; } @-webkit-keyframes target-fade { 0% { background-color: #FFFF66; } 100% { background-color: rgba(0,0,0,0); } } @-moz-keyframes target-fade { 0% { background-color: #FFFF66; } 100% { background-color: rgba(0,0,0,0); } } @-o-keyframes target-fade { 0% { background-color: #FFFF66; } 100% { background-color: rgba(0,0,0,0); } } @keyframes target-fade { 0% { background-color: #FFFF66; } 100% { background-color: rgba(0,0,0,0); } } #counter { border: 0; font-size: 250%; font-weight: normal; min-height: 3em; text-align: center; } #counter strong { white-space: nowrap; } #countdown-start, #countdown-units { background-color: #EEE; border-radius: 4px; -moz-border-radius: 4px; -webkit-border-radius: 4px; font-size: 150%; padding: 0.25em 0.5em; /* width: 100%;*/ } #countdown-start span, #countdown-units span { line-height: 1.5em; white-space: nowrap; } #countdown-start, #countdown-units { text-align: center; } #countdown-start label { font-weight: bold; padding: 0 0.25em; } #countdown-start input { border: 1px solid silver; border-radius: 4px; -moz-border-radius: 4px; -webkit-border-radius: 4px; text-align: center; } #countdown-units span { display: block; } #countdown-units label { font-weight: bold; padding: 0.25em; } #countdown-units input { border: 1px solid silver; border-radius: 4px; -moz-border-radius: 4px; -webkit-border-radius: 4px; margin-right: 0.5em; text-align: center; } /* http://ubuwaits.github.com/css3-buttons/ */ a.minimal:link, a.minimal:visited { background: #e3e3e3; border: 1px solid #ccc; border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; box-shadow: inset 0px 0px 1px 1px #f6f6f6; -moz-box-shadow: inset 0px 0px 1px 1px #f6f6f6; -webkit-box-shadow: inset 0px 0px 1px 1px #f6f6f6; color: #333; display: inline-block; font-family: "helvetica neue", helvetica, arial, sans-serif; font-size: 12px; font-weight: bold; line-height: 1; padding: 8px 0 9px; text-align: center; text-decoration: none; text-shadow: 0 1px 0px #fff; width: 150px; } a.minimal:hover { background: #d9d9d9; box-shadow: inset 0px 0px 1px 1px #eaeaea; -moz-box-shadow: inset 0px 0px 1px 1px #eaeaea; -webkit-box-shadow: inset 0px 0px 1px 1px #eaeaea; color: #222; } a.minimal:active { background: #d0d0d0; box-shadow: inset 0px 0px 1px 1px #e3e3e3; -moz-box-shadow: inset 0px 0px 1px 1px #e3e3e3; -webkit-box-shadow: inset 0px 0px 1px 1px #e3e3e3; color: #000; } .summary { font-size: 12pt; text-align: center; } .box { background-color: #FFF; border: 1px solid silver; border-radius: 4px; -moz-border-radius: 4px; -webkit-border-radius: 4px; box-shadow: 0px 0px 4px #DDDDDD; -moz-box-shadow: 0px 0px 4px #DDDDDD; -webkit-box-shadow: 0px 0px 4px #DDDDDD; float: left; height: 200px; line-height: 1.75em; margin: 0.5em 0 0.5em 0.5em; padding: 0.125em 0.5em; width: 45%; } .box h2 { border-bottom: none; color: #666; font-size: 30px; font-weight: bold; text-align: center; text-shadow: 0 -1px 1px silver; text-transform: lowercase; } .box ul, .box li { list-style-type: none; margin: 0; padding: 0; white-space: nowrap; } .box a.minimal { margin: 0.25em; text-transform: lowercase; } .box a.glow { box-shadow: 0px 0px 4px #FFCC00; -moz-box-shadow: 0px 0px 4px #FFCC00; -webkit-box-shadow: 0px 0px 4px #FFCC00; } .action-info { font-style: italic; white-space: nowrap; } ================================================ FILE: test/timespanTests.js ================================================ try{ /** * Mocks up a Timespan object for unit tests * * @private * @param {Timespan|Object} map properties to convert to a Timespan * @return {Timespan} */ countdown.mock = function(map) { var ts = countdown(); for (var key in map) { if (map.hasOwnProperty(key)) { ts[key] = map[key]; } } return ts; }; var formatTZ = function(date) { var tz = -(date instanceof Date ? date : new Date()).getTimezoneOffset(); if (!tz) { return 'UTC'; } var tzStr = ''+Math.abs(tz % 60); if (tzStr.length < 2) { tzStr = '0'+tzStr; } tzStr = Math.abs(tz / 60) + tzStr; if (tzStr.length < 4) { tzStr = '0'+tzStr; } return 'UTC'+((tz < 0) ? '-' : '+') +tzStr; }; module('countdown(...)'); test('Zero', function() { var start = 0; var end = 0; var expected = countdown.mock({ start: new Date(0), end: new Date(0), units: countdown.ALL, value: 0, millennia: 0, centuries: 0, decades: 0, years: 0, months: 0, weeks: 0, days: 0, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }); var actual = countdown(start, end, countdown.ALL); same(actual, expected, ''); }); test('1 ms', function() { var start = 0; var end = 1; var expected = countdown.mock({ start: new Date(0), end: new Date(1), units: countdown.ALL, value: 1, millennia: 0, centuries: 0, decades: 0, years: 0, months: 0, weeks: 0, days: 0, hours: 0, minutes: 0, seconds: 0, milliseconds: 1 }); var actual = countdown(start, end, countdown.ALL); same(actual, expected, ''); }); test('1 sec', function() { var start = 10000; var end = 11000; var expected = countdown.mock({ start: new Date(10000), end: new Date(11000), units: countdown.ALL, value: 1000, millennia: 0, centuries: 0, decades: 0, years: 0, months: 0, weeks: 0, days: 0, hours: 0, minutes: 0, seconds: 1, milliseconds: 0 }); var actual = countdown(start, end, countdown.ALL); same(actual, expected, ''); }); test('5 min, reversed', function() { var start = (5 * 60 * 1000); var end = 0; var expected = countdown.mock({ start: new Date(5 * 60 * 1000), end: new Date(0), units: countdown.ALL, value: -(5 * 60 * 1000), millennia: 0, centuries: 0, decades: 0, years: 0, months: 0, weeks: 0, days: 0, hours: 0, minutes: 5, seconds: 0, milliseconds: 0 }); var actual = countdown(start, end, countdown.ALL); same(actual, expected, ''); }); test('Constant 1 month span, daily over 5 years', function() { var start = new Date(1999, 10, 1, 12, 0, 0); var expected = countdown.mock({ start: start, end: start, value: 0, units: countdown.ALL, millennia: 0, centuries: 0, decades: 0, years: 0, months: 1, weeks: 0, days: 0, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }); for (var t=0, range=5*365.2425; t '+end); }); test('Extra day bug ('+formatTZ(2014, 6, 3)+')', function() { var start = new Date(2014, 6, 1, 0, 0, 0, 0); var end = new Date(2014, 6, 3, 17, 52, 49, 209); var expected = countdown.mock({ start: new Date(start.getTime()), end: new Date(end.getTime()), units: countdown.ALL, value: end.getTime() - start.getTime(), millennia: 0, centuries: 0, decades: 0, years: 0, months: 0, weeks: 0, days: 2, hours: 17, minutes: 52, seconds: 49, milliseconds: 209 }); var actual = countdown(start, end, countdown.ALL); same(actual, expected, ''+start+' => '+end); }); test('Daylight Savings Time ('+formatTZ(1318451880000)+')', function() { var start = new Date(2011, 9, 12, 13, 38, 0); var end = new Date(2013, 11, 2, 14, 0, 0); var expected = countdown.mock({ start: new Date(start.getTime()), end: new Date(end.getTime()), units: countdown.ALL, value: end.getTime() - start.getTime(), millennia: 0, centuries: 0, decades: 0, years: 2, months: 1, weeks: 3, days: 0, hours: 0, minutes: 22, seconds: 0, milliseconds: 0 }); var actual = countdown(start, end, countdown.ALL); same(actual, expected, ''+start+' => '+end); }); test('Reference month ordering', function() { var start = new Date(2015, 1, 1, 0, 0, 0); var end = new Date(2014, 9, 27, 12, 00, 0); var expected = countdown.mock({ start: new Date(start.getTime()), end: new Date(end.getTime()), units: countdown.ALL, value: end.getTime() - start.getTime(), millennia: 0, centuries: 0, decades: 0, years: 0, months: 3, weeks: 0, days: 4, hours: 12, minutes: 0, seconds: 0, milliseconds: 0 }); var actual = countdown(start, end, countdown.ALL); same(actual, expected, ''+start+' => '+end); }); test('Before leap day', function() { var start = new Date(2012, 1, 28, 13, 14, 30, 109); var end = new Date(2012, 1, 29, 17, 46, 22, 111); // Leap day 2012 var expected = countdown.mock({ start: new Date(2012, 1, 28, 13, 14, 30, 109), end: new Date(2012, 1, 29, 17, 46, 22, 111), units: countdown.ALL, value: end.getTime() - start.getTime(), millennia: 0, centuries: 0, decades: 0, years: 0, months: 0, weeks: 0, days: 1, hours: 4, minutes: 31, seconds: 52, milliseconds: 2 }); var actual = countdown(start, end, countdown.ALL); same(actual, expected, ''+start+' => '+end); }); test('After leap day (local)', function() { var start = new Date(2012, 1, 29, 17, 46, 22, 111); // Leap day 2012 var end = new Date(2012, 2, 1, 13, 14, 30, 109); var expected = countdown.mock({ start: new Date(2012, 1, 29, 17, 46, 22, 111), end: new Date(2012, 2, 1, 13, 14, 30, 109), units: countdown.ALL, value: end.getTime() - start.getTime(), millennia: 0, centuries: 0, decades: 0, years: 0, months: 0, weeks: 0, days: 0, hours: 19, minutes: 28, seconds: 7, milliseconds: 998 }); var actual = countdown(start, end, countdown.ALL); same(actual, expected, ''+start+' => '+end); }); test('After leap day (UTC)', function() { var start = new Date(1330537582111); // 2012-02-29T17:46:22.111Z, Leap day 2012 var end = new Date(1330607670109); // 2012-03-01T13:14:30.109Z var expected = countdown.mock({ start: new Date(1330537582111), end: new Date(1330607670109), units: countdown.ALL, value: end.getTime() - start.getTime(), millennia: 0, centuries: 0, decades: 0, years: 0, months: 0, weeks: 0, days: 0, hours: 19, minutes: 28, seconds: 7, milliseconds: 998 }); var actual = countdown(start, end, countdown.ALL); same(actual, expected, ''+start+' => '+end); }); test('Almost 2 minutes, rounded', function() { var start = new Date(915220800000); // 1999-01-01T20:00:00.000Z var end = new Date(915220919999); // 1999-01-01T20:01:59.999Z var expected = countdown.mock({ start: new Date(915220800000), end: new Date(915220919999), units: countdown.DEFAULTS, value: 119999, years: 0, months: 0, days: 0, hours: 0, minutes: 2, seconds: 0 }); var actual = countdown(start, end, countdown.DEFAULTS); same(actual, expected, ''+start+' => '+end); }); test('Almost 2 minutes, rounded 2 digits', function() { var start = new Date(915220800000); // 1999-01-01T20:00:00.000Z var end = new Date(915220919999); // 1999-01-01T20:01:59.999Z var expected = countdown.mock({ start: new Date(915220800000), end: new Date(915220919999), units: countdown.DEFAULTS, value: 119999, years: 0, months: 0, days: 0, hours: 0, minutes: 2, seconds: 0 }); var actual = countdown(start, end, countdown.DEFAULTS, NaN, 2); same(actual, expected, ''+start+' => '+end); }); test('Almost 2 minutes, full 3 digits', function() { var start = new Date(915220800000); // 1999-01-01T20:00:00.000Z var end = new Date(915220919999); // 1999-01-01T20:01:59.999Z var expected = countdown.mock({ start: new Date(915220800000), end: new Date(915220919999), units: countdown.DEFAULTS, value: 119999, years: 0, months: 0, days: 0, hours: 0, minutes: 1, seconds: 59.999 }); var actual = countdown(start, end, countdown.DEFAULTS, 0, 3); same(actual, expected, ''+start+' => '+end); }); module('Timespan.addTo(date)'); test('Zero', function() { var timespan = countdown.mock({ millennia: 0, centuries: 0, decades: 0, years: 0, months: 0, weeks: 0, days: 0, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }); var expected = 0; var actual = timespan.addTo(0).getTime(); same(actual, expected, ''); }); test('1 ms', function() { var timespan = countdown.mock({ millennia: 0, centuries: 0, decades: 0, years: 0, months: 0, weeks: 0, days: 0, hours: 0, minutes: 0, seconds: 0, milliseconds: 1 }); var expected = 1; var actual = timespan.addTo(0).getTime(); same(actual, expected, ''); }); test('1 sec', function() { var timespan = countdown.mock({ millennia: 0, centuries: 0, decades: 0, years: 0, months: 0, weeks: 0, days: 0, hours: 0, minutes: 0, seconds: 1, milliseconds: 0 }); var expected = 11000; var actual = timespan.addTo(10000).getTime(); same(actual, expected, ''); }); test('1 sec, value', function() { var timespan = countdown.mock({ value: 1000 }); var expected = 11000; var actual = timespan.addTo(10000).getTime(); same(actual, expected, ''); }); test('5 min, reversed', function() { var timespan = countdown.mock({ millennia: 0, centuries: 0, decades: 0, years: 0, months: 0, weeks: 0, days: 0, hours: 0, minutes: -5, seconds: 0, milliseconds: 0 }); var expected = 0; var actual = timespan.addTo((5 * 60 * 1000)).getTime(); same(actual, expected, ''); }); test('5 min, reversed, value', function() { var timespan = countdown.mock({ value: -(5 * 60 * 1000) }); var expected = 0; var actual = timespan.addTo((5 * 60 * 1000)).getTime(); same(actual, expected, ''); }); test('Daylight Savings Time ('+formatTZ(1318451880000)+')', function() { var start = new Date(2011, 9, 12, 13, 38, 0); var end = new Date(2013, 11, 2, 14, 0, 0); var timespan = countdown.mock({ millennia: 0, centuries: 0, decades: 0, years: 2, months: 1, weeks: 3, days: 0, hours: 0, minutes: 22, seconds: 0, milliseconds: 0 }); var expected = end.getTime(); var actual = timespan.addTo(start).getTime(); same(actual, expected, ''+start+' => '+end); }); test('Daylight Savings Time ('+formatTZ(1318451880000)+'), value', function() { var start = new Date(2011, 9, 12, 13, 38, 0); var end = new Date(2013, 11, 2, 14, 0, 0); var timespan = countdown.mock({ value: end.getTime() - start.getTime() }); var expected = end.getTime(); var actual = timespan.addTo(start).getTime(); same(actual, expected, ''+start+' => '+end); }); }catch(ex){alert(ex);} ================================================ FILE: test/unit.html ================================================ Countdown.js Unit Tests

Countdown.js Unit Tests