diff --git a/build/lib/parse-js.js b/build/lib/parse-js.js index 7e4fd0e2..9f90dfee 100644 --- a/build/lib/parse-js.js +++ b/build/lib/parse-js.js @@ -182,8 +182,6 @@ var OPERATORS = array_to_hash([ ">>=", "<<=", ">>>=", - "~=", - "%=", "|=", "^=", "&=", @@ -191,7 +189,7 @@ var OPERATORS = array_to_hash([ "||" ]); -var WHITESPACE_CHARS = array_to_hash(characters(" \n\r\t")); +var WHITESPACE_CHARS = array_to_hash(characters(" \n\r\t\u200b")); var PUNC_BEFORE_EXPRESSION = array_to_hash(characters("[{}(,.;:")); @@ -201,20 +199,47 @@ var REGEXP_MODIFIERS = array_to_hash(characters("gmsiy")); /* -----[ Tokenizer ]----- */ -function is_alphanumeric_char(ch) { - ch = ch.charCodeAt(0); - return (ch >= 48 && ch <= 57) || - (ch >= 65 && ch <= 90) || - (ch >= 97 && ch <= 122); +// regexps adapted from http://xregexp.com/plugins/#unicode +var UNICODE = { + letter: new RegExp("[\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u0523\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0621-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971\\u0972\\u097B-\\u097F\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C3D\\u0C58\\u0C59\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D28\\u0D2A-\\u0D39\\u0D3D\\u0D60\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC\\u0EDD\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8B\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10D0-\\u10FA\\u10FC\\u1100-\\u1159\\u115F-\\u11A2\\u11A8-\\u11F9\\u1200-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u1676\\u1681-\\u169A\\u16A0-\\u16EA\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u1900-\\u191C\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19A9\\u19C1-\\u19C7\\u1A00-\\u1A16\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u2094\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183\\u2184\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2C6F\\u2C71-\\u2C7D\\u2C80-\\u2CE4\\u2D00-\\u2D25\\u2D30-\\u2D65\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005\\u3006\\u3031-\\u3035\\u303B\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31B7\\u31F0-\\u31FF\\u3400\\u4DB5\\u4E00\\u9FC3\\uA000-\\uA48C\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA65F\\uA662-\\uA66E\\uA67F-\\uA697\\uA717-\\uA71F\\uA722-\\uA788\\uA78B\\uA78C\\uA7FB-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA90A-\\uA925\\uA930-\\uA946\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAC00\\uD7A3\\uF900-\\uFA2D\\uFA30-\\uFA6A\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC]"), + non_spacing_mark: new RegExp("[\\u0300-\\u036F\\u0483-\\u0487\\u0591-\\u05BD\\u05BF\\u05C1\\u05C2\\u05C4\\u05C5\\u05C7\\u0610-\\u061A\\u064B-\\u065E\\u0670\\u06D6-\\u06DC\\u06DF-\\u06E4\\u06E7\\u06E8\\u06EA-\\u06ED\\u0711\\u0730-\\u074A\\u07A6-\\u07B0\\u07EB-\\u07F3\\u0816-\\u0819\\u081B-\\u0823\\u0825-\\u0827\\u0829-\\u082D\\u0900-\\u0902\\u093C\\u0941-\\u0948\\u094D\\u0951-\\u0955\\u0962\\u0963\\u0981\\u09BC\\u09C1-\\u09C4\\u09CD\\u09E2\\u09E3\\u0A01\\u0A02\\u0A3C\\u0A41\\u0A42\\u0A47\\u0A48\\u0A4B-\\u0A4D\\u0A51\\u0A70\\u0A71\\u0A75\\u0A81\\u0A82\\u0ABC\\u0AC1-\\u0AC5\\u0AC7\\u0AC8\\u0ACD\\u0AE2\\u0AE3\\u0B01\\u0B3C\\u0B3F\\u0B41-\\u0B44\\u0B4D\\u0B56\\u0B62\\u0B63\\u0B82\\u0BC0\\u0BCD\\u0C3E-\\u0C40\\u0C46-\\u0C48\\u0C4A-\\u0C4D\\u0C55\\u0C56\\u0C62\\u0C63\\u0CBC\\u0CBF\\u0CC6\\u0CCC\\u0CCD\\u0CE2\\u0CE3\\u0D41-\\u0D44\\u0D4D\\u0D62\\u0D63\\u0DCA\\u0DD2-\\u0DD4\\u0DD6\\u0E31\\u0E34-\\u0E3A\\u0E47-\\u0E4E\\u0EB1\\u0EB4-\\u0EB9\\u0EBB\\u0EBC\\u0EC8-\\u0ECD\\u0F18\\u0F19\\u0F35\\u0F37\\u0F39\\u0F71-\\u0F7E\\u0F80-\\u0F84\\u0F86\\u0F87\\u0F90-\\u0F97\\u0F99-\\u0FBC\\u0FC6\\u102D-\\u1030\\u1032-\\u1037\\u1039\\u103A\\u103D\\u103E\\u1058\\u1059\\u105E-\\u1060\\u1071-\\u1074\\u1082\\u1085\\u1086\\u108D\\u109D\\u135F\\u1712-\\u1714\\u1732-\\u1734\\u1752\\u1753\\u1772\\u1773\\u17B7-\\u17BD\\u17C6\\u17C9-\\u17D3\\u17DD\\u180B-\\u180D\\u18A9\\u1920-\\u1922\\u1927\\u1928\\u1932\\u1939-\\u193B\\u1A17\\u1A18\\u1A56\\u1A58-\\u1A5E\\u1A60\\u1A62\\u1A65-\\u1A6C\\u1A73-\\u1A7C\\u1A7F\\u1B00-\\u1B03\\u1B34\\u1B36-\\u1B3A\\u1B3C\\u1B42\\u1B6B-\\u1B73\\u1B80\\u1B81\\u1BA2-\\u1BA5\\u1BA8\\u1BA9\\u1C2C-\\u1C33\\u1C36\\u1C37\\u1CD0-\\u1CD2\\u1CD4-\\u1CE0\\u1CE2-\\u1CE8\\u1CED\\u1DC0-\\u1DE6\\u1DFD-\\u1DFF\\u20D0-\\u20DC\\u20E1\\u20E5-\\u20F0\\u2CEF-\\u2CF1\\u2DE0-\\u2DFF\\u302A-\\u302F\\u3099\\u309A\\uA66F\\uA67C\\uA67D\\uA6F0\\uA6F1\\uA802\\uA806\\uA80B\\uA825\\uA826\\uA8C4\\uA8E0-\\uA8F1\\uA926-\\uA92D\\uA947-\\uA951\\uA980-\\uA982\\uA9B3\\uA9B6-\\uA9B9\\uA9BC\\uAA29-\\uAA2E\\uAA31\\uAA32\\uAA35\\uAA36\\uAA43\\uAA4C\\uAAB0\\uAAB2-\\uAAB4\\uAAB7\\uAAB8\\uAABE\\uAABF\\uAAC1\\uABE5\\uABE8\\uABED\\uFB1E\\uFE00-\\uFE0F\\uFE20-\\uFE26]"), + space_combining_mark: new RegExp("[\\u0903\\u093E-\\u0940\\u0949-\\u094C\\u094E\\u0982\\u0983\\u09BE-\\u09C0\\u09C7\\u09C8\\u09CB\\u09CC\\u09D7\\u0A03\\u0A3E-\\u0A40\\u0A83\\u0ABE-\\u0AC0\\u0AC9\\u0ACB\\u0ACC\\u0B02\\u0B03\\u0B3E\\u0B40\\u0B47\\u0B48\\u0B4B\\u0B4C\\u0B57\\u0BBE\\u0BBF\\u0BC1\\u0BC2\\u0BC6-\\u0BC8\\u0BCA-\\u0BCC\\u0BD7\\u0C01-\\u0C03\\u0C41-\\u0C44\\u0C82\\u0C83\\u0CBE\\u0CC0-\\u0CC4\\u0CC7\\u0CC8\\u0CCA\\u0CCB\\u0CD5\\u0CD6\\u0D02\\u0D03\\u0D3E-\\u0D40\\u0D46-\\u0D48\\u0D4A-\\u0D4C\\u0D57\\u0D82\\u0D83\\u0DCF-\\u0DD1\\u0DD8-\\u0DDF\\u0DF2\\u0DF3\\u0F3E\\u0F3F\\u0F7F\\u102B\\u102C\\u1031\\u1038\\u103B\\u103C\\u1056\\u1057\\u1062-\\u1064\\u1067-\\u106D\\u1083\\u1084\\u1087-\\u108C\\u108F\\u109A-\\u109C\\u17B6\\u17BE-\\u17C5\\u17C7\\u17C8\\u1923-\\u1926\\u1929-\\u192B\\u1930\\u1931\\u1933-\\u1938\\u19B0-\\u19C0\\u19C8\\u19C9\\u1A19-\\u1A1B\\u1A55\\u1A57\\u1A61\\u1A63\\u1A64\\u1A6D-\\u1A72\\u1B04\\u1B35\\u1B3B\\u1B3D-\\u1B41\\u1B43\\u1B44\\u1B82\\u1BA1\\u1BA6\\u1BA7\\u1BAA\\u1C24-\\u1C2B\\u1C34\\u1C35\\u1CE1\\u1CF2\\uA823\\uA824\\uA827\\uA880\\uA881\\uA8B4-\\uA8C3\\uA952\\uA953\\uA983\\uA9B4\\uA9B5\\uA9BA\\uA9BB\\uA9BD-\\uA9C0\\uAA2F\\uAA30\\uAA33\\uAA34\\uAA4D\\uAA7B\\uABE3\\uABE4\\uABE6\\uABE7\\uABE9\\uABEA\\uABEC]"), + connector_punctuation: new RegExp("[\\u005F\\u203F\\u2040\\u2054\\uFE33\\uFE34\\uFE4D-\\uFE4F\\uFF3F]") }; -function is_identifier_char(ch) { - return is_alphanumeric_char(ch) || ch == "$" || ch == "_"; +function is_letter(ch) { + return UNICODE.letter.test(ch); }; function is_digit(ch) { ch = ch.charCodeAt(0); - return ch >= 48 && ch <= 57; + return ch >= 48 && ch <= 57; //XXX: find out if "UnicodeDigit" means something else than 0..9 +}; + +function is_alphanumeric_char(ch) { + return is_digit(ch) || is_letter(ch); +}; + +function is_unicode_combining_mark(ch) { + return UNICODE.non_spacing_mark.test(ch) || UNICODE.space_combining_mark.test(ch); +}; + +function is_unicode_connector_punctuation(ch) { + return UNICODE.connector_punctuation.test(ch); +}; + +function is_identifier_start(ch) { + return ch == "$" || ch == "_" || is_letter(ch); +}; + +function is_identifier_char(ch) { + return is_identifier_start(ch) + || is_unicode_combining_mark(ch) + || is_digit(ch) + || is_unicode_connector_punctuation(ch) + || ch == "\u200c" // zero-width non-joiner + || ch == "\u200d" // zero-width joiner (in my ECMA-262 PDF, this is also 200c) + ; }; function parse_js_number(num) { @@ -253,18 +278,19 @@ function is_token(token, type, val) { var EX_EOF = {}; -function tokenizer($TEXT, skip_comments) { +function tokenizer($TEXT) { var S = { - text : $TEXT.replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, ''), - pos : 0, - tokpos : 0, - line : 0, - tokline : 0, - col : 0, - tokcol : 0, - newline_before : false, - regex_allowed : false + text : $TEXT.replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, ''), + pos : 0, + tokpos : 0, + line : 0, + tokline : 0, + col : 0, + tokcol : 0, + newline_before : false, + regex_allowed : false, + comments_before : [] }; function peek() { return S.text.charAt(S.pos); }; @@ -299,7 +325,7 @@ function tokenizer($TEXT, skip_comments) { S.tokpos = S.pos; }; - function token(type, value) { + function token(type, value, is_comment) { S.regex_allowed = ((type == "operator" && !HOP(UNARY_POSTFIX, value)) || (type == "keyword" && HOP(KEYWORDS_BEFORE_EXPRESSION, value)) || (type == "punc" && HOP(PUNC_BEFORE_EXPRESSION, value))); @@ -311,6 +337,10 @@ function tokenizer($TEXT, skip_comments) { pos : S.tokpos, nlb : S.newline_before }; + if (!is_comment) { + ret.comments_before = S.comments_before; + S.comments_before = []; + } S.newline_before = false; return ret; }; @@ -351,7 +381,7 @@ function tokenizer($TEXT, skip_comments) { if (ch == "+") return after_e; after_e = false; if (ch == ".") { - if (!has_dot) + if (!has_dot && !has_x) return has_dot = true; return false; } @@ -417,7 +447,7 @@ function tokenizer($TEXT, skip_comments) { ret = S.text.substring(S.pos, i); S.pos = i; } - return token("comment1", ret); + return token("comment1", ret, true); }; function read_multiline_comment() { @@ -425,14 +455,41 @@ function tokenizer($TEXT, skip_comments) { return with_eof_error("Unterminated multiline comment", function(){ var i = find("*/", true), text = S.text.substring(S.pos, i), - tok = token("comment2", text); + tok = token("comment2", text, true); S.pos = i + 2; S.line += text.split("\n").length - 1; S.newline_before = text.indexOf("\n") >= 0; + + // https://github.com/mishoo/UglifyJS/issues/#issue/100 + if (/^@cc_on/i.test(text)) { + warn("WARNING: at line " + S.line); + warn("*** Found \"conditional comment\": " + text); + warn("*** UglifyJS DISCARDS ALL COMMENTS. This means your code might no longer work properly in Internet Explorer."); + } + return tok; }); }; + function read_name() { + var backslash = false, name = "", ch; + while ((ch = peek()) != null) { + if (!backslash) { + if (ch == "\\") backslash = true, next(); + else if (is_identifier_char(ch)) name += next(); + else break; + } + else { + if (ch != "u") parse_error("Expecting UnicodeEscapeSequence -- uXXXX"); + ch = read_escaped_char(); + if (!is_identifier_char(ch)) parse_error("Unicode char: " + ch.charCodeAt(0) + " is not valid in identifier"); + name += ch; + backslash = false; + } + } + return name; + }; + function read_regexp() { return with_eof_error("Unterminated regular expression", function(){ var prev_backslash = false, regexp = "", ch, in_class = false; @@ -452,15 +509,14 @@ function tokenizer($TEXT, skip_comments) { } else { regexp += ch; } - var mods = read_while(function(ch){ - return HOP(REGEXP_MODIFIERS, ch); - }); + var mods = read_name(); return token("regexp", [ regexp, mods ]); }); }; function read_operator(prefix) { function grow(op) { + if (!peek()) return op; var bigger = op + peek(); if (HOP(OPERATORS, bigger)) { next(); @@ -472,19 +528,18 @@ function tokenizer($TEXT, skip_comments) { return token("operator", grow(prefix || next())); }; - var handle_slash = skip_comments ? function() { + function handle_slash() { next(); var regex_allowed = S.regex_allowed; switch (peek()) { - case "/": read_line_comment(); S.regex_allowed = regex_allowed; return next_token(); - case "*": read_multiline_comment(); S.regex_allowed = regex_allowed; return next_token(); - } - return S.regex_allowed ? read_regexp() : read_operator("/"); - } : function() { - next(); - switch (peek()) { - case "/": return read_line_comment(); - case "*": return read_multiline_comment(); + case "/": + S.comments_before.push(read_line_comment()); + S.regex_allowed = regex_allowed; + return next_token(); + case "*": + S.comments_before.push(read_multiline_comment()); + S.regex_allowed = regex_allowed; + return next_token(); } return S.regex_allowed ? read_regexp() : read_operator("/"); }; @@ -497,7 +552,7 @@ function tokenizer($TEXT, skip_comments) { }; function read_word() { - var word = read_while(is_identifier_char); + var word = read_name(); return !HOP(KEYWORDS, word) ? token("name", word) : HOP(OPERATORS, word) @@ -529,7 +584,7 @@ function tokenizer($TEXT, skip_comments) { if (ch == ".") return handle_dot(); if (ch == "/") return handle_slash(); if (HOP(OPERATOR_CHARS, ch)) return read_operator(); - if (is_identifier_char(ch)) return read_word(); + if (ch == "\\" || is_identifier_start(ch)) return read_word(); parse_error("Unexpected character '" + ch + "'"); }; @@ -565,7 +620,7 @@ var ASSIGNMENT = (function(a, ret, i){ } return ret; })( - ["+=", "-=", "/=", "*=", "%=", ">>=", "<<=", ">>>=", "~=", "%=", "|=", "^=", "&="], + ["+=", "-=", "/=", "*=", "%=", ">>=", "<<=", ">>>=", "|=", "^=", "&="], { "=": true }, 0 ); @@ -608,16 +663,16 @@ function NodeWithToken(str, start, end) { NodeWithToken.prototype.toString = function() { return this.name; }; -function parse($TEXT, strict_mode, embed_tokens) { +function parse($TEXT, exigent_mode, embed_tokens) { var S = { - input: tokenizer($TEXT, true), - token: null, - prev: null, - peeked: null, - in_function: 0, - in_loop: 0, - labels: [] + input : typeof $TEXT == "string" ? tokenizer($TEXT, true) : $TEXT, + token : null, + prev : null, + peeked : null, + in_function : 0, + in_loop : 0, + labels : [] }; S.token = next(); @@ -671,7 +726,7 @@ function parse($TEXT, strict_mode, embed_tokens) { function expect(punc) { return expect_token("punc", punc); }; function can_insert_semicolon() { - return !strict_mode && ( + return !exigent_mode && ( S.token.nlb || is("eof") || is("punc", "}") ); }; @@ -693,14 +748,14 @@ function parse($TEXT, strict_mode, embed_tokens) { }; function add_tokens(str, start, end) { - return new NodeWithToken(str, start, end); + return str instanceof NodeWithToken ? str : new NodeWithToken(str, start, end); }; var statement = embed_tokens ? function() { var start = S.token; - var stmt = $statement(); - stmt[0] = add_tokens(stmt[0], start, prev()); - return stmt; + var ast = $statement.apply(this, arguments); + ast[0] = add_tokens(ast[0], start, prev()); + return ast; } : $statement; function $statement() { @@ -802,7 +857,7 @@ function parse($TEXT, strict_mode, embed_tokens) { function labeled_statement(label) { S.labels.push(label); var start = S.token, stat = statement(); - if (strict_mode && !HOP(STATEMENTS_WITH_LABELS, stat[0])) + if (exigent_mode && !HOP(STATEMENTS_WITH_LABELS, stat[0])) unexpected(start); S.labels.pop(); return as("label", label, stat); @@ -827,29 +882,42 @@ function parse($TEXT, strict_mode, embed_tokens) { function for_() { expect("("); - var has_var = is("keyword", "var"); - if (has_var) - next(); - if (is("name") && is_token(peek(), "operator", "in")) { - // for (i in foo) - var name = S.token.value; - next(); next(); - var obj = expression(); - expect(")"); - return as("for-in", has_var, name, obj, in_loop(statement)); - } else { - // classic for - var init = is("punc", ";") ? null : has_var ? var_() : expression(); - expect(";"); - var test = is("punc", ";") ? null : expression(); - expect(";"); - var step = is("punc", ")") ? null : expression(); - expect(")"); - return as("for", init, test, step, in_loop(statement)); + var init = null; + if (!is("punc", ";")) { + init = is("keyword", "var") + ? (next(), var_(true)) + : expression(true, true); + if (is("operator", "in")) + return for_in(init); } + return regular_for(init); }; - function function_(in_statement) { + function regular_for(init) { + expect(";"); + var test = is("punc", ";") ? null : expression(); + expect(";"); + var step = is("punc", ")") ? null : expression(); + expect(")"); + return as("for", init, test, step, in_loop(statement)); + }; + + function for_in(init) { + var lhs = init[0] == "var" ? as("name", init[1][0]) : init; + next(); + var obj = expression(); + expect(")"); + return as("for-in", init, lhs, obj, in_loop(statement)); + }; + + var function_ = embed_tokens ? function() { + var start = prev(); + var ast = $function_.apply(this, arguments); + ast[0] = add_tokens(ast[0], start, prev()); + return ast; + } : $function_; + + function $function_(in_statement) { var name = is("name") ? prog1(S.token.value, next) : null; if (in_statement && !name) unexpected(); @@ -946,7 +1014,7 @@ function parse($TEXT, strict_mode, embed_tokens) { return as("try", body, bcatch, bfinally); }; - function vardefs() { + function vardefs(no_in) { var a = []; for (;;) { if (!is("name")) @@ -955,7 +1023,7 @@ function parse($TEXT, strict_mode, embed_tokens) { next(); if (is("operator", "=")) { next(); - a.push([ name, expression(false) ]); + a.push([ name, expression(false, no_in) ]); } else { a.push([ name ]); } @@ -966,8 +1034,8 @@ function parse($TEXT, strict_mode, embed_tokens) { return a; }; - function var_() { - return as("var", vardefs()); + function var_(no_in) { + return as("var", vardefs(no_in)); }; function const_() { @@ -1022,27 +1090,30 @@ function parse($TEXT, strict_mode, embed_tokens) { unexpected(); }; - function expr_list(closing, allow_trailing_comma) { + function expr_list(closing, allow_trailing_comma, allow_empty) { var first = true, a = []; while (!is("punc", closing)) { if (first) first = false; else expect(","); - if (allow_trailing_comma && is("punc", closing)) - break; - a.push(expression(false)); + if (allow_trailing_comma && is("punc", closing)) break; + if (is("punc", ",") && allow_empty) { + a.push([ "atom", "undefined" ]); + } else { + a.push(expression(false)); + } } next(); return a; }; function array_() { - return as("array", expr_list("]", !strict_mode)); + return as("array", expr_list("]", !exigent_mode, true)); }; function object_() { var first = true, a = []; while (!is("punc", "}")) { if (first) first = false; else expect(","); - if (!strict_mode && is("punc", "}")) + if (!exigent_mode && is("punc", "}")) // allow trailing comma break; var type = S.token.type; @@ -1105,61 +1176,65 @@ function parse($TEXT, strict_mode, embed_tokens) { return as(tag, op, expr); }; - function expr_op(left, min_prec) { + function expr_op(left, min_prec, no_in) { var op = is("operator") ? S.token.value : null; + if (op && op == "in" && no_in) op = null; var prec = op != null ? PRECEDENCE[op] : null; if (prec != null && prec > min_prec) { next(); - var right = expr_op(expr_atom(true), prec); - return expr_op(as("binary", op, left, right), min_prec); + var right = expr_op(expr_atom(true), prec, no_in); + return expr_op(as("binary", op, left, right), min_prec, no_in); } return left; }; - function expr_ops() { - return expr_op(expr_atom(true), 0); + function expr_ops(no_in) { + return expr_op(expr_atom(true), 0, no_in); }; - function maybe_conditional() { - var expr = expr_ops(); + function maybe_conditional(no_in) { + var expr = expr_ops(no_in); if (is("operator", "?")) { next(); var yes = expression(false); expect(":"); - return as("conditional", expr, yes, expression(false)); + return as("conditional", expr, yes, expression(false, no_in)); } return expr; }; function is_assignable(expr) { + if (!exigent_mode) return true; switch (expr[0]) { case "dot": case "sub": + case "new": + case "call": return true; case "name": return expr[1] != "this"; } }; - function maybe_assign() { - var left = maybe_conditional(), val = S.token.value; + function maybe_assign(no_in) { + var left = maybe_conditional(no_in), val = S.token.value; if (is("operator") && HOP(ASSIGNMENT, val)) { if (is_assignable(left)) { next(); - return as("assign", ASSIGNMENT[val], left, maybe_assign()); + return as("assign", ASSIGNMENT[val], left, maybe_assign(no_in)); } croak("Invalid assignment"); } return left; }; - function expression(commas) { + function expression(commas, no_in) { if (arguments.length == 0) commas = true; - var expr = maybe_assign(); + var expr = maybe_assign(no_in); if (commas && is("punc", ",")) { next(); - return as("seq", expr, expression()); + return as("seq", expr, expression(true, no_in)); } return expr; }; @@ -1222,6 +1297,8 @@ function HOP(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }; +var warn = function() {}; + /* -----[ Exports ]----- */ exports.tokenizer = tokenizer; @@ -1237,3 +1314,6 @@ exports.KEYWORDS = KEYWORDS; exports.ATOMIC_START_TOKEN = ATOMIC_START_TOKEN; exports.OPERATORS = OPERATORS; exports.is_alphanumeric_char = is_alphanumeric_char; +exports.set_logger = function(logger) { + warn = logger; +}; diff --git a/build/lib/process.js b/build/lib/process.js index edcf599d..09cbc2ad 100644 --- a/build/lib/process.js +++ b/build/lib/process.js @@ -10,14 +10,13 @@ Exported functions: - - ast_mangle(ast, include_toplevel) -- mangles the - variable/function names in the AST. Returns an AST. Pass true - as second argument to mangle toplevel names too. + - ast_mangle(ast, options) -- mangles the variable/function names + in the AST. Returns an AST. - ast_squeeze(ast) -- employs various optimizations to make the final generated code even smaller. Returns an AST. - - gen_code(ast, beautify) -- generates JS code from the AST. Pass + - gen_code(ast, options) -- generates JS code from the AST. Pass true (or an object, see the code for some options) as second argument to get "pretty" (indented) code. @@ -69,139 +68,135 @@ var jsp = require("./parse-js"), function ast_walker(ast) { function _vardefs(defs) { - return MAP(defs, function(def){ + return [ this[0], MAP(defs, function(def){ var a = [ def[0] ]; if (def.length > 1) a[1] = walk(def[1]); return a; - }); + }) ]; }; var walkers = { "string": function(str) { - return [ "string", str ]; + return [ this[0], str ]; }, "num": function(num) { - return [ "num", num ]; + return [ this[0], num ]; }, "name": function(name) { - return [ "name", name ]; + return [ this[0], name ]; }, "toplevel": function(statements) { - return [ "toplevel", MAP(statements, walk) ]; + return [ this[0], MAP(statements, walk) ]; }, "block": function(statements) { - var out = [ "block" ]; + var out = [ this[0] ]; if (statements != null) out.push(MAP(statements, walk)); return out; }, - "var": function(defs) { - return [ "var", _vardefs(defs) ]; - }, - "const": function(defs) { - return [ "const", _vardefs(defs) ]; - }, + "var": _vardefs, + "const": _vardefs, "try": function(t, c, f) { return [ - "try", + this[0], MAP(t, walk), c != null ? [ c[0], MAP(c[1], walk) ] : null, f != null ? MAP(f, walk) : null ]; }, "throw": function(expr) { - return [ "throw", walk(expr) ]; + return [ this[0], walk(expr) ]; }, "new": function(ctor, args) { - return [ "new", walk(ctor), MAP(args, walk) ]; + return [ this[0], walk(ctor), MAP(args, walk) ]; }, "switch": function(expr, body) { - return [ "switch", walk(expr), MAP(body, function(branch){ + return [ this[0], walk(expr), MAP(body, function(branch){ return [ branch[0] ? walk(branch[0]) : null, MAP(branch[1], walk) ]; }) ]; }, "break": function(label) { - return [ "break", label ]; + return [ this[0], label ]; }, "continue": function(label) { - return [ "continue", label ]; + return [ this[0], label ]; }, "conditional": function(cond, t, e) { - return [ "conditional", walk(cond), walk(t), walk(e) ]; + return [ this[0], walk(cond), walk(t), walk(e) ]; }, "assign": function(op, lvalue, rvalue) { - return [ "assign", op, walk(lvalue), walk(rvalue) ]; + return [ this[0], op, walk(lvalue), walk(rvalue) ]; }, "dot": function(expr) { - return [ "dot", walk(expr) ].concat(slice(arguments, 1)); + return [ this[0], walk(expr) ].concat(slice(arguments, 1)); }, "call": function(expr, args) { - return [ "call", walk(expr), MAP(args, walk) ]; + return [ this[0], walk(expr), MAP(args, walk) ]; }, "function": function(name, args, body) { - return [ "function", name, args.slice(), MAP(body, walk) ]; + return [ this[0], name, args.slice(), MAP(body, walk) ]; }, "defun": function(name, args, body) { - return [ "defun", name, args.slice(), MAP(body, walk) ]; + return [ this[0], name, args.slice(), MAP(body, walk) ]; }, "if": function(conditional, t, e) { - return [ "if", walk(conditional), walk(t), walk(e) ]; + return [ this[0], walk(conditional), walk(t), walk(e) ]; }, "for": function(init, cond, step, block) { - return [ "for", walk(init), walk(cond), walk(step), walk(block) ]; + return [ this[0], walk(init), walk(cond), walk(step), walk(block) ]; }, - "for-in": function(has_var, key, hash, block) { - return [ "for-in", has_var, key, walk(hash), walk(block) ]; + "for-in": function(vvar, key, hash, block) { + return [ this[0], walk(vvar), walk(key), walk(hash), walk(block) ]; }, "while": function(cond, block) { - return [ "while", walk(cond), walk(block) ]; + return [ this[0], walk(cond), walk(block) ]; }, "do": function(cond, block) { - return [ "do", walk(cond), walk(block) ]; + return [ this[0], walk(cond), walk(block) ]; }, "return": function(expr) { - return [ "return", walk(expr) ]; + return [ this[0], walk(expr) ]; }, "binary": function(op, left, right) { - return [ "binary", op, walk(left), walk(right) ]; + return [ this[0], op, walk(left), walk(right) ]; }, "unary-prefix": function(op, expr) { - return [ "unary-prefix", op, walk(expr) ]; + return [ this[0], op, walk(expr) ]; }, "unary-postfix": function(op, expr) { - return [ "unary-postfix", op, walk(expr) ]; + return [ this[0], op, walk(expr) ]; }, "sub": function(expr, subscript) { - return [ "sub", walk(expr), walk(subscript) ]; + return [ this[0], walk(expr), walk(subscript) ]; }, "object": function(props) { - return [ "object", MAP(props, function(p){ + return [ this[0], MAP(props, function(p){ return p.length == 2 ? [ p[0], walk(p[1]) ] : [ p[0], walk(p[1]), p[2] ]; // get/set-ter }) ]; }, "regexp": function(rx, mods) { - return [ "regexp", rx, mods ]; + return [ this[0], rx, mods ]; }, "array": function(elements) { - return [ "array", MAP(elements, walk) ]; + return [ this[0], MAP(elements, walk) ]; }, "stat": function(stat) { - return [ "stat", walk(stat) ]; + return [ this[0], walk(stat) ]; }, "seq": function() { - return [ "seq" ].concat(MAP(slice(arguments), walk)); + return [ this[0] ].concat(MAP(slice(arguments), walk)); }, "label": function(name, block) { - return [ "label", name, walk(block) ]; + return [ this[0], name, walk(block) ]; }, "with": function(expr, block) { - return [ "with", walk(expr), walk(block) ]; + return [ this[0], walk(expr), walk(block) ]; }, "atom": function(name) { - return [ "atom", name ]; + return [ this[0], name ]; } }; @@ -260,8 +255,8 @@ function Scope(parent) { this.rev_mangled = {}; // reverse lookup (mangled => orig.name) this.cname = -1; // current mangled name this.refs = {}; // names referenced from this scope - this.uses_with = false; // will become TRUE if eval() is detected in this or any subscopes - this.uses_eval = false; // will become TRUE if with() is detected in this or any subscopes + this.uses_with = false; // will become TRUE if with() is detected in this or any subscopes + this.uses_eval = false; // will become TRUE if eval() is detected in this or any subscopes this.parent = parent; // parent scope this.children = []; // sub-scopes if (parent) { @@ -382,7 +377,7 @@ function ast_add_scope(ast) { }; function _lambda(name, args, body) { - return [ this[0], define(name), args, with_new_scope(function(){ + return [ this[0], this[0] == "defun" ? define(name) : name, args, with_new_scope(function(){ MAP(args, define); return MAP(body, walk); })]; @@ -405,7 +400,7 @@ function ast_add_scope(ast) { }, "try": function(t, c, f) { if (c != null) return [ - "try", + this[0], MAP(t, walk), [ define(c[0]), MAP(c[1], walk) ], f != null ? MAP(f, walk) : null @@ -415,10 +410,6 @@ function ast_add_scope(ast) { if (name == "eval") having_eval.push(current_scope); reference(name); - }, - "for-in": function(has_var, name) { - if (has_var) define(name); - else reference(name); } }, function(){ return walk(ast); @@ -461,11 +452,14 @@ function ast_add_scope(ast) { /* -----[ mangle names ]----- */ -function ast_mangle(ast, do_toplevel) { +function ast_mangle(ast, options) { var w = ast_walker(), walk = w.walk, scope; + options = options || {}; function get_mangled(name, newMangle) { - if (!do_toplevel && !scope.parent) return name; // don't mangle toplevel + if (!options.toplevel && !scope.parent) return name; // don't mangle toplevel + if (options.except && member(name, options.except)) + return name; return scope.get_mangled(name, newMangle); }; @@ -491,9 +485,9 @@ function ast_mangle(ast, do_toplevel) { }; function _vardefs(defs) { - return MAP(defs, function(d){ + return [ this[0], MAP(defs, function(d){ return [ get_mangled(d[0]), walk(d[1]) ]; - }); + }) ]; }; return w.with_walkers({ @@ -510,28 +504,22 @@ function ast_mangle(ast, do_toplevel) { } return ast; }, - "var": function(defs) { - return [ "var", _vardefs(defs) ]; - }, - "const": function(defs) { - return [ "const", _vardefs(defs) ]; - }, + "var": _vardefs, + "const": _vardefs, "name": function(name) { - return [ "name", get_mangled(name) ]; + return [ this[0], get_mangled(name) ]; }, "try": function(t, c, f) { - return [ "try", + return [ this[0], MAP(t, walk), c != null ? [ get_mangled(c[0]), MAP(c[1], walk) ] : null, f != null ? MAP(f, walk) : null ]; }, "toplevel": function(body) { - return with_scope(this.scope, function(){ - return [ "toplevel", MAP(body, walk) ]; + var self = this; + return with_scope(self.scope, function(){ + return [ self[0], MAP(body, walk) ]; }); - }, - "for-in": function(has_var, name, obj, stat) { - return [ "for-in", has_var, get_mangled(name), walk(obj), walk(stat) ]; } }, function() { return walk(ast_add_scope(ast)); @@ -569,28 +557,29 @@ function aborts(t) { } }; -function negate(c) { - var not_c = [ "unary-prefix", "!", c ]; - switch (c[0]) { - case "unary-prefix": - return c[1] == "!" ? c[2] : not_c; - case "binary": - var op = c[1], left = c[2], right = c[3]; - switch (op) { - case "<=": return [ "binary", ">", left, right ]; - case "<": return [ "binary", ">=", left, right ]; - case ">=": return [ "binary", "<", left, right ]; - case ">": return [ "binary", "<=", left, right ]; - case "==": return [ "binary", "!=", left, right ]; - case "!=": return [ "binary", "==", left, right ]; - case "===": return [ "binary", "!==", left, right ]; - case "!==": return [ "binary", "===", left, right ]; - case "&&": return best_of(not_c, [ "binary", "||", negate(left), negate(right) ]); - case "||": return best_of(not_c, [ "binary", "&&", negate(left), negate(right) ]); - } - break; - } - return not_c; +function boolean_expr(expr) { + return ( (expr[0] == "unary-prefix" + && member(expr[1], [ "!", "delete" ])) || + + (expr[0] == "binary" + && member(expr[1], [ "in", "instanceof", "==", "!=", "===", "!==", "<", "<=", ">=", ">" ])) || + + (expr[0] == "binary" + && member(expr[1], [ "&&", "||" ]) + && boolean_expr(expr[2]) + && boolean_expr(expr[3])) || + + (expr[0] == "conditional" + && boolean_expr(expr[2]) + && boolean_expr(expr[3])) || + + (expr[0] == "assign" + && expr[1] === true + && boolean_expr(expr[3])) || + + (expr[0] == "seq" + && boolean_expr(expr[expr.length - 1])) + ); }; function make_conditional(c, t, e) { @@ -605,16 +594,143 @@ function empty(b) { return !b || (b[0] == "block" && (!b[1] || b[1].length == 0)); }; +function is_string(node) { + return (node[0] == "string" || + node[0] == "unary-prefix" && node[1] == "typeof" || + node[0] == "binary" && node[1] == "+" && + (is_string(node[2]) || is_string(node[3]))); +}; + +var when_constant = (function(){ + + var $NOT_CONSTANT = {}; + + // this can only evaluate constant expressions. If it finds anything + // not constant, it throws $NOT_CONSTANT. + function evaluate(expr) { + switch (expr[0]) { + case "string": + case "num": + return expr[1]; + case "name": + case "atom": + switch (expr[1]) { + case "true": return true; + case "false": return false; + } + break; + case "unary-prefix": + switch (expr[1]) { + case "!": return !evaluate(expr[2]); + case "typeof": return typeof evaluate(expr[2]); + case "~": return ~evaluate(expr[2]); + case "-": return -evaluate(expr[2]); + case "+": return +evaluate(expr[2]); + } + break; + case "binary": + var left = expr[2], right = expr[3]; + switch (expr[1]) { + case "&&" : return evaluate(left) && evaluate(right); + case "||" : return evaluate(left) || evaluate(right); + case "|" : return evaluate(left) | evaluate(right); + case "&" : return evaluate(left) & evaluate(right); + case "^" : return evaluate(left) ^ evaluate(right); + case "+" : return evaluate(left) + evaluate(right); + case "*" : return evaluate(left) * evaluate(right); + case "/" : return evaluate(left) / evaluate(right); + case "-" : return evaluate(left) - evaluate(right); + case "<<" : return evaluate(left) << evaluate(right); + case ">>" : return evaluate(left) >> evaluate(right); + case ">>>" : return evaluate(left) >>> evaluate(right); + case "==" : return evaluate(left) == evaluate(right); + case "===" : return evaluate(left) === evaluate(right); + case "!=" : return evaluate(left) != evaluate(right); + case "!==" : return evaluate(left) !== evaluate(right); + case "<" : return evaluate(left) < evaluate(right); + case "<=" : return evaluate(left) <= evaluate(right); + case ">" : return evaluate(left) > evaluate(right); + case ">=" : return evaluate(left) >= evaluate(right); + case "in" : return evaluate(left) in evaluate(right); + case "instanceof" : return evaluate(left) instanceof evaluate(right); + } + } + throw $NOT_CONSTANT; + }; + + return function(expr, yes, no) { + try { + var val = evaluate(expr), ast; + switch (typeof val) { + case "string": ast = [ "string", val ]; break; + case "number": ast = [ "num", val ]; break; + case "boolean": ast = [ "name", String(val) ]; break; + default: throw new Error("Can't handle constant of type: " + (typeof val)); + } + return yes.call(expr, ast, val); + } catch(ex) { + if (ex === $NOT_CONSTANT) { + if (expr[0] == "binary" + && (expr[1] == "===" || expr[1] == "!==") + && ((is_string(expr[2]) && is_string(expr[3])) + || (boolean_expr(expr[2]) && boolean_expr(expr[3])))) { + expr[1] = expr[1].substr(0, 2); + } + return no ? no.call(expr, expr) : null; + } + else throw ex; + } + }; + +})(); + +function warn_unreachable(ast) { + if (!empty(ast)) + warn("Dropping unreachable code: " + gen_code(ast, true)); +}; + function ast_squeeze(ast, options) { options = defaults(options, { make_seqs : true, dead_code : true, - no_warnings : false, - extra : false + keep_comps : true, + no_warnings : false }); var w = ast_walker(), walk = w.walk, scope; + function negate(c) { + var not_c = [ "unary-prefix", "!", c ]; + switch (c[0]) { + case "unary-prefix": + return c[1] == "!" && boolean_expr(c[2]) ? c[2] : not_c; + case "seq": + c = slice(c); + c[c.length - 1] = negate(c[c.length - 1]); + return c; + case "conditional": + return best_of(not_c, [ "conditional", c[1], negate(c[2]), negate(c[3]) ]); + case "binary": + var op = c[1], left = c[2], right = c[3]; + if (!options.keep_comps) switch (op) { + case "<=" : return [ "binary", ">", left, right ]; + case "<" : return [ "binary", ">=", left, right ]; + case ">=" : return [ "binary", "<", left, right ]; + case ">" : return [ "binary", "<=", left, right ]; + } + switch (op) { + case "==" : return [ "binary", "!=", left, right ]; + case "!=" : return [ "binary", "==", left, right ]; + case "===" : return [ "binary", "!==", left, right ]; + case "!==" : return [ "binary", "===", left, right ]; + case "&&" : return best_of(not_c, [ "binary", "||", negate(left), negate(right) ]); + case "||" : return best_of(not_c, [ "binary", "&&", negate(left), negate(right) ]); + } + break; + } + return not_c; + }; + function with_scope(s, cont) { var _scope = scope; scope = s; @@ -624,89 +740,14 @@ function ast_squeeze(ast, options) { return ret; }; - function is_constant(node) { - return node[0] == "string" || node[0] == "num"; - }; - - function find_first_execute(node) { - if (!node) - return false; - - switch (node[0]) { - case "num": - case "string": - case "name": - return node; - case "call": - case "conditional": - case "for": - case "if": - case "new": - case "return": - case "stat": - case "switch": - case "throw": - return find_first_execute(node[1]); - case "binary": - return find_first_execute(node[2]); - case "assign": - if (node[1] === true) - return find_first_execute(node[3]); - break; - case "var": - if (node[1][0].length > 1) - return find_first_execute(node[1][0][1]); - break; - } - return null; - } - - function find_assign_recursive(p, v) { - if (p[0] == "assign" && p[1] != true || p[0] == "unary-prefix") { - if (p[2][0] == "name" && v[0] == "name" && p[2][1] == v[1]) - return true; - return false; - } - - if (p[0] != "assign" || p[1] !== true) - return false; - - if ((is_constant(p[3]) && p[3][0] == v[0] && p[3][1] == v[1]) || - (p[3][0] == "name" && v[0] == "name" && p[3][1] == v[1]) || - (p[2][0] == "name" && v[0] == "name" && p[2][1] == v[1])) - return true; - - return find_assign_recursive(p[3], v); - }; - function rmblock(block) { - if (block != null && block[0] == "block" && block[1] && block[1].length == 1) - block = block[1][0]; - return block; - }; - - function clone(obj) { - if (obj && obj.constructor == Array) - return MAP(obj, clone); - return obj; - }; - - function make_seq_to_statements(node) { - if (node[0] != "seq") { - switch (node[0]) { - case "var": - case "const": - return [ node ]; - default: - return [ [ "stat", node ] ]; - } + if (block != null && block[0] == "block" && block[1]) { + if (block[1].length == 1) + block = block[1][0]; + else if (block[1].length == 0) + block = [ "block" ]; } - - var ret = []; - for (var i = 1; i < node.length; i++) - ret.push.apply(ret, make_seq_to_statements(node[i])); - - return ret; + return block; }; function _lambda(name, args, body) { @@ -734,64 +775,6 @@ function ast_squeeze(ast, options) { return a; }, []); - if (options.extra) { - // Detightening things. We do this because then we can assume that the - // statements are structured in a specific way. - statements = (function(a, prev) { - statements.forEach(function(cur) { - switch (cur[0]) { - case "for": - if (cur[1] != null) { - a.push.apply(a, make_seq_to_statements(cur[1])); - cur[1] = null; - } - a.push(cur); - break; - case "stat": - var stats = make_seq_to_statements(cur[1]); - stats.forEach(function(s) { - if (s[1][0] == "unary-postfix") - s[1][0] = "unary-prefix"; - }); - a.push.apply(a, stats); - break; - default: - a.push(cur); - } - }); - return a; - })([]); - - statements = (function(a, prev) { - statements.forEach(function(cur) { - if (!(prev && prev[0] == "stat")) { - a.push(cur); - prev = cur; - return; - } - - var p = prev[1]; - var c = find_first_execute(cur); - if (c && find_assign_recursive(p, c)) { - var old_cur = clone(cur); - c.splice(0, c.length); - c.push.apply(c, p); - var tmp_cur = best_of(cur, [ "toplevel", [ prev, old_cur ] ]); - if (tmp_cur == cur) { - a[a.length -1] = cur; - } else { - cur = old_cur; - a.push(cur); - } - } else { - a.push(cur); - } - prev = cur; - }); - return a; - })([]); - } - statements = (function(a, prev){ statements.forEach(function(cur){ if (prev && ((cur[0] == "var" && prev[0] == "var") || @@ -812,7 +795,7 @@ function ast_squeeze(ast, options) { a.push(st); } else if (!options.no_warnings) - warn("Removing unreachable code: " + gen_code(st, true)); + warn_unreachable(st); } else { a.push(st); @@ -835,22 +818,6 @@ function ast_squeeze(ast, options) { return a; })([]); - if (options.extra) { - statements = (function(a, prev){ - statements.forEach(function(cur){ - var replaced = false; - if (prev && cur[0] == "for" && cur[1] == null && (prev[0] == "var" || prev[0] == "const" || prev[0] == "stat")) { - cur[1] = prev; - a[a.length - 1] = cur; - } else { - a.push(cur); - } - prev = cur; - }); - return a; - })([]); - } - if (block_type == "lambda") statements = (function(i, a, stat){ while (i < statements.length) { stat = statements[i++]; @@ -874,6 +841,20 @@ function ast_squeeze(ast, options) { }; function make_if(c, t, e) { + return when_constant(c, function(ast, val){ + if (val) { + warn_unreachable(e); + return t; + } else { + warn_unreachable(t); + return e; + } + }, function() { + return make_real_if(c, t, e); + }); + }; + + function make_real_if(c, t, e) { c = walk(c); t = walk(t); e = walk(e); @@ -901,7 +882,10 @@ function ast_squeeze(ast, options) { if (empty(e) && empty(t)) return [ "stat", c ]; var ret = [ "if", c, t, e ]; - if (t[0] == "stat") { + if (t[0] == "if" && empty(t[3]) && empty(e)) { + ret = best_of(ret, walk([ "if", [ "binary", "&&", c, t[1] ], t[2] ])); + } + else if (t[0] == "stat") { if (e) { if (e[0] == "stat") { ret = best_of(ret, [ "stat", make_conditional(c, t[1], e[1]) ]); @@ -911,7 +895,7 @@ function ast_squeeze(ast, options) { ret = best_of(ret, [ "stat", make_conditional(c, t[1]) ]); } } - else if (e && t[0] == e[0] && (t[0] == "return" || t[0] == "throw")) { + else if (e && t[0] == e[0] && (t[0] == "return" || t[0] == "throw") && t[1] && e[1]) { ret = best_of(ret, [ t[0], make_conditional(c, t[1], e[1] ) ]); } else if (e && aborts(t)) { @@ -936,13 +920,25 @@ function ast_squeeze(ast, options) { return ret; }; + function _do_while(cond, body) { + return when_constant(cond, function(cond, val){ + if (!val) { + warn_unreachable(body); + return [ "block" ]; + } else { + return [ "for", null, null, null, walk(body) ]; + } + }); + }; + return w.with_walkers({ "sub": function(expr, subscript) { if (subscript[0] == "string") { var name = subscript[1]; - if (is_identifier(name)) { + if (is_identifier(name)) return [ "dot", walk(expr), name ]; - } + else if (/^[1-9][0-9]*$/.test(name) || name === "0") + return [ "sub", walk(expr), [ "num", parseInt(name, 10) ] ]; } }, "if": make_if, @@ -963,35 +959,23 @@ function ast_squeeze(ast, options) { return [ branch[0] ? walk(branch[0]) : null, block ]; }) ]; }, - "function": _lambda, + "function": function() { + var ret = _lambda.apply(this, arguments); + if (ret[1] && !HOP(scope.refs, ret[1])) { + ret[1] = null; + } + return ret; + }, "defun": _lambda, "block": function(body) { if (body) return rmblock([ "block", tighten(MAP(body, walk)) ]); }, "binary": function(op, left, right) { - left = walk(left); - right = walk(right); - var best = [ "binary", op, left, right ]; - if (is_constant(right)) { - if (is_constant(left)) { - var val = null; - switch (op) { - case "+": val = left[1] + right[1]; break; - case "*": val = left[1] * right[1]; break; - case "/": val = left[1] / right[1]; break; - case "-": val = left[1] - right[1]; break; - case "<<": val = left[1] << right[1]; break; - case ">>": val = left[1] >> right[1]; break; - case ">>>": val = left[1] >>> right[1]; break; - } - if (val != null) { - best = best_of(best, [ typeof val == "string" ? "string" : "num", val ]); - } - } else if (left[0] == "binary" && left[1] == "+" && left[3][0] == "string") { - best = best_of(best, [ "binary", "+", left[2], [ "string", left[3][1] + right[1] ] ]); - } - } - return best; + return when_constant([ "binary", op, walk(left), walk(right) ], function yes(c){ + return best_of(walk(c), this); + }, function no() { + return this; + }); }, "conditional": function(c, t, e) { return make_conditional(walk(c), walk(t), walk(e)); @@ -1004,17 +988,14 @@ function ast_squeeze(ast, options) { f != null ? tighten(MAP(f, walk)) : null ]; }, - "unary-prefix": function(op, cond) { - if (op == "!") { - cond = walk(cond); - if (cond[0] == "unary-prefix" && cond[1] == "!") { - var p = w.parent(); - if (p[0] == "unary-prefix" && p[1] == "!") - return cond[2]; - return [ "unary-prefix", "!", cond ]; - } - return best_of(this, negate(cond)); - } + "unary-prefix": function(op, expr) { + expr = walk(expr); + var ret = [ "unary-prefix", op, expr ]; + if (op == "!") + ret = best_of(ret, negate(expr)); + return when_constant(ret, function(ast, val){ + return walk(ast); // it's either true or false, so minifies to !0 or !1 + }, function() { return ret }); }, "name": function(name) { switch (name) { @@ -1035,7 +1016,9 @@ function ast_squeeze(ast, options) { if (expr[0] == "name" && expr[1] == "Array" && args.length != 1 && !scope.has("Array")) { return [ "array", args ]; } - } + }, + "while": _do_while, + "do": _do_while }, function() { return walk(ast_add_scope(ast)); }); @@ -1046,6 +1029,7 @@ function ast_squeeze(ast, options) { var DOT_CALL_NO_PARENS = jsp.array_to_hash([ "name", "array", + "object", "string", "dot", "sub", @@ -1053,9 +1037,9 @@ var DOT_CALL_NO_PARENS = jsp.array_to_hash([ "regexp" ]); -function make_string(str) { +function make_string(str, ascii_only) { var dq = 0, sq = 0; - str = str.replace(/[\\\b\f\n\r\t\x22\x27]/g, function(s){ + str = str.replace(/[\\\b\f\n\r\t\x22\x27\u2028\u2029]/g, function(s){ switch (s) { case "\\": return "\\\\"; case "\b": return "\\b"; @@ -1063,34 +1047,56 @@ function make_string(str) { case "\n": return "\\n"; case "\r": return "\\r"; case "\t": return "\\t"; + case "\u2028": return "\\u2028"; + case "\u2029": return "\\u2029"; case '"': ++dq; return '"'; case "'": ++sq; return "'"; } return s; }); - if (dq > sq) { - return "'" + str.replace(/\x27/g, "\\'") + "'"; - } else { - return '"' + str.replace(/\x22/g, '\\"') + '"'; - } + if (ascii_only) str = to_ascii(str); + if (dq > sq) return "'" + str.replace(/\x27/g, "\\'") + "'"; + else return '"' + str.replace(/\x22/g, '\\"') + '"'; }; -function gen_code(ast, beautify) { - if (beautify) beautify = defaults(beautify, { +function to_ascii(str) { + return str.replace(/[\u0080-\uffff]/g, function(ch) { + var code = ch.charCodeAt(0).toString(16); + while (code.length < 4) code = "0" + code; + return "\\u" + code; + }); +}; + +function gen_code(ast, options) { + options = defaults(options, { indent_start : 0, indent_level : 4, quote_keys : false, - space_colon : false + space_colon : false, + beautify : false, + ascii_only : false }); + var beautify = !!options.beautify; var indentation = 0, newline = beautify ? "\n" : "", space = beautify ? " " : ""; + function encode_string(str) { + return make_string(str, options.ascii_only); + }; + + function make_name(name) { + name = name.toString(); + if (options.ascii_only) + name = to_ascii(name); + return name; + }; + function indent(line) { if (line == null) line = ""; if (beautify) - line = repeat_string(" ", beautify.indent_start + indentation * beautify.indent_level) + line; + line = repeat_string(" ", options.indent_start + indentation * options.indent_level) + line; return line; }; @@ -1144,7 +1150,7 @@ function gen_code(ast, beautify) { }; function needs_parens(expr) { - if (expr[0] == "function") { + if (expr[0] == "function" || expr[0] == "object") { // dot/call on a literal function requires the // function literal itself to be parenthesized // only if it's the first "thing" in a @@ -1156,9 +1162,8 @@ function gen_code(ast, beautify) { var a = slice($stack), self = a.pop(), p = a.pop(); while (p) { if (p[0] == "stat") return true; - if ((p[0] == "seq" && p[1] === self) || - (p[0] == "call" && p[1] === self) || - (p[0] == "binary" && p[2] === self)) { + if (((p[0] == "seq" || p[0] == "call" || p[0] == "dot" || p[0] == "sub" || p[0] == "conditional") && p[1] === self) || + ((p[0] == "binary" || p[0] == "assign" || p[0] == "unary-postfix") && p[2] === self)) { self = p; p = a.pop(); } else { @@ -1185,7 +1190,7 @@ function gen_code(ast, beautify) { }; var generators = { - "string": make_string, + "string": encode_string, "num": make_num, "name": make_name, "toplevel": function(statements) { @@ -1253,9 +1258,10 @@ function gen_code(ast, beautify) { }, "dot": function(expr) { var out = make(expr), i = 1; - if (expr[0] == "num") - out += "."; - else if (needs_parens(expr)) + if (expr[0] == "num") { + if (!/\./.test(expr[1])) + out += "."; + } else if (needs_parens(expr)) out = "(" + out + ")"; while (i < arguments.length) out += "." + make_name(arguments[i++]); @@ -1288,12 +1294,11 @@ function gen_code(ast, beautify) { out.push("(" + args + ")", make(block)); return add_spaces(out); }, - "for-in": function(has_var, key, hash, block) { - var out = add_spaces([ "for", "(" ]); - if (has_var) - out += "var "; - out += add_spaces([ make_name(key) + " in " + make(hash) + ")", make(block) ]); - return out; + "for-in": function(vvar, key, hash, block) { + return add_spaces([ "for", "(" + + (vvar ? make(vvar).replace(/;+$/, "") : make(key)), + "in", + make(hash) + ")", make(block) ]); }, "while": function(condition, block) { return add_spaces([ "while", "(" + make(condition) + ")", make(block) ]); @@ -1316,7 +1321,8 @@ function gen_code(ast, beautify) { left = "(" + left + ")"; } if (member(rvalue[0], [ "assign", "conditional", "seq" ]) || - rvalue[0] == "binary" && PRECEDENCE[operator] >= PRECEDENCE[rvalue[1]]) { + rvalue[0] == "binary" && PRECEDENCE[operator] >= PRECEDENCE[rvalue[1]] && + !(rvalue[1] == operator && member(operator, [ "&&", "||", "*" ]))) { right = "(" + right + ")"; } return add_spaces([ left, operator, right ]); @@ -1350,14 +1356,15 @@ function gen_code(ast, beautify) { return indent(make_function(p[0], p[1][2], p[1][3], p[2])); } var key = p[0], val = make(p[1]); - if (beautify && beautify.quote_keys) { - key = make_string(key); - } else if (typeof key == "number" || !beautify && +key + "" == key) { + if (options.quote_keys) { + key = encode_string(key); + } else if ((typeof key == "number" || !beautify && +key + "" == key) + && parseFloat(key) >= 0) { key = make_num(+key); } else if (!is_identifier(key)) { - key = make_string(key); + key = encode_string(key); } - return indent(add_spaces(beautify && beautify.space_colon + return indent(add_spaces(beautify && options.space_colon ? [ key, ":", val ] : [ key + ":", val ])); }).join("," + newline); @@ -1369,6 +1376,7 @@ function gen_code(ast, beautify) { "array": function(elements) { if (elements.length == 0) return "[]"; return add_spaces([ "[", add_commas(MAP(elements, function(el){ + if (!beautify && el[0] == "atom" && el[1] == "undefined") return ""; return parenthesize(el, "seq"); })), "]" ]); }, @@ -1386,12 +1394,6 @@ function gen_code(ast, beautify) { }, "atom": function(name) { return make_name(name); - }, - "comment1": function(text) { - return "//" + text + "\n"; - }, - "comment2": function(text) { - return "/*" + text + "*/"; } }; @@ -1435,17 +1437,21 @@ function gen_code(ast, beautify) { return add_spaces([ out, make_block(body) ]); }; - function make_name(name) { - return name.toString(); - }; - function make_block_statements(statements) { for (var a = [], last = statements.length - 1, i = 0; i <= last; ++i) { var stat = statements[i]; var code = make(stat); if (code != ";") { - if (!beautify && i == last) - code = code.replace(/;+\s*$/, ""); + if (!beautify && i == last) { + if ((stat[0] == "while" && empty(stat[2])) || + (member(stat[0], [ "for", "for-in"] ) && empty(stat[4])) || + (stat[0] == "if" && empty(stat[2]) && !stat[3]) || + (stat[0] == "if" && stat[3] && empty(stat[3]))) { + code = code.replace(/;*\s*$/, ";"); + } else { + code = code.replace(/;+\s*$/, ""); + } + } a.push(code); } } @@ -1480,7 +1486,7 @@ function gen_code(ast, beautify) { function make_1vardef(def) { var name = def[0], val = def[1]; if (val != null) - name = add_spaces([ name, "=", make(val) ]); + name = add_spaces([ make_name(name), "=", parenthesize(val, "seq") ]); return name; }; @@ -1500,6 +1506,49 @@ function gen_code(ast, beautify) { return make(ast); }; +function split_lines(code, max_line_length) { + var splits = [ 0 ]; + jsp.parse(function(){ + var next_token = jsp.tokenizer(code); + var last_split = 0; + var prev_token; + function current_length(tok) { + return tok.pos - last_split; + }; + function split_here(tok) { + last_split = tok.pos; + splits.push(last_split); + }; + function custom(){ + var tok = next_token.apply(this, arguments); + out: { + if (prev_token) { + if (prev_token.type == "keyword") break out; + } + if (current_length(tok) > max_line_length) { + switch (tok.type) { + case "keyword": + case "atom": + case "name": + case "punc": + split_here(tok); + break out; + } + } + } + prev_token = tok; + return tok; + }; + custom.context = function() { + return next_token.context.apply(this, arguments); + }; + return custom; + }()); + return splits.map(function(pos, i){ + return code.substring(pos, splits[i + 1] || code.length); + }).join("\n"); +}; + /* -----[ Utilities ]----- */ function repeat_string(str, i) { @@ -1558,5 +1607,10 @@ exports.ast_mangle = ast_mangle; exports.ast_squeeze = ast_squeeze; exports.gen_code = gen_code; exports.ast_add_scope = ast_add_scope; -exports.ast_squeeze_more = require("./squeeze-more").ast_squeeze_more; exports.set_logger = function(logger) { warn = logger }; +exports.make_string = make_string; +exports.split_lines = split_lines; +exports.MAP = MAP; + +// keep this last! +exports.ast_squeeze_more = require("./squeeze-more").ast_squeeze_more; diff --git a/src/ajax.js b/src/ajax.js index 4f8fee6c..a16717b0 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -95,7 +95,7 @@ function addToPrefiltersOrTransports( structure ) { }; } -//Base inspection function for prefilters and transports +// Base inspection function for prefilters and transports function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR, dataType /* internal */, inspected /* internal */ ) { @@ -389,6 +389,7 @@ jQuery.extend({ ifModifiedKey, // Headers (they are sent all at once) requestHeaders = {}, + requestHeadersNames = {}, // Response headers responseHeadersString, responseHeaders, @@ -412,7 +413,9 @@ jQuery.extend({ // Caches the header setRequestHeader: function( name, value ) { if ( !state ) { - requestHeaders[ name.toLowerCase() ] = { n: name, v: value }; + var lname = name.toLowerCase(); + name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name; + requestHeaders[ name ] = value; } return this; }, diff --git a/src/ajax/xhr.js b/src/ajax/xhr.js index ba4c3ec7..a87c3239 100644 --- a/src/ajax/xhr.js +++ b/src/ajax/xhr.js @@ -1,21 +1,14 @@ (function( jQuery ) { -var // #5280: next active xhr id and list of active xhrs' callbacks - xhrId = jQuery.now(), - xhrCallbacks, - - // XHR used to determine supports properties - testXHR; - -// #5280: Internet Explorer will keep connections alive if we don't abort on unload -function xhrOnUnloadAbort() { - jQuery( window ).unload(function() { +var // #5280: Internet Explorer will keep connections alive if we don't abort on unload + xhrOnUnloadAbort = window.ActiveXObject ? function() { // Abort all pending requests for ( var key in xhrCallbacks ) { xhrCallbacks[ key ]( 0, 1 ); } - }); -} + } : false, + xhrId = 0, + xhrCallbacks; // Functions to create xhrs function createStandardXHR() { @@ -45,15 +38,13 @@ jQuery.ajaxSettings.xhr = window.ActiveXObject ? // For all other browsers, use the standard XMLHttpRequest object createStandardXHR; -// Test if we can create an xhr object -testXHR = jQuery.ajaxSettings.xhr(); -jQuery.support.ajax = !!testXHR; - -// Does this browser support crossDomain XHR requests -jQuery.support.cors = testXHR && ( "withCredentials" in testXHR ); - -// No need for the temporary xhr anymore -testXHR = undefined; +// Determine support properties +(function( xhr ) { + jQuery.extend( jQuery.support, { + ajax: !!xhr, + cors: !!xhr && ( "withCredentials" in xhr ) + }); +})( jQuery.ajaxSettings.xhr() ); // Create transport if the browser can provide an xhr if ( jQuery.support.ajax ) { @@ -104,7 +95,7 @@ if ( jQuery.support.ajax ) { // Need an extra try/catch for cross domain requests in Firefox 3 try { for ( i in headers ) { - xhr.setRequestHeader( headers[ i ].n, headers[ i ].v ); + xhr.setRequestHeader( i, headers[ i ] ); } } catch( _ ) {} @@ -136,7 +127,9 @@ if ( jQuery.support.ajax ) { // Do not keep as active anymore if ( handle ) { xhr.onreadystatechange = jQuery.noop; - delete xhrCallbacks[ handle ]; + if ( xhrOnUnloadAbort ) { + delete xhrCallbacks[ handle ]; + } } // If it's an abort @@ -197,15 +190,18 @@ if ( jQuery.support.ajax ) { if ( !s.async || xhr.readyState === 4 ) { callback(); } else { - // Create the active xhrs callbacks list if needed - // and attach the unload handler - if ( !xhrCallbacks ) { - xhrCallbacks = {}; - xhrOnUnloadAbort(); + handle = ++xhrId; + if ( xhrOnUnloadAbort ) { + // Create the active xhrs callbacks list if needed + // and attach the unload handler + if ( !xhrCallbacks ) { + xhrCallbacks = {}; + jQuery( window ).unload( xhrOnUnloadAbort ); + } + // Add to list of active xhrs callbacks + xhrCallbacks[ handle ] = callback; } - // Add to list of active xhrs callbacks - handle = xhrId++; - xhr.onreadystatechange = xhrCallbacks[ handle ] = callback; + xhr.onreadystatechange = callback; } }, diff --git a/src/attributes.js b/src/attributes.js index 374196e1..a296ad11 100644 --- a/src/attributes.js +++ b/src/attributes.js @@ -158,7 +158,7 @@ jQuery.fn.extend({ if ( elem ) { hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ]; - if ( hooks && "get" in hooks && (ret = hooks.get( elem )) !== undefined ) { + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { return ret; } @@ -197,7 +197,7 @@ jQuery.fn.extend({ hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ]; // If set returns undefined, fall back to normal setting - if ( !hooks || ("set" in hooks && hooks.set( this, val ) === undefined) ) { + if ( !hooks || ("set" in hooks && hooks.set( this, val, "value" ) === undefined) ) { this.value = val; } }); @@ -360,6 +360,15 @@ jQuery.extend({ // We can't allow the type property to be changed (since it causes problems in IE) if ( rtype.test( elem.nodeName ) && elem.parentNode ) { jQuery.error( "type property can't be changed" ); + } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to it's default in case type is set after value + var val = elem.getAttribute("value"); + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; } } }, @@ -432,9 +441,11 @@ if ( !jQuery.support.getSetAttribute ) { }); // Use this for any attribute on a form in IE6/7 - // And the name attribute - formHook = jQuery.attrHooks.name = { + formHook = jQuery.attrHooks.name = jQuery.attrHooks.value = jQuery.valHooks.button = { get: function( elem, name ) { + if ( name === "value" && !jQuery.nodeName( elem, "button" ) ) { + return elem.getAttribute( name ); + } var ret = elem.getAttributeNode( name ); // Return undefined if not specified instead of empty string return ret && ret.specified ? @@ -470,7 +481,10 @@ if ( !jQuery.support.getSetAttribute ) { jQuery.each([ "selected", "checked", "readOnly", "disabled" ], function( i, name ) { jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { set: function( elem, value ) { - if ( value === false ) { + if ( value === true ) { + elem.setAttribute( name, name ); + return value; + } else if ( value === false ) { jQuery.removeAttr( elem, name ); return value; } diff --git a/src/core.js b/src/core.js index 89bbde5c..8d812e38 100644 --- a/src/core.js +++ b/src/core.js @@ -731,7 +731,7 @@ jQuery.extend({ } } - // Go thorugh every key on the object, + // Go through every key on the object, } else { for ( key in elems ) { value = callback( elems[ key ], key, arg ); @@ -752,45 +752,23 @@ jQuery.extend({ // Bind a function to a context, optionally partially applying any // arguments. proxy: function( fn, context ) { - var args, proxy; - - // XXX BACKCOMPAT: Support old string method. if ( typeof context === "string" ) { - fn = fn[ context ]; - context = arguments[0]; + var tmp = fn[ context ]; + context = fn; + fn = tmp; } // Quick check to determine if target is callable, in the spec // this throws a TypeError, but we will just return undefined. - if ( ! jQuery.isFunction( fn ) ) { + if ( !jQuery.isFunction( fn ) ) { return undefined; } - if ( jQuery.support.nativeBind ) { - // Native bind - args = slice.call( arguments, 1 ); - if ( args.length ) { - proxy = Function.prototype.bind.apply( fn, args ); - } else { - proxy = fn.bind( context ); - } - } else { - // Simulated bind - args = slice.call( arguments, 2 ); - if ( args.length ) { - proxy = function() { - return arguments.length ? - fn.apply( context, args.concat( slice.call( arguments ) ) ) : - fn.apply( context, args ); - }; - } else { - proxy = function() { - return arguments.length ? - fn.apply( context, arguments ) : - fn.call( context ); - }; - } - } + // Simulated bind + var args = slice.call( arguments, 2 ), + proxy = function() { + return fn.apply( context, args.concat( slice.call( arguments ) ) ); + }; // Set the guid of unique handler to the same of original handler, so it can be removed proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; diff --git a/src/effects.js b/src/effects.js index 2c7b44cb..832ef5da 100644 --- a/src/effects.js +++ b/src/effects.js @@ -28,19 +28,22 @@ jQuery.fn.extend({ } else { for ( var i = 0, j = this.length; i < j; i++ ) { elem = this[i]; - display = elem.style.display; - // Reset the inline display of this element to learn if it is - // being hidden by cascaded rules or not - if ( !jQuery._data(elem, "olddisplay") && display === "none" ) { - display = elem.style.display = ""; - } + if ( elem.style ) { + display = elem.style.display; - // Set elements which have been overridden with display: none - // in a stylesheet to whatever the default browser style is - // for such an element - if ( display === "" && jQuery.css( elem, "display" ) === "none" ) { - jQuery._data(elem, "olddisplay", defaultDisplay(elem.nodeName)); + // Reset the inline display of this element to learn if it is + // being hidden by cascaded rules or not + if ( !jQuery._data(elem, "olddisplay") && display === "none" ) { + display = elem.style.display = ""; + } + + // Set elements which have been overridden with display: none + // in a stylesheet to whatever the default browser style is + // for such an element + if ( display === "" && jQuery.css( elem, "display" ) === "none" ) { + jQuery._data(elem, "olddisplay", defaultDisplay(elem.nodeName)); + } } } @@ -48,10 +51,13 @@ jQuery.fn.extend({ // to avoid the constant reflow for ( i = 0; i < j; i++ ) { elem = this[i]; - display = elem.style.display; - if ( display === "" || display === "none" ) { - elem.style.display = jQuery._data(elem, "olddisplay") || ""; + if ( elem.style ) { + display = elem.style.display; + + if ( display === "" || display === "none" ) { + elem.style.display = jQuery._data(elem, "olddisplay") || ""; + } } } @@ -65,17 +71,21 @@ jQuery.fn.extend({ } else { for ( var i = 0, j = this.length; i < j; i++ ) { - var display = jQuery.css( this[i], "display" ); + if ( this[i].style ) { + var display = jQuery.css( this[i], "display" ); - if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) { - jQuery._data( this[i], "olddisplay", display ); + if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) { + jQuery._data( this[i], "olddisplay", display ); + } } } // Set the display of the elements in a second loop // to avoid the constant reflow for ( i = 0; i < j; i++ ) { - this[i].style.display = "none"; + if ( this[i].style ) { + this[i].style.display = "none"; + } } return this; @@ -165,7 +175,7 @@ jQuery.fn.extend({ this.style.display = "inline-block"; } else { - var display = defaultDisplay(this.nodeName); + display = defaultDisplay(this.nodeName); // inline-level elements accept inline-block; // block-level elements need to be inline with layout @@ -266,6 +276,27 @@ jQuery.fn.extend({ }); +// Animations created synchronously will run synchronously +function createFxNow() { + setTimeout( clearFxNow, 0 ); + return ( fxNow = jQuery.now() ); +} + +function clearFxNow() { + fxNow = undefined; +} + +// Generate parameters to create a standard animation +function genFx( type, num ) { + var obj = {}; + + jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() { + obj[ this ] = type; + }); + + return obj; +} + // Generate shortcuts for custom animations jQuery.each({ slideDown: genFx("show", 1), @@ -581,25 +612,4 @@ function defaultDisplay( nodeName ) { return elemdisplay[ nodeName ]; } -// Animations created synchronously will run synchronously -function createFxNow() { - setTimeout( clearFxNow, 0 ); - return ( fxNow = jQuery.now() ); -} - -function clearFxNow() { - fxNow = undefined; -} - -// Generate parameters to create a standard animation -function genFx( type, num ) { - var obj = {}; - - jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() { - obj[ this ] = type; - }); - - return obj; -} - })( jQuery ); diff --git a/src/event.js b/src/event.js index 0938be8b..d20c6f2d 100644 --- a/src/event.js +++ b/src/event.js @@ -63,7 +63,7 @@ jQuery.event = { elemData.handle = eventHandle = function( e ) { // Discard the second event of a jQuery.event.trigger() and // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.handle.apply( eventHandle.elem, arguments ) : undefined; }; @@ -910,7 +910,7 @@ jQuery.each(["bind", "one"], function( i, name ) { return this; } - if ( jQuery.isFunction( data ) || data === false ) { + if ( arguments.length === 2 || data === false ) { fn = data; data = undefined; } @@ -1063,7 +1063,7 @@ jQuery.each(["live", "die"], function( i, name ) { preType = type; - if ( type === "focus" || type === "blur" ) { + if ( liveMap[ type ] ) { types.push( liveMap[ type ] + namespaces ); type = type + namespaces; @@ -1134,6 +1134,11 @@ function liveHandler( event ) { if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { event.type = handleObj.preType; related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; + + // Make sure not to accidentally match a child element with the same selector + if ( related && jQuery.contains( elem, related ) ) { + related = elem; + } } if ( !related || related !== elem ) { diff --git a/src/manipulation.js b/src/manipulation.js index f519b4d3..610d19b7 100644 --- a/src/manipulation.js +++ b/src/manipulation.js @@ -70,7 +70,7 @@ jQuery.fn.extend({ } return elem; - }).append(this); + }).append( this ); } return this; @@ -379,13 +379,13 @@ function cloneCopyEvent( src, dest ) { } function cloneFixAttributes( src, dest ) { + var nodeName; + // We do not need to do anything for non-Elements if ( dest.nodeType !== 1 ) { return; } - var nodeName = dest.nodeName.toLowerCase(); - // clearAttributes removes the attributes, which we don't want, // but also removes the attachEvent events, which we *do* want if ( dest.clearAttributes ) { @@ -398,6 +398,8 @@ function cloneFixAttributes( src, dest ) { dest.mergeAttributes( src ); } + nodeName = dest.nodeName.toLowerCase(); + // IE6-8 fail to clone children inside object elements that use // the proprietary classid attribute value (rather than the type // attribute) to identify the type of content to display @@ -446,11 +448,10 @@ jQuery.buildFragment = function( args, nodes, scripts ) { args[0].charAt(0) === "<" && !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) { cacheable = true; + cacheresults = jQuery.fragments[ args[0] ]; - if ( cacheresults ) { - if ( cacheresults !== 1 ) { - fragment = cacheresults; - } + if ( cacheresults && cacheresults !== 1 ) { + fragment = cacheresults; } } @@ -508,6 +509,21 @@ function getAll( elem ) { } } +// Used in clean, fixes the defaultChecked property +function fixDefaultChecked( elem ) { + if ( elem.type === "checkbox" || elem.type === "radio" ) { + elem.defaultChecked = elem.checked; + } +} +// Finds all inputs and passes them to fixDefaultChecked +function findInputs( elem ) { + if ( jQuery.nodeName( elem, "input" ) ) { + fixDefaultChecked( elem ); + } else if ( elem.getElementsByTagName ) { + jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked ); + } +} + jQuery.extend({ clone: function( elem, dataAndEvents, deepDataAndEvents ) { var clone = elem.cloneNode(true), @@ -578,54 +594,67 @@ jQuery.extend({ } // Convert html string into DOM nodes - if ( typeof elem === "string" && !rhtml.test( elem ) ) { - elem = context.createTextNode( elem ); + if ( typeof elem === "string" ) { + if ( !rhtml.test( elem ) ) { + elem = context.createTextNode( elem ); + } else { + // Fix "XHTML"-style tags in all browsers + elem = elem.replace(rxhtmlTag, "<$1>"); - } else if ( typeof elem === "string" ) { - // Fix "XHTML"-style tags in all browsers - elem = elem.replace(rxhtmlTag, "<$1>"); + // Trim whitespace, otherwise indexOf won't work as expected + var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(), + wrap = wrapMap[ tag ] || wrapMap._default, + depth = wrap[0], + div = context.createElement("div"); - // Trim whitespace, otherwise indexOf won't work as expected - var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(), - wrap = wrapMap[ tag ] || wrapMap._default, - depth = wrap[0], - div = context.createElement("div"); + // Go to html and back, then peel off extra wrappers + div.innerHTML = wrap[1] + elem + wrap[2]; - // Go to html and back, then peel off extra wrappers - div.innerHTML = wrap[1] + elem + wrap[2]; + // Move to the right depth + while ( depth-- ) { + div = div.lastChild; + } - // Move to the right depth - while ( depth-- ) { - div = div.lastChild; - } + // Remove IE's autoinserted from table fragments + if ( !jQuery.support.tbody ) { - // Remove IE's autoinserted from table fragments - if ( !jQuery.support.tbody ) { + // String was a , *may* have spurious + var hasBody = rtbody.test(elem), + tbody = tag === "table" && !hasBody ? + div.firstChild && div.firstChild.childNodes : - // String was a
, *may* have spurious - var hasBody = rtbody.test(elem), - tbody = tag === "table" && !hasBody ? - div.firstChild && div.firstChild.childNodes : + // String was a bare or + wrap[1] === "
" && !hasBody ? + div.childNodes : + []; - // String was a bare or - wrap[1] === "
" && !hasBody ? - div.childNodes : - []; - - for ( var j = tbody.length - 1; j >= 0 ; --j ) { - if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) { - tbody[ j ].parentNode.removeChild( tbody[ j ] ); + for ( var j = tbody.length - 1; j >= 0 ; --j ) { + if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) { + tbody[ j ].parentNode.removeChild( tbody[ j ] ); + } } } - } + // IE completely kills leading whitespace when innerHTML is used + if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild ); + } - // IE completely kills leading whitespace when innerHTML is used - if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { - div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild ); + elem = div.childNodes; } + } - elem = div.childNodes; + // Resets defaultChecked for any radios and checkboxes + // about to be appended to the DOM in IE 6/7 (#8060) + var len; + if ( !jQuery.support.appendChecked ) { + if ( elem[0] && typeof (len = elem.length) === "number" ) { + for ( i = 0; i < len; i++ ) { + findInputs( elem[i] ); + } + } else { + findInputs( elem ); + } } if ( elem.nodeType ) { diff --git a/src/sizzle b/src/sizzle index 69ecd019..4bcc0970 160000 --- a/src/sizzle +++ b/src/sizzle @@ -1 +1 @@ -Subproject commit 69ecd019852c1421cbd81fe6ceb4e22a81022ea7 +Subproject commit 4bcc09702d6dadfd0b90c7de3c8b206e97ff97f4 diff --git a/src/support.js b/src/support.js index 1bd35cab..8f2beebb 100644 --- a/src/support.js +++ b/src/support.js @@ -77,10 +77,6 @@ jQuery.support = (function() { // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) getSetAttribute: div.className !== "t", - // Test for presence of native Function#bind. - // Not in: >= Chrome 6, >= FireFox 3, Safari 5?, IE 9?, Opera 11? - nativeBind: jQuery.isFunction( Function.prototype.bind ), - // Will be defined later submitBubbles: true, changeBubbles: true, @@ -119,6 +115,11 @@ jQuery.support = (function() { div.cloneNode( true ).fireEvent( "onclick" ); } + input = document.createElement("input"); + input.value = "t"; + input.setAttribute("type", "radio"); + support.radioValue = input.value === "t"; + div.innerHTML = ""; fragment = document.createDocumentFragment(); @@ -185,6 +186,14 @@ jQuery.support = (function() { support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); div.innerHTML = ""; + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM + input = document.createElement("input"); + input.setAttribute("type", "checkbox"); + input.checked = true; + div.appendChild( input ); + support.appendChecked = input.checked; + // Check if div with explicit width and no margin-right incorrectly // gets computed margin-right based on width of container. For more // info see bug #3333 diff --git a/src/traversing.js b/src/traversing.js index e0f40151..8c4b4ef8 100644 --- a/src/traversing.js +++ b/src/traversing.js @@ -160,7 +160,7 @@ jQuery.fn.extend({ add: function( selector, context ) { var set = typeof selector === "string" ? jQuery( selector, context ) : - jQuery.makeArray( selector ), + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), all = jQuery.merge( this.get(), set ); return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? diff --git a/test/index.html b/test/index.html index a1065508..b19673d9 100644 --- a/test/index.html +++ b/test/index.html @@ -58,10 +58,10 @@
- + -
-
+
+

See this blog entry for more information.

Here are some links in a normal paragraph: Google, diff --git a/test/qunit b/test/qunit index d404faf8..98876633 160000 --- a/test/qunit +++ b/test/qunit @@ -1 +1 @@ -Subproject commit d404faf8f587fcbe6b8907943022e6318dd51e0c +Subproject commit 9887663380693009874e8c76f0bf77a921931766 diff --git a/test/unit/ajax.js b/test/unit/ajax.js index a8a5fa0d..9f084136 100644 --- a/test/unit/ajax.js +++ b/test/unit/ajax.js @@ -1782,7 +1782,7 @@ test("jQuery.ajaxSetup({timeout: Number}) - with global timeout", function() { passed++; if ( passed == 2 ) { ok( true, "Check local and global callbacks after timeout" ); - jQuery("#main").unbind("ajaxError"); + jQuery("#qunit-fixture").unbind("ajaxError"); start(); } }; @@ -1792,7 +1792,7 @@ test("jQuery.ajaxSetup({timeout: Number}) - with global timeout", function() { start(); }; - jQuery("#main").ajaxError(pass); + jQuery("#qunit-fixture").ajaxError(pass); jQuery.ajax({ type: "GET", diff --git a/test/unit/attributes.js b/test/unit/attributes.js index 8ea27aa3..0889bf33 100644 --- a/test/unit/attributes.js +++ b/test/unit/attributes.js @@ -77,7 +77,7 @@ test("prop(String, Object)", function() { }); test("attr(String)", function() { - expect(32); + expect(35); equals( jQuery("#text1").attr("type"), "text", "Check for type attribute" ); equals( jQuery("#radio1").attr("type"), "radio", "Check for type attribute" ); @@ -90,6 +90,7 @@ test("attr(String)", function() { equals( jQuery("#name").attr("name"), "name", "Check for name attribute" ); equals( jQuery("#text1").attr("name"), "action", "Check for name attribute" ); ok( jQuery("#form").attr("action").indexOf("formaction") >= 0, "Check for action attribute" ); + equals( jQuery("#text1").attr("value", "t").attr("value"), "t", "Check setting the value attribute" ); equals( jQuery("#form").attr("blah", "blah").attr("blah"), "blah", "Set non-existant attribute on a form" ); equals( jQuery("#foo").attr("height"), undefined, "Non existent height attribute should return undefined" ); @@ -108,7 +109,7 @@ test("attr(String)", function() { equals( jQuery("#area1").attr("maxLength"), "30", "Check for maxLength attribute" ); // using innerHTML in IE causes href attribute to be serialized to the full path - jQuery("").attr({ "id": "tAnchor5", "href": "#5" }).appendTo("#main"); + jQuery("").attr({ "id": "tAnchor5", "href": "#5" }).appendTo("#qunit-fixture"); equals( jQuery("#tAnchor5").attr("href"), "#5", "Check for non-absolute href (an anchor)" ); // list attribute is readonly by default in browsers that support it @@ -136,6 +137,11 @@ test("attr(String)", function() { ok( !!~jQuery("#dl").attr("style").indexOf("position"), "Check style attribute getter, also normalize css props to lowercase" ); ok( !!~jQuery("#foo").attr("style", "position:absolute;").attr("style").indexOf("position"), "Check style setter" ); + // Check value on button element (#1954) + var $button = jQuery("").insertAfter("#button"); + equals( $button.attr("value"), "foobar", "Value retrieval on a button does not return innerHTML" ); + equals( $button.attr("value", "baz").html(), "text", "Setting the value does not change innerHTML" ); + ok( jQuery("

").attr("doesntexist") === undefined, "Make sure undefined is returned when no attribute is found." ); ok( jQuery().attr("doesntexist") === undefined, "Make sure undefined is returned when no element is there." ); }); @@ -171,7 +177,7 @@ test("attr(Hash)", function() { }); test("attr(String, Object)", function() { - expect(29); + expect(30); var div = jQuery("div").attr("foo", "bar"), fail = false; @@ -284,7 +290,10 @@ test("attr(String, Object)", function() { } ok( thrown, "Exception thrown when trying to change type property" ); equals( "button", button.attr("type"), "Verify that you can't change the type of a button element" ); - + + var $radio = jQuery("", { "value": "sup", "type": "radio" }).appendTo("#testForm"); + equals( $radio.val(), "sup", "Value is not reset when type is set after value on a radio" ); + // Setting attributes on svg elements (bug #3116) var $svg = jQuery("" + "" @@ -426,7 +435,7 @@ test("removeProp(String)", function() { }); test("val()", function() { - expect(23); + expect(25); document.getElementById("text1").value = "bla"; equals( jQuery("#text1").val(), "bla", "Check for modified value of input element" ); @@ -488,6 +497,10 @@ test("val()", function() { same( checks.serialize(), "test=1&test=on", "Get multiple checked values." ); checks.remove(); + + var $button = jQuery("").insertAfter("#button"); + equals( $button.val(), "foobar", "Value retrieval on a button does not return innerHTML" ); + equals( $button.val("baz").html(), "text", "Setting the value does not change innerHTML" ); }); var testVal = function(valueObj) { @@ -547,6 +560,7 @@ test( "val(Array of Numbers) (Bug #7123)", function() { test("val(Function) with incoming value", function() { expect(10); + QUnit.reset(); var oldVal = jQuery("#text1").val(); jQuery("#text1").val(function(i, val) { @@ -599,7 +613,7 @@ test("val(Function) with incoming value", function() { test("val(select) after form.reset() (Bug #2551)", function() { expect(3); - jQuery("
").appendTo("#main"); + jQuery("
").appendTo("#qunit-fixture"); jQuery("#kkk").val( "gf" ); diff --git a/test/unit/core.js b/test/unit/core.js index 8d29575a..f0bf24cd 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -29,7 +29,7 @@ test("jQuery()", function() { equals( jQuery(window).length, 1, "Correct number of elements generated for jQuery(window)" ); - var main = jQuery("#main"); + var main = jQuery("#qunit-fixture"); same( jQuery("div p", main).get(), q("sndp", "en", "sap"), "Basic selector with jQuery object as context" ); /* @@ -115,54 +115,54 @@ test("selector state", function() { equals( test.selector, "", "Body Selector" ); equals( test.context, document.body, "Body Context" ); - test = jQuery("#main"); - equals( test.selector, "#main", "#main Selector" ); - equals( test.context, document, "#main Context" ); + test = jQuery("#qunit-fixture"); + equals( test.selector, "#qunit-fixture", "#qunit-fixture Selector" ); + equals( test.context, document, "#qunit-fixture Context" ); test = jQuery("#notfoundnono"); equals( test.selector, "#notfoundnono", "#notfoundnono Selector" ); equals( test.context, document, "#notfoundnono Context" ); - test = jQuery("#main", document); - equals( test.selector, "#main", "#main Selector" ); - equals( test.context, document, "#main Context" ); + test = jQuery("#qunit-fixture", document); + equals( test.selector, "#qunit-fixture", "#qunit-fixture Selector" ); + equals( test.context, document, "#qunit-fixture Context" ); - test = jQuery("#main", document.body); - equals( test.selector, "#main", "#main Selector" ); - equals( test.context, document.body, "#main Context" ); + test = jQuery("#qunit-fixture", document.body); + equals( test.selector, "#qunit-fixture", "#qunit-fixture Selector" ); + equals( test.context, document.body, "#qunit-fixture Context" ); // Test cloning test = jQuery(test); - equals( test.selector, "#main", "#main Selector" ); - equals( test.context, document.body, "#main Context" ); + equals( test.selector, "#qunit-fixture", "#qunit-fixture Selector" ); + equals( test.context, document.body, "#qunit-fixture Context" ); - test = jQuery(document.body).find("#main"); - equals( test.selector, "#main", "#main find Selector" ); - equals( test.context, document.body, "#main find Context" ); + test = jQuery(document.body).find("#qunit-fixture"); + equals( test.selector, "#qunit-fixture", "#qunit-fixture find Selector" ); + equals( test.context, document.body, "#qunit-fixture find Context" ); - test = jQuery("#main").filter("div"); - equals( test.selector, "#main.filter(div)", "#main filter Selector" ); - equals( test.context, document, "#main filter Context" ); + test = jQuery("#qunit-fixture").filter("div"); + equals( test.selector, "#qunit-fixture.filter(div)", "#qunit-fixture filter Selector" ); + equals( test.context, document, "#qunit-fixture filter Context" ); - test = jQuery("#main").not("div"); - equals( test.selector, "#main.not(div)", "#main not Selector" ); - equals( test.context, document, "#main not Context" ); + test = jQuery("#qunit-fixture").not("div"); + equals( test.selector, "#qunit-fixture.not(div)", "#qunit-fixture not Selector" ); + equals( test.context, document, "#qunit-fixture not Context" ); - test = jQuery("#main").filter("div").not("div"); - equals( test.selector, "#main.filter(div).not(div)", "#main filter, not Selector" ); - equals( test.context, document, "#main filter, not Context" ); + test = jQuery("#qunit-fixture").filter("div").not("div"); + equals( test.selector, "#qunit-fixture.filter(div).not(div)", "#qunit-fixture filter, not Selector" ); + equals( test.context, document, "#qunit-fixture filter, not Context" ); - test = jQuery("#main").filter("div").not("div").end(); - equals( test.selector, "#main.filter(div)", "#main filter, not, end Selector" ); - equals( test.context, document, "#main filter, not, end Context" ); + test = jQuery("#qunit-fixture").filter("div").not("div").end(); + equals( test.selector, "#qunit-fixture.filter(div)", "#qunit-fixture filter, not, end Selector" ); + equals( test.context, document, "#qunit-fixture filter, not, end Context" ); - test = jQuery("#main").parent("body"); - equals( test.selector, "#main.parent(body)", "#main parent Selector" ); - equals( test.context, document, "#main parent Context" ); + test = jQuery("#qunit-fixture").parent("body"); + equals( test.selector, "#qunit-fixture.parent(body)", "#qunit-fixture parent Selector" ); + equals( test.context, document, "#qunit-fixture parent Context" ); - test = jQuery("#main").eq(0); - equals( test.selector, "#main.slice(0,1)", "#main eq Selector" ); - equals( test.context, document, "#main eq Context" ); + test = jQuery("#qunit-fixture").eq(0); + equals( test.selector, "#qunit-fixture.slice(0,1)", "#qunit-fixture eq Selector" ); + equals( test.context, document, "#qunit-fixture eq Context" ); var d = "
"; equals( @@ -228,7 +228,7 @@ test("noConflict", function() { equals( jQuery.noConflict(true), $$, "noConflict returned the jQuery object" ); equals( jQuery, originaljQuery, "Make sure jQuery was reverted." ); equals( $, original$, "Make sure $ was reverted." ); - ok( $$("#main").html("test"), "Make sure that jQuery still works." ); + ok( $$("#qunit-fixture").html("test"), "Make sure that jQuery still works." ); jQuery = $$; }); @@ -571,29 +571,29 @@ test("end()", function() { test("length", function() { expect(1); - equals( jQuery("#main p").length, 6, "Get Number of Elements Found" ); + equals( jQuery("#qunit-fixture p").length, 6, "Get Number of Elements Found" ); }); test("size()", function() { expect(1); - equals( jQuery("#main p").size(), 6, "Get Number of Elements Found" ); + equals( jQuery("#qunit-fixture p").size(), 6, "Get Number of Elements Found" ); }); test("get()", function() { expect(1); - same( jQuery("#main p").get(), q("firstp","ap","sndp","en","sap","first"), "Get All Elements" ); + same( jQuery("#qunit-fixture p").get(), q("firstp","ap","sndp","en","sap","first"), "Get All Elements" ); }); test("toArray()", function() { expect(1); - same( jQuery("#main p").toArray(), + same( jQuery("#qunit-fixture p").toArray(), q("firstp","ap","sndp","en","sap","first"), "Convert jQuery object to an Array" ) }) test("get(Number)", function() { expect(2); - equals( jQuery("#main p").get(0), document.getElementById("firstp"), "Get A Single Element" ); + equals( jQuery("#qunit-fixture p").get(0), document.getElementById("firstp"), "Get A Single Element" ); strictEqual( jQuery("#firstp").get(1), undefined, "Try get with index larger elements count" ); }); @@ -910,7 +910,7 @@ test("jQuery.isEmptyObject", function(){ }); test("jQuery.proxy", function(){ - expect(6); + expect(7); var test = function(){ equals( this, thisObject, "Make sure that scope is set properly." ); }; var thisObject = { foo: "bar", method: test }; @@ -921,6 +921,9 @@ test("jQuery.proxy", function(){ // Basic scoping jQuery.proxy( test, thisObject )(); + // Another take on it + jQuery.proxy( thisObject, "method" )(); + // Make sure it doesn't freak out equals( jQuery.proxy( null, thisObject ), undefined, "Make sure no function was returned." ); diff --git a/test/unit/css.js b/test/unit/css.js index 33bc1548..60e8744c 100644 --- a/test/unit/css.js +++ b/test/unit/css.js @@ -3,7 +3,7 @@ module("css", { teardown: moduleTeardown }); test("css(String|Hash)", function() { expect( 42 ); - equals( jQuery("#main").css("display"), "block", "Check for css property \"display\""); + equals( jQuery("#qunit-fixture").css("display"), "block", "Check for css property \"display\""); ok( jQuery("#nothiddendiv").is(":visible"), "Modifying CSS display: Assert element is visible"); jQuery("#nothiddendiv").css({display: "none"}); diff --git a/test/unit/data.js b/test/unit/data.js index 888f71cb..8b5ce961 100644 --- a/test/unit/data.js +++ b/test/unit/data.js @@ -316,7 +316,7 @@ test("data-* attributes", function() { div.remove(); - child.appendTo("#main"); + child.appendTo("#qunit-fixture"); equals( child.data("myobj"), "old data", "Value accessed from data-* attribute"); child.data("myobj", "replaced"); @@ -406,7 +406,7 @@ test("data-* attributes", function() { } var metadata = "
  1. Some stuff
  2. Some stuff
  3. Some stuff
  4. Some stuff
", - elem = jQuery(metadata).appendTo("#main"); + elem = jQuery(metadata).appendTo("#qunit-fixture"); elem.find("li").each(testData); elem.remove(); diff --git a/test/unit/effects.js b/test/unit/effects.js index 8b7cf467..d0518217 100644 --- a/test/unit/effects.js +++ b/test/unit/effects.js @@ -2,7 +2,7 @@ module("effects", { teardown: moduleTeardown }); test("sanity check", function() { expect(1); - ok( jQuery("#dl:visible, #main:visible, #foo:visible").length === 3, "QUnit state is correct for testing effects" ); + ok( jQuery("#dl:visible, #qunit-fixture:visible, #foo:visible").length === 3, "QUnit state is correct for testing effects" ); }); test("show()", function() { @@ -14,7 +14,7 @@ test("show()", function() { equals( hiddendiv.css("display"), "block", "Make sure a pre-hidden div is visible." ); - var div = jQuery("
"); + jQuery("#qunit-fixture").append("

"); var old = jQuery("#test-table").show().css("display") !== "table"; jQuery("#test-table").remove(); @@ -88,6 +88,10 @@ test("show()", function() { var elem = jQuery(selector, "#show-tests").show(); equals( elem.css("display"), expected, "Show using correct display type for " + selector ); }); + + // Make sure that showing or hiding a text node doesn't cause an error + jQuery("
test
text test").show().remove(); + jQuery("
test
text test").hide().remove(); }); test("show(Number) - other displays", function() { @@ -96,7 +100,7 @@ test("show(Number) - other displays", function() { stop(); // #show-tests * is set display: none in CSS - jQuery("#main").append("

"); + jQuery("#qunit-fixture").append("

"); var old = jQuery("#test-table").show().css("display") !== "table", num = 0; @@ -138,7 +142,7 @@ test("Persist correct display value", function() { stop(); // #show-tests * is set display: none in CSS - jQuery("#main").append("
foo
"); + jQuery("#qunit-fixture").append("
foo
"); var $span = jQuery("#show-tests span"), displayNone = $span.css("display"), @@ -581,7 +585,7 @@ jQuery.checkOverflowDisplay = function(){ } test( "jQuery.fx.prototype.cur()", 6, function() { - var div = jQuery( "
" ).appendTo( "#main" ).css({ + var div = jQuery( "
" ).appendTo( "#qunit-fixture" ).css({ color: "#ABC", border: "5px solid black", left: "auto", @@ -954,7 +958,7 @@ test("hide hidden elements (bug #7141)", function() { expect(3); QUnit.reset(); - var div = jQuery("
").appendTo("#main"); + var div = jQuery("
").appendTo("#qunit-fixture"); equals( div.css("display"), "none", "Element is hidden by default" ); div.hide(); ok( !jQuery._data(div, "olddisplay"), "olddisplay is undefined after hiding an already-hidden element" ); @@ -969,7 +973,7 @@ test("hide hidden elements, with animation (bug #7141)", function() { QUnit.reset(); stop(); - var div = jQuery("
").appendTo("#main"); + var div = jQuery("
").appendTo("#qunit-fixture"); equals( div.css("display"), "none", "Element is hidden by default" ); div.hide(1, function () { ok( !jQuery._data(div, "olddisplay"), "olddisplay is undefined after hiding an already-hidden element" ); @@ -982,7 +986,7 @@ test("hide hidden elements, with animation (bug #7141)", function() { test("animate unit-less properties (#4966)", 2, function() { stop(); - var div = jQuery( "
" ).appendTo( "#main" ); + var div = jQuery( "
" ).appendTo( "#qunit-fixture" ); equal( div.css( "z-index" ), "0", "z-index is 0" ); div.animate({ zIndex: 2 }, function() { equal( div.css( "z-index" ), "2", "z-index is 2" ); diff --git a/test/unit/event.js b/test/unit/event.js index 1710f6f9..0424538d 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -15,7 +15,7 @@ test("null or undefined handler", function() { }); test("bind(), with data", function() { - expect(3); + expect(4); var handler = function(event) { ok( event.data, "bind() with data, check passed data exists" ); equals( event.data.foo, "bar", "bind() with data, Check value of passed data" ); @@ -23,6 +23,12 @@ test("bind(), with data", function() { jQuery("#firstp").bind("click", {foo: "bar"}, handler).click().unbind("click", handler); ok( !jQuery._data(jQuery("#firstp")[0], "events"), "Event handler unbound when using data." ); + + var test = function(){}; + var handler2 = function(event) { + equals( event.data, test, "bind() with function data, Check value of passed data" ); + }; + jQuery("#firstp").bind("click", test, handler2).click().unbind("click", handler2); }); test("click(), with data", function() { @@ -163,7 +169,7 @@ test("bind(), namespace with special add", function() { }); // Should trigger 2 - div.appendTo("#main").remove(); + div.appendTo("#qunit-fixture").remove(); delete jQuery.event.special.test; }); @@ -301,7 +307,7 @@ test("live/delegate immediate propagation", function() { test("bind/delegate bubbling, isDefaultPrevented", function() { expect(2); var $anchor2 = jQuery( "#anchor2" ), - $main = jQuery( "#main" ), + $main = jQuery( "#qunit-fixture" ), fakeClick = function($jq) { // Use a native click so we don't get jQuery simulated bubbling if ( document.createEvent ) { @@ -409,7 +415,7 @@ test("bind(), namespaced events, cloned events", 18, function() { }).trigger("tester"); // Make sure events stick with appendTo'd elements (which are cloned) #2027 - jQuery("test").click(function(){ return false; }).appendTo("#main"); + jQuery("test").click(function(){ return false; }).appendTo("#qunit-fixture"); ok( jQuery("a.test:first").triggerHandler("click") === false, "Handler is bound to appendTo'd elements" ); }); @@ -532,7 +538,7 @@ test("bind(name, false), unbind(name, false)", function() { expect(3); var main = 0; - jQuery("#main").bind("click", function(e){ main++; }); + jQuery("#qunit-fixture").bind("click", function(e){ main++; }); jQuery("#ap").trigger("click"); equals( main, 1, "Verify that the trigger happened correctly." ); @@ -547,14 +553,14 @@ test("bind(name, false), unbind(name, false)", function() { equals( main, 1, "Verify that the trigger happened correctly." ); // manually clean up events from elements outside the fixture - jQuery("#main").unbind("click"); + jQuery("#qunit-fixture").unbind("click"); }); test("live(name, false), die(name, false)", function() { expect(3); var main = 0; - jQuery("#main").live("click", function(e){ main++; }); + jQuery("#qunit-fixture").live("click", function(e){ main++; }); jQuery("#ap").trigger("click"); equals( main, 1, "Verify that the trigger happened correctly." ); @@ -567,7 +573,7 @@ test("live(name, false), die(name, false)", function() { jQuery("#ap").die("click", false); jQuery("#ap").trigger("click"); equals( main, 1, "Verify that the trigger happened correctly." ); - jQuery("#main").die("click"); + jQuery("#qunit-fixture").die("click"); }); test("delegate(selector, name, false), undelegate(selector, name, false)", function() { @@ -575,7 +581,7 @@ test("delegate(selector, name, false), undelegate(selector, name, false)", funct var main = 0; - jQuery("#main").delegate("#ap", "click", function(e){ main++; }); + jQuery("#qunit-fixture").delegate("#ap", "click", function(e){ main++; }); jQuery("#ap").trigger("click"); equals( main, 1, "Verify that the trigger happened correctly." ); @@ -588,7 +594,7 @@ test("delegate(selector, name, false), undelegate(selector, name, false)", funct jQuery("#ap").undelegate("#groups", "click", false); jQuery("#groups").trigger("click"); equals( main, 1, "Verify that the trigger happened correctly." ); - jQuery("#main").undelegate("#ap", "click"); + jQuery("#qunit-fixture").undelegate("#ap", "click"); }); test("bind()/trigger()/unbind() on plain object", function() { @@ -792,7 +798,7 @@ test("trigger() bubbling", function() { jQuery(document).bind("click", function(e){ if ( e.target !== document) { doc++; } }); jQuery("html").bind("click", function(e){ html++; }); jQuery("body").bind("click", function(e){ body++; }); - jQuery("#main").bind("click", function(e){ main++; }); + jQuery("#qunit-fixture").bind("click", function(e){ main++; }); jQuery("#ap").bind("click", function(){ ap++; return false; }); jQuery("html").trigger("click"); @@ -806,7 +812,7 @@ test("trigger() bubbling", function() { equals( html, 2, "Body bubble" ); equals( body, 1, "Body bubble" ); - jQuery("#main").trigger("click"); + jQuery("#qunit-fixture").trigger("click"); equals( win, 3, "Main bubble" ); equals( doc, 3, "Main bubble" ); equals( html, 3, "Main bubble" ); @@ -822,11 +828,11 @@ test("trigger() bubbling", function() { // manually clean up events from elements outside the fixture jQuery(document).unbind("click"); - jQuery("html, body, #main").unbind("click"); + jQuery("html, body, #qunit-fixture").unbind("click"); }); test("trigger(type, [data], [fn])", function() { - expect(14); + expect(16); var handler = function(event, a, b, c) { equals( event.type, "click", "check passed data" ); @@ -843,7 +849,24 @@ test("trigger(type, [data], [fn])", function() { ok( true, "Native call was triggered" ); }; - // Triggers handlrs and native + + $elem.live('mouseenter', function(){ + ok( true, 'Trigger mouseenter bound by live' ); + }); + + $elem.live('mouseleave', function(){ + ok( true, 'Trigger mouseleave bound by live' ); + }); + + $elem.trigger('mouseenter'); + + $elem.trigger('mouseleave'); + + $elem.die('mouseenter'); + + $elem.die('mouseleave'); + + // Triggers handlrs and native // Trigger 5 $elem.bind("click", handler).trigger("click", [1, "2", "abc"]); @@ -866,7 +889,7 @@ test("trigger(type, [data], [fn])", function() { pass = true; try { - jQuery("#main table:first").bind("test:test", function(){}).trigger("test:test"); + jQuery("#qunit-fixture table:first").bind("test:test", function(){}).trigger("test:test"); } catch (e) { pass = false; } @@ -1183,11 +1206,11 @@ test(".live()/.die()", function() { jQuery("div").die("submit"); // Test binding with a different context - var clicked = 0, container = jQuery("#main")[0]; + var clicked = 0, container = jQuery("#qunit-fixture")[0]; jQuery("#foo", container).live("click", function(e){ clicked++; }); jQuery("div").trigger("click"); jQuery("#foo").trigger("click"); - jQuery("#main").trigger("click"); + jQuery("#qunit-fixture").trigger("click"); jQuery("body").trigger("click"); equals( clicked, 2, "live with a context" ); @@ -1710,11 +1733,11 @@ test(".delegate()/.undelegate()", function() { jQuery("#body").undelegate("div", "submit"); // Test binding with a different context - var clicked = 0, container = jQuery("#main")[0]; - jQuery("#main").delegate("#foo", "click", function(e){ clicked++; }); + var clicked = 0, container = jQuery("#qunit-fixture")[0]; + jQuery("#qunit-fixture").delegate("#foo", "click", function(e){ clicked++; }); jQuery("div").trigger("click"); jQuery("#foo").trigger("click"); - jQuery("#main").trigger("click"); + jQuery("#qunit-fixture").trigger("click"); jQuery("body").trigger("click"); equals( clicked, 2, "delegate with a context" ); @@ -1722,7 +1745,7 @@ test(".delegate()/.undelegate()", function() { ok( jQuery._data(container, "events").live, "delegate with a context" ); // Test unbinding with a different context - jQuery("#main").undelegate("#foo", "click"); + jQuery("#qunit-fixture").undelegate("#foo", "click"); jQuery("#foo").trigger("click"); equals( clicked, 2, "undelegate with a context"); diff --git a/test/unit/manipulation.js b/test/unit/manipulation.js index de65daa1..b71b6962 100644 --- a/test/unit/manipulation.js +++ b/test/unit/manipulation.js @@ -227,7 +227,7 @@ test("unwrap()", function() { }); var testAppend = function(valueObj) { - expect(37); + expect(40); var defaultText = "Try them out:" var result = jQuery("#first").append(valueObj("buga")); equals( result.text(), defaultText + "buga", "Check if text appending works" ); @@ -330,6 +330,20 @@ var testAppend = function(valueObj) { d.contents().appendTo("#nonnodes"); d.remove(); ok( jQuery("#nonnodes").contents().length >= 2, "Check node,textnode,comment append cleanup worked" ); + + QUnit.reset(); + var $input = jQuery("").attr({ "type": "checkbox", "checked": true }).appendTo('#testForm'); + equals( $input[0].checked, true, "A checked checkbox that is appended stays checked" ); + + QUnit.reset(); + var $radios = jQuery("input:radio[name='R1']"), + $radioNot = jQuery("").insertAfter( $radios ), + $radio = $radios.eq(1).click(); + $radioNot[0].checked = false; + $radios.parent().wrap("
"); + equals( $radio[0].checked, true, "Reappending radios uphold which radio is checked" ); + equals( $radioNot[0].checked, false, "Reappending radios uphold not being checked" ); + QUnit.reset(); } test("append(String|Element|Array<Element>|jQuery)", function() { @@ -483,19 +497,19 @@ test("appendTo(String|Element|Array<Element>|jQuery)", function() { var div = jQuery("
").click(function(){ ok(true, "Running a cloned click."); }); - div.appendTo("#main, #moretests"); + div.appendTo("#qunit-fixture, #moretests"); - jQuery("#main div:last").click(); + jQuery("#qunit-fixture div:last").click(); jQuery("#moretests div:last").click(); QUnit.reset(); - var div = jQuery("
").appendTo("#main, #moretests"); + var div = jQuery("
").appendTo("#qunit-fixture, #moretests"); equals( div.length, 2, "appendTo returns the inserted elements" ); div.addClass("test"); - ok( jQuery("#main div:last").hasClass("test"), "appendTo element was modified after the insertion" ); + ok( jQuery("#qunit-fixture div:last").hasClass("test"), "appendTo element was modified after the insertion" ); ok( jQuery("#moretests div:last").hasClass("test"), "appendTo element was modified after the insertion" ); QUnit.reset(); @@ -507,10 +521,10 @@ test("appendTo(String|Element|Array<Element>|jQuery)", function() { div = jQuery("#moretests div"); - var num = jQuery("#main div").length; - div.remove().appendTo("#main"); + var num = jQuery("#qunit-fixture div").length; + div.remove().appendTo("#qunit-fixture"); - equals( jQuery("#main div").length, num, "Make sure all the removed divs were inserted." ); + equals( jQuery("#qunit-fixture div").length, num, "Make sure all the removed divs were inserted." ); QUnit.reset(); }); @@ -750,7 +764,7 @@ var testReplaceWith = function(val) { ok( !jQuery("#yahoo")[0], "Verify that original element is gone, after element" ); QUnit.reset(); - jQuery("#main").append("
"); + jQuery("#qunit-fixture").append("
"); jQuery("#baz").replaceWith("Baz"); equals( jQuery("#bar").text(),"Baz", "Replace element with text" ); ok( !jQuery("#baz")[0], "Verify that original element is gone, after element" ); @@ -813,14 +827,14 @@ var testReplaceWith = function(val) { QUnit.reset(); - jQuery("#main").append("
"); - equals( jQuery("#main").find("div[id=replaceWith]").length, 1, "Make sure only one div exists." ); + jQuery("#qunit-fixture").append("
"); + equals( jQuery("#qunit-fixture").find("div[id=replaceWith]").length, 1, "Make sure only one div exists." ); jQuery("#replaceWith").replaceWith( val("
") ); - equals( jQuery("#main").find("div[id=replaceWith]").length, 1, "Make sure only one div exists." ); + equals( jQuery("#qunit-fixture").find("div[id=replaceWith]").length, 1, "Make sure only one div exists." ); jQuery("#replaceWith").replaceWith( val("
") ); - equals( jQuery("#main").find("div[id=replaceWith]").length, 1, "Make sure only one div exists." ); + equals( jQuery("#qunit-fixture").find("div[id=replaceWith]").length, 1, "Make sure only one div exists." ); } test("replaceWith(String|Element|Array<Element>|jQuery)", function() { @@ -881,7 +895,7 @@ test("jQuery.clone() (#8017)", function() { ok( jQuery.clone && jQuery.isFunction( jQuery.clone ) , "jQuery.clone() utility exists and is a function."); - var main = jQuery("#main")[0], + var main = jQuery("#qunit-fixture")[0], clone = jQuery.clone( main ); equals( main.childNodes.length, clone.childNodes.length, "Simple child length to ensure a large dom tree copies correctly" ); @@ -890,7 +904,7 @@ test("jQuery.clone() (#8017)", function() { test("clone() (#8070)", function () { expect(2); - jQuery("").appendTo("#main"); + jQuery("").appendTo("#qunit-fixture"); var selects = jQuery(".test8070"); selects.append(""); @@ -1062,7 +1076,7 @@ var testHtml = function(valueObj) { jQuery.scriptorder = 0; - var div = jQuery("#main > div"); + var div = jQuery("#qunit-fixture > div"); div.html(valueObj("test")); var pass = true; for ( var i = 0; i < div.size(); i++ ) { @@ -1079,10 +1093,10 @@ var testHtml = function(valueObj) { ok( /^\xA0$|^ $/.test( space ), "Make sure entities are passed through correctly." ); equals( jQuery("
").html(valueObj("&"))[0].innerHTML, "&", "Make sure entities are passed through correctly." ); - jQuery("#main").html(valueObj("")); + jQuery("#qunit-fixture").html(valueObj("")); - equals( jQuery("#main").children().length, 1, "Make sure there is a child element." ); - equals( jQuery("#main").children()[0].nodeName.toUpperCase(), "STYLE", "And that a style element was inserted." ); + equals( jQuery("#qunit-fixture").children().length, 1, "Make sure there is a child element." ); + equals( jQuery("#qunit-fixture").children()[0].nodeName.toUpperCase(), "STYLE", "And that a style element was inserted." ); QUnit.reset(); // using contents will get comments regular, text, and comment nodes @@ -1093,9 +1107,9 @@ var testHtml = function(valueObj) { j.find("b").removeData(); equals( j.html().replace(/ xmlns="[^"]+"/g, "").toLowerCase(), "bold", "Check node,textnode,comment with html()" ); - jQuery("#main").html(valueObj("")); + jQuery("#qunit-fixture select").html(valueObj("")); + equals( jQuery("#qunit-fixture select").val(), "O2", "Selected option correct" ); var $div = jQuery("
"); equals( $div.html(valueObj( 5 )).html(), "5", "Setting a number as html" ); @@ -1113,23 +1127,23 @@ var testHtml = function(valueObj) { QUnit.reset(); - jQuery("#main").html(valueObj("
")); + jQuery("#qunit-fixture").html(valueObj("
")); - var child = jQuery("#main").find("script"); + var child = jQuery("#qunit-fixture").find("script"); equals( child.length, 2, "Make sure that two non-JavaScript script tags are left." ); equals( child[0].type, "something/else", "Verify type of script tag." ); equals( child[1].type, "something/else", "Verify type of script tag." ); - jQuery("#main").html(valueObj("")); - jQuery("#main").html(valueObj("")); - jQuery("#main").html(valueObj("")); + jQuery("#qunit-fixture").html(valueObj("")); + jQuery("#qunit-fixture").html(valueObj("")); + jQuery("#qunit-fixture").html(valueObj("")); - jQuery("#main").html(valueObj("")); + jQuery("#qunit-fixture").html(valueObj("")); - jQuery("#main").html(valueObj("foo
")); + jQuery("#qunit-fixture").html(valueObj("foo
")); - jQuery("#main").html(valueObj("