1 /*! blanket - v1.1.5 */
3 if (typeof QUnit !== 'undefined'){ QUnit.config.autostart = false; }
6 Copyright (C) 2013 Ariya Hidayat <ariya.hidayat@gmail.com>
7 Copyright (C) 2013 Thaddee Tyl <thaddee.tyl@gmail.com>
8 Copyright (C) 2013 Mathias Bynens <mathias@qiwi.be>
9 Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com>
10 Copyright (C) 2012 Mathias Bynens <mathias@qiwi.be>
11 Copyright (C) 2012 Joost-Wim Boekesteijn <joost-wim@boekesteijn.nl>
12 Copyright (C) 2012 Kris Kowal <kris.kowal@cixar.com>
13 Copyright (C) 2012 Yusuke Suzuki <utatane.tea@gmail.com>
14 Copyright (C) 2012 Arpad Borsos <arpad.borsos@googlemail.com>
15 Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
17 Redistribution and use in source and binary forms, with or without
18 modification, are permitted provided that the following conditions are met:
20 * Redistributions of source code must retain the above copyright
21 notice, this list of conditions and the following disclaimer.
22 * Redistributions in binary form must reproduce the above copyright
23 notice, this list of conditions and the following disclaimer in the
24 documentation and/or other materials provided with the distribution.
26 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
27 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
30 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
33 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
35 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 /*jslint bitwise:true plusplus:true */
39 /*global esprima:true, define:true, exports:true, window: true,
40 throwErrorTolerant: true,
41 throwError: true, generateStatement: true, peek: true,
42 parseAssignmentExpression: true, parseBlock: true, parseExpression: true,
43 parseFunctionDeclaration: true, parseFunctionExpression: true,
44 parseFunctionSourceElements: true, parseVariableIdentifier: true,
45 parseLeftHandSideExpression: true,
46 parseUnaryExpression: true,
47 parseStatement: true, parseSourceElement: true */
49 (function (root, factory) {
52 // Universal Module Definition (UMD) to support AMD, CommonJS/Node.js,
53 // Rhino, and plain browser loading.
55 /* istanbul ignore next */
56 if (typeof define === 'function' && define.amd) {
57 define(['exports'], factory);
58 } else if (typeof exports !== 'undefined') {
61 factory((root.esprima = {}));
63 }(this, function (exports) {
98 TokenName[Token.BooleanLiteral] = 'Boolean';
99 TokenName[Token.EOF] = '<end>';
100 TokenName[Token.Identifier] = 'Identifier';
101 TokenName[Token.Keyword] = 'Keyword';
102 TokenName[Token.NullLiteral] = 'Null';
103 TokenName[Token.NumericLiteral] = 'Numeric';
104 TokenName[Token.Punctuator] = 'Punctuator';
105 TokenName[Token.StringLiteral] = 'String';
106 TokenName[Token.RegularExpression] = 'RegularExpression';
108 // A function following one of those tokens is an expression.
109 FnExprTokens = ['(', '{', '[', 'in', 'typeof', 'instanceof', 'new',
110 'return', 'case', 'delete', 'throw', 'void',
111 // assignment operators
112 '=', '+=', '-=', '*=', '/=', '%=', '<<=', '>>=', '>>>=',
113 '&=', '|=', '^=', ',',
114 // binary/unary operators
115 '+', '-', '*', '/', '%', '++', '--', '<<', '>>', '>>>', '&',
116 '|', '^', '!', '~', '&&', '||', '?', ':', '===', '==', '>=',
117 '<=', '<', '>', '!=', '!=='];
120 AssignmentExpression: 'AssignmentExpression',
121 ArrayExpression: 'ArrayExpression',
122 BlockStatement: 'BlockStatement',
123 BinaryExpression: 'BinaryExpression',
124 BreakStatement: 'BreakStatement',
125 CallExpression: 'CallExpression',
126 CatchClause: 'CatchClause',
127 ConditionalExpression: 'ConditionalExpression',
128 ContinueStatement: 'ContinueStatement',
129 DoWhileStatement: 'DoWhileStatement',
130 DebuggerStatement: 'DebuggerStatement',
131 EmptyStatement: 'EmptyStatement',
132 ExpressionStatement: 'ExpressionStatement',
133 ForStatement: 'ForStatement',
134 ForInStatement: 'ForInStatement',
135 FunctionDeclaration: 'FunctionDeclaration',
136 FunctionExpression: 'FunctionExpression',
137 Identifier: 'Identifier',
138 IfStatement: 'IfStatement',
140 LabeledStatement: 'LabeledStatement',
141 LogicalExpression: 'LogicalExpression',
142 MemberExpression: 'MemberExpression',
143 NewExpression: 'NewExpression',
144 ObjectExpression: 'ObjectExpression',
146 Property: 'Property',
147 ReturnStatement: 'ReturnStatement',
148 SequenceExpression: 'SequenceExpression',
149 SwitchStatement: 'SwitchStatement',
150 SwitchCase: 'SwitchCase',
151 ThisExpression: 'ThisExpression',
152 ThrowStatement: 'ThrowStatement',
153 TryStatement: 'TryStatement',
154 UnaryExpression: 'UnaryExpression',
155 UpdateExpression: 'UpdateExpression',
156 VariableDeclaration: 'VariableDeclaration',
157 VariableDeclarator: 'VariableDeclarator',
158 WhileStatement: 'WhileStatement',
159 WithStatement: 'WithStatement'
168 // Error messages should be identical to V8.
170 UnexpectedToken: 'Unexpected token %0',
171 UnexpectedNumber: 'Unexpected number',
172 UnexpectedString: 'Unexpected string',
173 UnexpectedIdentifier: 'Unexpected identifier',
174 UnexpectedReserved: 'Unexpected reserved word',
175 UnexpectedEOS: 'Unexpected end of input',
176 NewlineAfterThrow: 'Illegal newline after throw',
177 InvalidRegExp: 'Invalid regular expression',
178 UnterminatedRegExp: 'Invalid regular expression: missing /',
179 InvalidLHSInAssignment: 'Invalid left-hand side in assignment',
180 InvalidLHSInForIn: 'Invalid left-hand side in for-in',
181 MultipleDefaultsInSwitch: 'More than one default clause in switch statement',
182 NoCatchOrFinally: 'Missing catch or finally after try',
183 UnknownLabel: 'Undefined label \'%0\'',
184 Redeclaration: '%0 \'%1\' has already been declared',
185 IllegalContinue: 'Illegal continue statement',
186 IllegalBreak: 'Illegal break statement',
187 IllegalReturn: 'Illegal return statement',
188 StrictModeWith: 'Strict mode code may not include a with statement',
189 StrictCatchVariable: 'Catch variable may not be eval or arguments in strict mode',
190 StrictVarName: 'Variable name may not be eval or arguments in strict mode',
191 StrictParamName: 'Parameter name eval or arguments is not allowed in strict mode',
192 StrictParamDupe: 'Strict mode function may not have duplicate parameter names',
193 StrictFunctionName: 'Function name may not be eval or arguments in strict mode',
194 StrictOctalLiteral: 'Octal literals are not allowed in strict mode.',
195 StrictDelete: 'Delete of an unqualified identifier in strict mode.',
196 StrictDuplicateProperty: 'Duplicate data property in object literal not allowed in strict mode',
197 AccessorDataProperty: 'Object literal may not have data and accessor property with the same name',
198 AccessorGetSet: 'Object literal may not have multiple get/set accessors with the same name',
199 StrictLHSAssignment: 'Assignment to eval or arguments is not allowed in strict mode',
200 StrictLHSPostfix: 'Postfix increment/decrement may not have eval or arguments operand in strict mode',
201 StrictLHSPrefix: 'Prefix increment/decrement may not have eval or arguments operand in strict mode',
202 StrictReservedWord: 'Use of future reserved word in strict mode'
205 // See also tools/generate-unicode-regex.py.
207 NonAsciiIdentifierStart: new RegExp('[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977\u0979-\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\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\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-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\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-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F0\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\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-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\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]'),
208 NonAsciiIdentifierPart: new RegExp('[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u08A0\u08A2-\u08AC\u08E4-\u08FE\u0900-\u0963\u0966-\u096F\u0971-\u0977\u0979-\u097F\u0981-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C01-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58\u0C59\u0C60-\u0C63\u0C66-\u0C6F\u0C82\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D02\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D57\u0D60-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\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\u135D-\u135F\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F0\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191C\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19D9\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1CD0-\u1CD2\u1CD4-\u1CF6\u1D00-\u1DE6\u1DFC-\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\u200C\u200D\u203F\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u2E2F\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099\u309A\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA697\uA69F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA827\uA840-\uA873\uA880-\uA8C4\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A\uAA7B\uAA80-\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE26\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]')
211 // Ensure the condition is true, otherwise throw an error.
212 // This is only to have a better contract semantic, i.e. another safety net
213 // to catch a logic error. The condition shall be fulfilled in normal case.
214 // Do NOT use this to enforce a certain condition on any user input.
216 function assert(condition, message) {
217 /* istanbul ignore if */
219 throw new Error('ASSERT: ' + message);
223 function isDecimalDigit(ch) {
224 return (ch >= 48 && ch <= 57); // 0..9
227 function isHexDigit(ch) {
228 return '0123456789abcdefABCDEF'.indexOf(ch) >= 0;
231 function isOctalDigit(ch) {
232 return '01234567'.indexOf(ch) >= 0;
238 function isWhiteSpace(ch) {
239 return (ch === 0x20) || (ch === 0x09) || (ch === 0x0B) || (ch === 0x0C) || (ch === 0xA0) ||
240 (ch >= 0x1680 && [0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000, 0xFEFF].indexOf(ch) >= 0);
243 // 7.3 Line Terminators
245 function isLineTerminator(ch) {
246 return (ch === 0x0A) || (ch === 0x0D) || (ch === 0x2028) || (ch === 0x2029);
249 // 7.6 Identifier Names and Identifiers
251 function isIdentifierStart(ch) {
252 return (ch === 0x24) || (ch === 0x5F) || // $ (dollar) and _ (underscore)
253 (ch >= 0x41 && ch <= 0x5A) || // A..Z
254 (ch >= 0x61 && ch <= 0x7A) || // a..z
255 (ch === 0x5C) || // \ (backslash)
256 ((ch >= 0x80) && Regex.NonAsciiIdentifierStart.test(String.fromCharCode(ch)));
259 function isIdentifierPart(ch) {
260 return (ch === 0x24) || (ch === 0x5F) || // $ (dollar) and _ (underscore)
261 (ch >= 0x41 && ch <= 0x5A) || // A..Z
262 (ch >= 0x61 && ch <= 0x7A) || // a..z
263 (ch >= 0x30 && ch <= 0x39) || // 0..9
264 (ch === 0x5C) || // \ (backslash)
265 ((ch >= 0x80) && Regex.NonAsciiIdentifierPart.test(String.fromCharCode(ch)));
268 // 7.6.1.2 Future Reserved Words
270 function isFutureReservedWord(id) {
284 function isStrictModeReservedWord(id) {
301 function isRestrictedWord(id) {
302 return id === 'eval' || id === 'arguments';
307 function isKeyword(id) {
308 if (strict && isStrictModeReservedWord(id)) {
312 // 'const' is specialized as Keyword in V8.
313 // 'yield' and 'let' are for compatiblity with SpiderMonkey and ES.next.
314 // Some others are from future reserved words.
318 return (id === 'if') || (id === 'in') || (id === 'do');
320 return (id === 'var') || (id === 'for') || (id === 'new') ||
321 (id === 'try') || (id === 'let');
323 return (id === 'this') || (id === 'else') || (id === 'case') ||
324 (id === 'void') || (id === 'with') || (id === 'enum');
326 return (id === 'while') || (id === 'break') || (id === 'catch') ||
327 (id === 'throw') || (id === 'const') || (id === 'yield') ||
328 (id === 'class') || (id === 'super');
330 return (id === 'return') || (id === 'typeof') || (id === 'delete') ||
331 (id === 'switch') || (id === 'export') || (id === 'import');
333 return (id === 'default') || (id === 'finally') || (id === 'extends');
335 return (id === 'function') || (id === 'continue') || (id === 'debugger');
337 return (id === 'instanceof');
345 function addComment(type, value, start, end, loc) {
346 var comment, attacher;
348 assert(typeof start === 'number', 'Comment must have valid position');
350 // Because the way the actual token is scanned, often the comments
351 // (if any) are skipped twice during the lexical analysis.
352 // Thus, we need to skip adding a comment if the comment array already
354 if (state.lastCommentStart >= start) {
357 state.lastCommentStart = start;
364 comment.range = [start, end];
369 extra.comments.push(comment);
370 if (extra.attachComment) {
371 extra.leadingComments.push(comment);
372 extra.trailingComments.push(comment);
376 function skipSingleLineComment(offset) {
377 var start, loc, ch, comment;
379 start = index - offset;
383 column: index - lineStart - offset
387 while (index < length) {
388 ch = source.charCodeAt(index);
390 if (isLineTerminator(ch)) {
391 if (extra.comments) {
392 comment = source.slice(start + offset, index - 1);
395 column: index - lineStart - 1
397 addComment('Line', comment, start, index - 1, loc);
399 if (ch === 13 && source.charCodeAt(index) === 10) {
408 if (extra.comments) {
409 comment = source.slice(start + offset, index);
412 column: index - lineStart
414 addComment('Line', comment, start, index, loc);
418 function skipMultiLineComment() {
419 var start, loc, ch, comment;
421 if (extra.comments) {
426 column: index - lineStart - 2
431 while (index < length) {
432 ch = source.charCodeAt(index);
433 if (isLineTerminator(ch)) {
434 if (ch === 0x0D && source.charCodeAt(index + 1) === 0x0A) {
440 if (index >= length) {
441 throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
443 } else if (ch === 0x2A) {
444 // Block comment ends with '*/'.
445 if (source.charCodeAt(index + 1) === 0x2F) {
448 if (extra.comments) {
449 comment = source.slice(start + 2, index - 2);
452 column: index - lineStart
454 addComment('Block', comment, start, index, loc);
464 throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
467 function skipComment() {
470 start = (index === 0);
471 while (index < length) {
472 ch = source.charCodeAt(index);
474 if (isWhiteSpace(ch)) {
476 } else if (isLineTerminator(ch)) {
478 if (ch === 0x0D && source.charCodeAt(index) === 0x0A) {
484 } else if (ch === 0x2F) { // U+002F is '/'
485 ch = source.charCodeAt(index + 1);
489 skipSingleLineComment(2);
491 } else if (ch === 0x2A) { // U+002A is '*'
494 skipMultiLineComment();
498 } else if (start && ch === 0x2D) { // U+002D is '-'
500 if ((source.charCodeAt(index + 1) === 0x2D) && (source.charCodeAt(index + 2) === 0x3E)) {
501 // '-->' is a single-line comment
503 skipSingleLineComment(3);
507 } else if (ch === 0x3C) { // U+003C is '<'
508 if (source.slice(index + 1, index + 4) === '!--') {
513 skipSingleLineComment(4);
523 function scanHexEscape(prefix) {
524 var i, len, ch, code = 0;
526 len = (prefix === 'u') ? 4 : 2;
527 for (i = 0; i < len; ++i) {
528 if (index < length && isHexDigit(source[index])) {
529 ch = source[index++];
530 code = code * 16 + '0123456789abcdef'.indexOf(ch.toLowerCase());
535 return String.fromCharCode(code);
538 function getEscapedIdentifier() {
541 ch = source.charCodeAt(index++);
542 id = String.fromCharCode(ch);
544 // '\u' (U+005C, U+0075) denotes an escaped character.
546 if (source.charCodeAt(index) !== 0x75) {
547 throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
550 ch = scanHexEscape('u');
551 if (!ch || ch === '\\' || !isIdentifierStart(ch.charCodeAt(0))) {
552 throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
557 while (index < length) {
558 ch = source.charCodeAt(index);
559 if (!isIdentifierPart(ch)) {
563 id += String.fromCharCode(ch);
565 // '\u' (U+005C, U+0075) denotes an escaped character.
567 id = id.substr(0, id.length - 1);
568 if (source.charCodeAt(index) !== 0x75) {
569 throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
572 ch = scanHexEscape('u');
573 if (!ch || ch === '\\' || !isIdentifierPart(ch.charCodeAt(0))) {
574 throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
583 function getIdentifier() {
587 while (index < length) {
588 ch = source.charCodeAt(index);
590 // Blackslash (U+005C) marks Unicode escape sequence.
592 return getEscapedIdentifier();
594 if (isIdentifierPart(ch)) {
601 return source.slice(start, index);
604 function scanIdentifier() {
609 // Backslash (U+005C) starts an escaped character.
610 id = (source.charCodeAt(index) === 0x5C) ? getEscapedIdentifier() : getIdentifier();
612 // There is no keyword or literal with only one character.
613 // Thus, it must be an identifier.
614 if (id.length === 1) {
615 type = Token.Identifier;
616 } else if (isKeyword(id)) {
617 type = Token.Keyword;
618 } else if (id === 'null') {
619 type = Token.NullLiteral;
620 } else if (id === 'true' || id === 'false') {
621 type = Token.BooleanLiteral;
623 type = Token.Identifier;
629 lineNumber: lineNumber,
630 lineStart: lineStart,
639 function scanPunctuator() {
641 code = source.charCodeAt(index),
650 // Check for most common single-character punctuators.
652 case 0x28: // ( open bracket
653 case 0x29: // ) close bracket
654 case 0x3B: // ; semicolon
655 case 0x2C: // , comma
656 case 0x7B: // { open curly brace
657 case 0x7D: // } close curly brace
664 if (extra.tokenize) {
666 extra.openParenToken = extra.tokens.length;
667 } else if (code === 0x7B) {
668 extra.openCurlyToken = extra.tokens.length;
672 type: Token.Punctuator,
673 value: String.fromCharCode(code),
674 lineNumber: lineNumber,
675 lineStart: lineStart,
681 code2 = source.charCodeAt(index + 1);
683 // '=' (U+003D) marks an assignment or comparison operator.
684 if (code2 === 0x3D) {
698 type: Token.Punctuator,
699 value: String.fromCharCode(code) + String.fromCharCode(code2),
700 lineNumber: lineNumber,
701 lineStart: lineStart,
711 if (source.charCodeAt(index) === 0x3D) {
715 type: Token.Punctuator,
716 value: source.slice(start, index),
717 lineNumber: lineNumber,
718 lineStart: lineStart,
726 // 4-character punctuator: >>>=
728 ch4 = source.substr(index, 4);
730 if (ch4 === '>>>=') {
733 type: Token.Punctuator,
735 lineNumber: lineNumber,
736 lineStart: lineStart,
742 // 3-character punctuators: === !== >>> <<= >>=
744 ch3 = ch4.substr(0, 3);
746 if (ch3 === '>>>' || ch3 === '<<=' || ch3 === '>>=') {
749 type: Token.Punctuator,
751 lineNumber: lineNumber,
752 lineStart: lineStart,
758 // Other 2-character punctuators: ++ -- << >> && ||
759 ch2 = ch3.substr(0, 2);
761 if ((ch1 === ch2[1] && ('+-<>&|'.indexOf(ch1) >= 0)) || ch2 === '=>') {
764 type: Token.Punctuator,
766 lineNumber: lineNumber,
767 lineStart: lineStart,
773 // 1-character punctuators: < > = ! + - * % & | ^ /
774 if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) {
777 type: Token.Punctuator,
779 lineNumber: lineNumber,
780 lineStart: lineStart,
786 throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
789 // 7.8.3 Numeric Literals
791 function scanHexLiteral(start) {
794 while (index < length) {
795 if (!isHexDigit(source[index])) {
798 number += source[index++];
801 if (number.length === 0) {
802 throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
805 if (isIdentifierStart(source.charCodeAt(index))) {
806 throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
810 type: Token.NumericLiteral,
811 value: parseInt('0x' + number, 16),
812 lineNumber: lineNumber,
813 lineStart: lineStart,
819 function scanOctalLiteral(start) {
820 var number = '0' + source[index++];
821 while (index < length) {
822 if (!isOctalDigit(source[index])) {
825 number += source[index++];
828 if (isIdentifierStart(source.charCodeAt(index)) || isDecimalDigit(source.charCodeAt(index))) {
829 throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
833 type: Token.NumericLiteral,
834 value: parseInt(number, 8),
836 lineNumber: lineNumber,
837 lineStart: lineStart,
843 function scanNumericLiteral() {
844 var number, start, ch;
847 assert(isDecimalDigit(ch.charCodeAt(0)) || (ch === '.'),
848 'Numeric literal must start with a decimal digit or a decimal point');
853 number = source[index++];
856 // Hex number starts with '0x'.
857 // Octal number starts with '0'.
858 if (number === '0') {
859 if (ch === 'x' || ch === 'X') {
861 return scanHexLiteral(start);
863 if (isOctalDigit(ch)) {
864 return scanOctalLiteral(start);
867 // decimal number starts with '0' such as '09' is illegal.
868 if (ch && isDecimalDigit(ch.charCodeAt(0))) {
869 throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
873 while (isDecimalDigit(source.charCodeAt(index))) {
874 number += source[index++];
880 number += source[index++];
881 while (isDecimalDigit(source.charCodeAt(index))) {
882 number += source[index++];
887 if (ch === 'e' || ch === 'E') {
888 number += source[index++];
891 if (ch === '+' || ch === '-') {
892 number += source[index++];
894 if (isDecimalDigit(source.charCodeAt(index))) {
895 while (isDecimalDigit(source.charCodeAt(index))) {
896 number += source[index++];
899 throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
903 if (isIdentifierStart(source.charCodeAt(index))) {
904 throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
908 type: Token.NumericLiteral,
909 value: parseFloat(number),
910 lineNumber: lineNumber,
911 lineStart: lineStart,
917 // 7.8.4 String Literals
919 function scanStringLiteral() {
920 var str = '', quote, start, ch, code, unescaped, restore, octal = false, startLineNumber, startLineStart;
921 startLineNumber = lineNumber;
922 startLineStart = lineStart;
924 quote = source[index];
925 assert((quote === '\'' || quote === '"'),
926 'String literal must starts with a quote');
931 while (index < length) {
932 ch = source[index++];
937 } else if (ch === '\\') {
938 ch = source[index++];
939 if (!ch || !isLineTerminator(ch.charCodeAt(0))) {
944 unescaped = scanHexEscape(ch);
972 if (isOctalDigit(ch)) {
973 code = '01234567'.indexOf(ch);
975 // \0 is not octal escape sequence
980 if (index < length && isOctalDigit(source[index])) {
982 code = code * 8 + '01234567'.indexOf(source[index++]);
984 // 3 digits are only allowed when string starts
986 if ('0123'.indexOf(ch) >= 0 &&
988 isOctalDigit(source[index])) {
989 code = code * 8 + '01234567'.indexOf(source[index++]);
992 str += String.fromCharCode(code);
1000 if (ch === '\r' && source[index] === '\n') {
1005 } else if (isLineTerminator(ch.charCodeAt(0))) {
1013 throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
1017 type: Token.StringLiteral,
1020 startLineNumber: startLineNumber,
1021 startLineStart: startLineStart,
1022 lineNumber: lineNumber,
1023 lineStart: lineStart,
1029 function testRegExp(pattern, flags) {
1032 value = new RegExp(pattern, flags);
1034 throwError({}, Messages.InvalidRegExp);
1039 function scanRegExpBody() {
1040 var ch, str, classMarker, terminated, body;
1043 assert(ch === '/', 'Regular expression literal must start with a slash');
1044 str = source[index++];
1046 classMarker = false;
1048 while (index < length) {
1049 ch = source[index++];
1052 ch = source[index++];
1054 if (isLineTerminator(ch.charCodeAt(0))) {
1055 throwError({}, Messages.UnterminatedRegExp);
1058 } else if (isLineTerminator(ch.charCodeAt(0))) {
1059 throwError({}, Messages.UnterminatedRegExp);
1060 } else if (classMarker) {
1062 classMarker = false;
1068 } else if (ch === '[') {
1075 throwError({}, Messages.UnterminatedRegExp);
1078 // Exclude leading and trailing slash.
1079 body = str.substr(1, str.length - 2);
1086 function scanRegExpFlags() {
1087 var ch, str, flags, restore;
1091 while (index < length) {
1093 if (!isIdentifierPart(ch.charCodeAt(0))) {
1098 if (ch === '\\' && index < length) {
1103 ch = scanHexEscape('u');
1106 for (str += '\\u'; restore < index; ++restore) {
1107 str += source[restore];
1114 throwErrorTolerant({}, Messages.UnexpectedToken, 'ILLEGAL');
1117 throwErrorTolerant({}, Messages.UnexpectedToken, 'ILLEGAL');
1131 function scanRegExp() {
1132 var start, body, flags, pattern, value;
1138 body = scanRegExpBody();
1139 flags = scanRegExpFlags();
1140 value = testRegExp(body.value, flags.value);
1142 if (extra.tokenize) {
1144 type: Token.RegularExpression,
1146 lineNumber: lineNumber,
1147 lineStart: lineStart,
1154 literal: body.literal + flags.literal,
1161 function collectRegex() {
1162 var pos, loc, regex, token;
1170 column: index - lineStart
1174 regex = scanRegExp();
1177 column: index - lineStart
1180 /* istanbul ignore next */
1181 if (!extra.tokenize) {
1182 // Pop the previous token, which is likely '/' or '/='
1183 if (extra.tokens.length > 0) {
1184 token = extra.tokens[extra.tokens.length - 1];
1185 if (token.range[0] === pos && token.type === 'Punctuator') {
1186 if (token.value === '/' || token.value === '/=') {
1193 type: 'RegularExpression',
1194 value: regex.literal,
1195 range: [pos, index],
1203 function isIdentifierName(token) {
1204 return token.type === Token.Identifier ||
1205 token.type === Token.Keyword ||
1206 token.type === Token.BooleanLiteral ||
1207 token.type === Token.NullLiteral;
1210 function advanceSlash() {
1213 // Using the following algorithm:
1214 // https://github.com/mozilla/sweet.js/wiki/design
1215 prevToken = extra.tokens[extra.tokens.length - 1];
1217 // Nothing before that: it cannot be a division.
1218 return collectRegex();
1220 if (prevToken.type === 'Punctuator') {
1221 if (prevToken.value === ']') {
1222 return scanPunctuator();
1224 if (prevToken.value === ')') {
1225 checkToken = extra.tokens[extra.openParenToken - 1];
1227 checkToken.type === 'Keyword' &&
1228 (checkToken.value === 'if' ||
1229 checkToken.value === 'while' ||
1230 checkToken.value === 'for' ||
1231 checkToken.value === 'with')) {
1232 return collectRegex();
1234 return scanPunctuator();
1236 if (prevToken.value === '}') {
1237 // Dividing a function by anything makes little sense,
1238 // but we have to check for that.
1239 if (extra.tokens[extra.openCurlyToken - 3] &&
1240 extra.tokens[extra.openCurlyToken - 3].type === 'Keyword') {
1241 // Anonymous function.
1242 checkToken = extra.tokens[extra.openCurlyToken - 4];
1244 return scanPunctuator();
1246 } else if (extra.tokens[extra.openCurlyToken - 4] &&
1247 extra.tokens[extra.openCurlyToken - 4].type === 'Keyword') {
1249 checkToken = extra.tokens[extra.openCurlyToken - 5];
1251 return collectRegex();
1254 return scanPunctuator();
1256 // checkToken determines whether the function is
1257 // a declaration or an expression.
1258 if (FnExprTokens.indexOf(checkToken.value) >= 0) {
1259 // It is an expression.
1260 return scanPunctuator();
1262 // It is a declaration.
1263 return collectRegex();
1265 return collectRegex();
1267 if (prevToken.type === 'Keyword') {
1268 return collectRegex();
1270 return scanPunctuator();
1273 function advance() {
1278 if (index >= length) {
1281 lineNumber: lineNumber,
1282 lineStart: lineStart,
1288 ch = source.charCodeAt(index);
1290 if (isIdentifierStart(ch)) {
1291 return scanIdentifier();
1294 // Very common: ( and ) and ;
1295 if (ch === 0x28 || ch === 0x29 || ch === 0x3B) {
1296 return scanPunctuator();
1299 // String literal starts with single quote (U+0027) or double quote (U+0022).
1300 if (ch === 0x27 || ch === 0x22) {
1301 return scanStringLiteral();
1305 // Dot (.) U+002E can also start a floating-point number, hence the need
1306 // to check the next character.
1308 if (isDecimalDigit(source.charCodeAt(index + 1))) {
1309 return scanNumericLiteral();
1311 return scanPunctuator();
1314 if (isDecimalDigit(ch)) {
1315 return scanNumericLiteral();
1318 // Slash (/) U+002F can also start a regex.
1319 if (extra.tokenize && ch === 0x2F) {
1320 return advanceSlash();
1323 return scanPunctuator();
1326 function collectToken() {
1327 var loc, token, range, value;
1333 column: index - lineStart
1340 column: index - lineStart
1343 if (token.type !== Token.EOF) {
1344 value = source.slice(token.start, token.end);
1346 type: TokenName[token.type],
1348 range: [token.start, token.end],
1361 lineNumber = token.lineNumber;
1362 lineStart = token.lineStart;
1364 lookahead = (typeof extra.tokens !== 'undefined') ? collectToken() : advance();
1367 lineNumber = token.lineNumber;
1368 lineStart = token.lineStart;
1374 var pos, line, start;
1379 lookahead = (typeof extra.tokens !== 'undefined') ? collectToken() : advance();
1385 function Position(line, column) {
1387 this.column = column;
1390 function SourceLocation(startLine, startColumn, line, column) {
1391 this.start = new Position(startLine, startColumn);
1392 this.end = new Position(line, column);
1395 SyntaxTreeDelegate = {
1399 processComment: function (node) {
1400 var lastChild, trailingComments;
1402 if (node.type === Syntax.Program) {
1403 if (node.body.length > 0) {
1408 if (extra.trailingComments.length > 0) {
1409 if (extra.trailingComments[0].range[0] >= node.range[1]) {
1410 trailingComments = extra.trailingComments;
1411 extra.trailingComments = [];
1413 extra.trailingComments.length = 0;
1416 if (extra.bottomRightStack.length > 0 &&
1417 extra.bottomRightStack[extra.bottomRightStack.length - 1].trailingComments &&
1418 extra.bottomRightStack[extra.bottomRightStack.length - 1].trailingComments[0].range[0] >= node.range[1]) {
1419 trailingComments = extra.bottomRightStack[extra.bottomRightStack.length - 1].trailingComments;
1420 delete extra.bottomRightStack[extra.bottomRightStack.length - 1].trailingComments;
1424 // Eating the stack.
1425 while (extra.bottomRightStack.length > 0 && extra.bottomRightStack[extra.bottomRightStack.length - 1].range[0] >= node.range[0]) {
1426 lastChild = extra.bottomRightStack.pop();
1430 if (lastChild.leadingComments && lastChild.leadingComments[lastChild.leadingComments.length - 1].range[1] <= node.range[0]) {
1431 node.leadingComments = lastChild.leadingComments;
1432 delete lastChild.leadingComments;
1434 } else if (extra.leadingComments.length > 0 && extra.leadingComments[extra.leadingComments.length - 1].range[1] <= node.range[0]) {
1435 node.leadingComments = extra.leadingComments;
1436 extra.leadingComments = [];
1440 if (trailingComments) {
1441 node.trailingComments = trailingComments;
1444 extra.bottomRightStack.push(node);
1447 markEnd: function (node, startToken) {
1449 node.range = [startToken.start, index];
1452 node.loc = new SourceLocation(
1453 startToken.startLineNumber === undefined ? startToken.lineNumber : startToken.startLineNumber,
1454 startToken.start - (startToken.startLineStart === undefined ? startToken.lineStart : startToken.startLineStart),
1458 this.postProcess(node);
1461 if (extra.attachComment) {
1462 this.processComment(node);
1467 postProcess: function (node) {
1469 node.loc.source = extra.source;
1474 createArrayExpression: function (elements) {
1476 type: Syntax.ArrayExpression,
1481 createAssignmentExpression: function (operator, left, right) {
1483 type: Syntax.AssignmentExpression,
1490 createBinaryExpression: function (operator, left, right) {
1491 var type = (operator === '||' || operator === '&&') ? Syntax.LogicalExpression :
1492 Syntax.BinaryExpression;
1501 createBlockStatement: function (body) {
1503 type: Syntax.BlockStatement,
1508 createBreakStatement: function (label) {
1510 type: Syntax.BreakStatement,
1515 createCallExpression: function (callee, args) {
1517 type: Syntax.CallExpression,
1523 createCatchClause: function (param, body) {
1525 type: Syntax.CatchClause,
1531 createConditionalExpression: function (test, consequent, alternate) {
1533 type: Syntax.ConditionalExpression,
1535 consequent: consequent,
1536 alternate: alternate
1540 createContinueStatement: function (label) {
1542 type: Syntax.ContinueStatement,
1547 createDebuggerStatement: function () {
1549 type: Syntax.DebuggerStatement
1553 createDoWhileStatement: function (body, test) {
1555 type: Syntax.DoWhileStatement,
1561 createEmptyStatement: function () {
1563 type: Syntax.EmptyStatement
1567 createExpressionStatement: function (expression) {
1569 type: Syntax.ExpressionStatement,
1570 expression: expression
1574 createForStatement: function (init, test, update, body) {
1576 type: Syntax.ForStatement,
1584 createForInStatement: function (left, right, body) {
1586 type: Syntax.ForInStatement,
1594 createFunctionDeclaration: function (id, params, defaults, body) {
1596 type: Syntax.FunctionDeclaration,
1607 createFunctionExpression: function (id, params, defaults, body) {
1609 type: Syntax.FunctionExpression,
1620 createIdentifier: function (name) {
1622 type: Syntax.Identifier,
1627 createIfStatement: function (test, consequent, alternate) {
1629 type: Syntax.IfStatement,
1631 consequent: consequent,
1632 alternate: alternate
1636 createLabeledStatement: function (label, body) {
1638 type: Syntax.LabeledStatement,
1644 createLiteral: function (token) {
1646 type: Syntax.Literal,
1648 raw: source.slice(token.start, token.end)
1652 createMemberExpression: function (accessor, object, property) {
1654 type: Syntax.MemberExpression,
1655 computed: accessor === '[',
1661 createNewExpression: function (callee, args) {
1663 type: Syntax.NewExpression,
1669 createObjectExpression: function (properties) {
1671 type: Syntax.ObjectExpression,
1672 properties: properties
1676 createPostfixExpression: function (operator, argument) {
1678 type: Syntax.UpdateExpression,
1685 createProgram: function (body) {
1687 type: Syntax.Program,
1692 createProperty: function (kind, key, value) {
1694 type: Syntax.Property,
1701 createReturnStatement: function (argument) {
1703 type: Syntax.ReturnStatement,
1708 createSequenceExpression: function (expressions) {
1710 type: Syntax.SequenceExpression,
1711 expressions: expressions
1715 createSwitchCase: function (test, consequent) {
1717 type: Syntax.SwitchCase,
1719 consequent: consequent
1723 createSwitchStatement: function (discriminant, cases) {
1725 type: Syntax.SwitchStatement,
1726 discriminant: discriminant,
1731 createThisExpression: function () {
1733 type: Syntax.ThisExpression
1737 createThrowStatement: function (argument) {
1739 type: Syntax.ThrowStatement,
1744 createTryStatement: function (block, guardedHandlers, handlers, finalizer) {
1746 type: Syntax.TryStatement,
1748 guardedHandlers: guardedHandlers,
1750 finalizer: finalizer
1754 createUnaryExpression: function (operator, argument) {
1755 if (operator === '++' || operator === '--') {
1757 type: Syntax.UpdateExpression,
1764 type: Syntax.UnaryExpression,
1771 createVariableDeclaration: function (declarations, kind) {
1773 type: Syntax.VariableDeclaration,
1774 declarations: declarations,
1779 createVariableDeclarator: function (id, init) {
1781 type: Syntax.VariableDeclarator,
1787 createWhileStatement: function (test, body) {
1789 type: Syntax.WhileStatement,
1795 createWithStatement: function (object, body) {
1797 type: Syntax.WithStatement,
1804 // Return true if there is a line terminator before the next token.
1806 function peekLineTerminator() {
1807 var pos, line, start, found;
1813 found = lineNumber !== line;
1821 // Throw an exception
1823 function throwError(token, messageFormat) {
1825 args = Array.prototype.slice.call(arguments, 2),
1826 msg = messageFormat.replace(
1828 function (whole, index) {
1829 assert(index < args.length, 'Message reference must be in range');
1834 if (typeof token.lineNumber === 'number') {
1835 error = new Error('Line ' + token.lineNumber + ': ' + msg);
1836 error.index = token.start;
1837 error.lineNumber = token.lineNumber;
1838 error.column = token.start - lineStart + 1;
1840 error = new Error('Line ' + lineNumber + ': ' + msg);
1841 error.index = index;
1842 error.lineNumber = lineNumber;
1843 error.column = index - lineStart + 1;
1846 error.description = msg;
1850 function throwErrorTolerant() {
1852 throwError.apply(null, arguments);
1855 extra.errors.push(e);
1863 // Throw an exception because of the token.
1865 function throwUnexpected(token) {
1866 if (token.type === Token.EOF) {
1867 throwError(token, Messages.UnexpectedEOS);
1870 if (token.type === Token.NumericLiteral) {
1871 throwError(token, Messages.UnexpectedNumber);
1874 if (token.type === Token.StringLiteral) {
1875 throwError(token, Messages.UnexpectedString);
1878 if (token.type === Token.Identifier) {
1879 throwError(token, Messages.UnexpectedIdentifier);
1882 if (token.type === Token.Keyword) {
1883 if (isFutureReservedWord(token.value)) {
1884 throwError(token, Messages.UnexpectedReserved);
1885 } else if (strict && isStrictModeReservedWord(token.value)) {
1886 throwErrorTolerant(token, Messages.StrictReservedWord);
1889 throwError(token, Messages.UnexpectedToken, token.value);
1892 // BooleanLiteral, NullLiteral, or Punctuator.
1893 throwError(token, Messages.UnexpectedToken, token.value);
1896 // Expect the next token to match the specified punctuator.
1897 // If not, an exception will be thrown.
1899 function expect(value) {
1901 if (token.type !== Token.Punctuator || token.value !== value) {
1902 throwUnexpected(token);
1906 // Expect the next token to match the specified keyword.
1907 // If not, an exception will be thrown.
1909 function expectKeyword(keyword) {
1911 if (token.type !== Token.Keyword || token.value !== keyword) {
1912 throwUnexpected(token);
1916 // Return true if the next token matches the specified punctuator.
1918 function match(value) {
1919 return lookahead.type === Token.Punctuator && lookahead.value === value;
1922 // Return true if the next token matches the specified keyword
1924 function matchKeyword(keyword) {
1925 return lookahead.type === Token.Keyword && lookahead.value === keyword;
1928 // Return true if the next token is an assignment operator
1930 function matchAssign() {
1933 if (lookahead.type !== Token.Punctuator) {
1936 op = lookahead.value;
1937 return op === '=' ||
1951 function consumeSemicolon() {
1954 // Catch the very common case first: immediately a semicolon (U+003B).
1955 if (source.charCodeAt(index) === 0x3B || match(';')) {
1962 if (lineNumber !== line) {
1966 if (lookahead.type !== Token.EOF && !match('}')) {
1967 throwUnexpected(lookahead);
1971 // Return true if provided expression is LeftHandSideExpression
1973 function isLeftHandSide(expr) {
1974 return expr.type === Syntax.Identifier || expr.type === Syntax.MemberExpression;
1977 // 11.1.4 Array Initialiser
1979 function parseArrayInitialiser() {
1980 var elements = [], startToken;
1982 startToken = lookahead;
1985 while (!match(']')) {
1988 elements.push(null);
1990 elements.push(parseAssignmentExpression());
2000 return delegate.markEnd(delegate.createArrayExpression(elements), startToken);
2003 // 11.1.5 Object Initialiser
2005 function parsePropertyFunction(param, first) {
2006 var previousStrict, body, startToken;
2008 previousStrict = strict;
2009 startToken = lookahead;
2010 body = parseFunctionSourceElements();
2011 if (first && strict && isRestrictedWord(param[0].name)) {
2012 throwErrorTolerant(first, Messages.StrictParamName);
2014 strict = previousStrict;
2015 return delegate.markEnd(delegate.createFunctionExpression(null, param, [], body), startToken);
2018 function parseObjectPropertyKey() {
2019 var token, startToken;
2021 startToken = lookahead;
2024 // Note: This function is called only from parseObjectProperty(), where
2025 // EOF and Punctuator tokens are already filtered out.
2027 if (token.type === Token.StringLiteral || token.type === Token.NumericLiteral) {
2028 if (strict && token.octal) {
2029 throwErrorTolerant(token, Messages.StrictOctalLiteral);
2031 return delegate.markEnd(delegate.createLiteral(token), startToken);
2034 return delegate.markEnd(delegate.createIdentifier(token.value), startToken);
2037 function parseObjectProperty() {
2038 var token, key, id, value, param, startToken;
2041 startToken = lookahead;
2043 if (token.type === Token.Identifier) {
2045 id = parseObjectPropertyKey();
2047 // Property Assignment: Getter and Setter.
2049 if (token.value === 'get' && !match(':')) {
2050 key = parseObjectPropertyKey();
2053 value = parsePropertyFunction([]);
2054 return delegate.markEnd(delegate.createProperty('get', key, value), startToken);
2056 if (token.value === 'set' && !match(':')) {
2057 key = parseObjectPropertyKey();
2060 if (token.type !== Token.Identifier) {
2062 throwErrorTolerant(token, Messages.UnexpectedToken, token.value);
2063 value = parsePropertyFunction([]);
2065 param = [ parseVariableIdentifier() ];
2067 value = parsePropertyFunction(param, token);
2069 return delegate.markEnd(delegate.createProperty('set', key, value), startToken);
2072 value = parseAssignmentExpression();
2073 return delegate.markEnd(delegate.createProperty('init', id, value), startToken);
2075 if (token.type === Token.EOF || token.type === Token.Punctuator) {
2076 throwUnexpected(token);
2078 key = parseObjectPropertyKey();
2080 value = parseAssignmentExpression();
2081 return delegate.markEnd(delegate.createProperty('init', key, value), startToken);
2085 function parseObjectInitialiser() {
2086 var properties = [], property, name, key, kind, map = {}, toString = String, startToken;
2088 startToken = lookahead;
2092 while (!match('}')) {
2093 property = parseObjectProperty();
2095 if (property.key.type === Syntax.Identifier) {
2096 name = property.key.name;
2098 name = toString(property.key.value);
2100 kind = (property.kind === 'init') ? PropertyKind.Data : (property.kind === 'get') ? PropertyKind.Get : PropertyKind.Set;
2103 if (Object.prototype.hasOwnProperty.call(map, key)) {
2104 if (map[key] === PropertyKind.Data) {
2105 if (strict && kind === PropertyKind.Data) {
2106 throwErrorTolerant({}, Messages.StrictDuplicateProperty);
2107 } else if (kind !== PropertyKind.Data) {
2108 throwErrorTolerant({}, Messages.AccessorDataProperty);
2111 if (kind === PropertyKind.Data) {
2112 throwErrorTolerant({}, Messages.AccessorDataProperty);
2113 } else if (map[key] & kind) {
2114 throwErrorTolerant({}, Messages.AccessorGetSet);
2122 properties.push(property);
2131 return delegate.markEnd(delegate.createObjectExpression(properties), startToken);
2134 // 11.1.6 The Grouping Operator
2136 function parseGroupExpression() {
2141 expr = parseExpression();
2149 // 11.1 Primary Expressions
2151 function parsePrimaryExpression() {
2152 var type, token, expr, startToken;
2155 return parseGroupExpression();
2159 return parseArrayInitialiser();
2163 return parseObjectInitialiser();
2166 type = lookahead.type;
2167 startToken = lookahead;
2169 if (type === Token.Identifier) {
2170 expr = delegate.createIdentifier(lex().value);
2171 } else if (type === Token.StringLiteral || type === Token.NumericLiteral) {
2172 if (strict && lookahead.octal) {
2173 throwErrorTolerant(lookahead, Messages.StrictOctalLiteral);
2175 expr = delegate.createLiteral(lex());
2176 } else if (type === Token.Keyword) {
2177 if (matchKeyword('function')) {
2178 return parseFunctionExpression();
2180 if (matchKeyword('this')) {
2182 expr = delegate.createThisExpression();
2184 throwUnexpected(lex());
2186 } else if (type === Token.BooleanLiteral) {
2188 token.value = (token.value === 'true');
2189 expr = delegate.createLiteral(token);
2190 } else if (type === Token.NullLiteral) {
2193 expr = delegate.createLiteral(token);
2194 } else if (match('/') || match('/=')) {
2195 if (typeof extra.tokens !== 'undefined') {
2196 expr = delegate.createLiteral(collectRegex());
2198 expr = delegate.createLiteral(scanRegExp());
2202 throwUnexpected(lex());
2205 return delegate.markEnd(expr, startToken);
2208 // 11.2 Left-Hand-Side Expressions
2210 function parseArguments() {
2216 while (index < length) {
2217 args.push(parseAssignmentExpression());
2230 function parseNonComputedProperty() {
2231 var token, startToken;
2233 startToken = lookahead;
2236 if (!isIdentifierName(token)) {
2237 throwUnexpected(token);
2240 return delegate.markEnd(delegate.createIdentifier(token.value), startToken);
2243 function parseNonComputedMember() {
2246 return parseNonComputedProperty();
2249 function parseComputedMember() {
2254 expr = parseExpression();
2261 function parseNewExpression() {
2262 var callee, args, startToken;
2264 startToken = lookahead;
2265 expectKeyword('new');
2266 callee = parseLeftHandSideExpression();
2267 args = match('(') ? parseArguments() : [];
2269 return delegate.markEnd(delegate.createNewExpression(callee, args), startToken);
2272 function parseLeftHandSideExpressionAllowCall() {
2273 var previousAllowIn, expr, args, property, startToken;
2275 startToken = lookahead;
2277 previousAllowIn = state.allowIn;
2278 state.allowIn = true;
2279 expr = matchKeyword('new') ? parseNewExpression() : parsePrimaryExpression();
2280 state.allowIn = previousAllowIn;
2284 property = parseNonComputedMember();
2285 expr = delegate.createMemberExpression('.', expr, property);
2286 } else if (match('(')) {
2287 args = parseArguments();
2288 expr = delegate.createCallExpression(expr, args);
2289 } else if (match('[')) {
2290 property = parseComputedMember();
2291 expr = delegate.createMemberExpression('[', expr, property);
2295 delegate.markEnd(expr, startToken);
2301 function parseLeftHandSideExpression() {
2302 var previousAllowIn, expr, property, startToken;
2304 startToken = lookahead;
2306 previousAllowIn = state.allowIn;
2307 expr = matchKeyword('new') ? parseNewExpression() : parsePrimaryExpression();
2308 state.allowIn = previousAllowIn;
2310 while (match('.') || match('[')) {
2312 property = parseComputedMember();
2313 expr = delegate.createMemberExpression('[', expr, property);
2315 property = parseNonComputedMember();
2316 expr = delegate.createMemberExpression('.', expr, property);
2318 delegate.markEnd(expr, startToken);
2324 // 11.3 Postfix Expressions
2326 function parsePostfixExpression() {
2327 var expr, token, startToken = lookahead;
2329 expr = parseLeftHandSideExpressionAllowCall();
2331 if (lookahead.type === Token.Punctuator) {
2332 if ((match('++') || match('--')) && !peekLineTerminator()) {
2334 if (strict && expr.type === Syntax.Identifier && isRestrictedWord(expr.name)) {
2335 throwErrorTolerant({}, Messages.StrictLHSPostfix);
2338 if (!isLeftHandSide(expr)) {
2339 throwErrorTolerant({}, Messages.InvalidLHSInAssignment);
2343 expr = delegate.markEnd(delegate.createPostfixExpression(token.value, expr), startToken);
2350 // 11.4 Unary Operators
2352 function parseUnaryExpression() {
2353 var token, expr, startToken;
2355 if (lookahead.type !== Token.Punctuator && lookahead.type !== Token.Keyword) {
2356 expr = parsePostfixExpression();
2357 } else if (match('++') || match('--')) {
2358 startToken = lookahead;
2360 expr = parseUnaryExpression();
2362 if (strict && expr.type === Syntax.Identifier && isRestrictedWord(expr.name)) {
2363 throwErrorTolerant({}, Messages.StrictLHSPrefix);
2366 if (!isLeftHandSide(expr)) {
2367 throwErrorTolerant({}, Messages.InvalidLHSInAssignment);
2370 expr = delegate.createUnaryExpression(token.value, expr);
2371 expr = delegate.markEnd(expr, startToken);
2372 } else if (match('+') || match('-') || match('~') || match('!')) {
2373 startToken = lookahead;
2375 expr = parseUnaryExpression();
2376 expr = delegate.createUnaryExpression(token.value, expr);
2377 expr = delegate.markEnd(expr, startToken);
2378 } else if (matchKeyword('delete') || matchKeyword('void') || matchKeyword('typeof')) {
2379 startToken = lookahead;
2381 expr = parseUnaryExpression();
2382 expr = delegate.createUnaryExpression(token.value, expr);
2383 expr = delegate.markEnd(expr, startToken);
2384 if (strict && expr.operator === 'delete' && expr.argument.type === Syntax.Identifier) {
2385 throwErrorTolerant({}, Messages.StrictDelete);
2388 expr = parsePostfixExpression();
2394 function binaryPrecedence(token, allowIn) {
2397 if (token.type !== Token.Punctuator && token.type !== Token.Keyword) {
2401 switch (token.value) {
2438 prec = allowIn ? 7 : 0;
2465 // 11.5 Multiplicative Operators
2466 // 11.6 Additive Operators
2467 // 11.7 Bitwise Shift Operators
2468 // 11.8 Relational Operators
2469 // 11.9 Equality Operators
2470 // 11.10 Binary Bitwise Operators
2471 // 11.11 Binary Logical Operators
2473 function parseBinaryExpression() {
2474 var marker, markers, expr, token, prec, stack, right, operator, left, i;
2477 left = parseUnaryExpression();
2480 prec = binaryPrecedence(token, state.allowIn);
2487 markers = [marker, lookahead];
2488 right = parseUnaryExpression();
2490 stack = [left, token, right];
2492 while ((prec = binaryPrecedence(lookahead, state.allowIn)) > 0) {
2494 // Reduce: make a binary expression from the three topmost entries.
2495 while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec)) {
2496 right = stack.pop();
2497 operator = stack.pop().value;
2499 expr = delegate.createBinaryExpression(operator, left, right);
2501 marker = markers[markers.length - 1];
2502 delegate.markEnd(expr, marker);
2510 markers.push(lookahead);
2511 expr = parseUnaryExpression();
2515 // Final reduce to clean-up the stack.
2516 i = stack.length - 1;
2520 expr = delegate.createBinaryExpression(stack[i - 1].value, stack[i - 2], expr);
2522 marker = markers.pop();
2523 delegate.markEnd(expr, marker);
2530 // 11.12 Conditional Operator
2532 function parseConditionalExpression() {
2533 var expr, previousAllowIn, consequent, alternate, startToken;
2535 startToken = lookahead;
2537 expr = parseBinaryExpression();
2541 previousAllowIn = state.allowIn;
2542 state.allowIn = true;
2543 consequent = parseAssignmentExpression();
2544 state.allowIn = previousAllowIn;
2546 alternate = parseAssignmentExpression();
2548 expr = delegate.createConditionalExpression(expr, consequent, alternate);
2549 delegate.markEnd(expr, startToken);
2555 // 11.13 Assignment Operators
2557 function parseAssignmentExpression() {
2558 var token, left, right, node, startToken;
2561 startToken = lookahead;
2563 node = left = parseConditionalExpression();
2565 if (matchAssign()) {
2566 // LeftHandSideExpression
2567 if (!isLeftHandSide(left)) {
2568 throwErrorTolerant({}, Messages.InvalidLHSInAssignment);
2572 if (strict && left.type === Syntax.Identifier && isRestrictedWord(left.name)) {
2573 throwErrorTolerant(token, Messages.StrictLHSAssignment);
2577 right = parseAssignmentExpression();
2578 node = delegate.markEnd(delegate.createAssignmentExpression(token.value, left, right), startToken);
2584 // 11.14 Comma Operator
2586 function parseExpression() {
2587 var expr, startToken = lookahead;
2589 expr = parseAssignmentExpression();
2592 expr = delegate.createSequenceExpression([ expr ]);
2594 while (index < length) {
2599 expr.expressions.push(parseAssignmentExpression());
2602 delegate.markEnd(expr, startToken);
2610 function parseStatementList() {
2614 while (index < length) {
2618 statement = parseSourceElement();
2619 if (typeof statement === 'undefined') {
2622 list.push(statement);
2628 function parseBlock() {
2629 var block, startToken;
2631 startToken = lookahead;
2634 block = parseStatementList();
2638 return delegate.markEnd(delegate.createBlockStatement(block), startToken);
2641 // 12.2 Variable Statement
2643 function parseVariableIdentifier() {
2644 var token, startToken;
2646 startToken = lookahead;
2649 if (token.type !== Token.Identifier) {
2650 throwUnexpected(token);
2653 return delegate.markEnd(delegate.createIdentifier(token.value), startToken);
2656 function parseVariableDeclaration(kind) {
2657 var init = null, id, startToken;
2659 startToken = lookahead;
2660 id = parseVariableIdentifier();
2663 if (strict && isRestrictedWord(id.name)) {
2664 throwErrorTolerant({}, Messages.StrictVarName);
2667 if (kind === 'const') {
2669 init = parseAssignmentExpression();
2670 } else if (match('=')) {
2672 init = parseAssignmentExpression();
2675 return delegate.markEnd(delegate.createVariableDeclarator(id, init), startToken);
2678 function parseVariableDeclarationList(kind) {
2682 list.push(parseVariableDeclaration(kind));
2687 } while (index < length);
2692 function parseVariableStatement() {
2695 expectKeyword('var');
2697 declarations = parseVariableDeclarationList();
2701 return delegate.createVariableDeclaration(declarations, 'var');
2704 // kind may be `const` or `let`
2705 // Both are experimental and not in the specification yet.
2706 // see http://wiki.ecmascript.org/doku.php?id=harmony:const
2707 // and http://wiki.ecmascript.org/doku.php?id=harmony:let
2708 function parseConstLetDeclaration(kind) {
2709 var declarations, startToken;
2711 startToken = lookahead;
2713 expectKeyword(kind);
2715 declarations = parseVariableDeclarationList(kind);
2719 return delegate.markEnd(delegate.createVariableDeclaration(declarations, kind), startToken);
2722 // 12.3 Empty Statement
2724 function parseEmptyStatement() {
2726 return delegate.createEmptyStatement();
2729 // 12.4 Expression Statement
2731 function parseExpressionStatement() {
2732 var expr = parseExpression();
2734 return delegate.createExpressionStatement(expr);
2737 // 12.5 If statement
2739 function parseIfStatement() {
2740 var test, consequent, alternate;
2742 expectKeyword('if');
2746 test = parseExpression();
2750 consequent = parseStatement();
2752 if (matchKeyword('else')) {
2754 alternate = parseStatement();
2759 return delegate.createIfStatement(test, consequent, alternate);
2762 // 12.6 Iteration Statements
2764 function parseDoWhileStatement() {
2765 var body, test, oldInIteration;
2767 expectKeyword('do');
2769 oldInIteration = state.inIteration;
2770 state.inIteration = true;
2772 body = parseStatement();
2774 state.inIteration = oldInIteration;
2776 expectKeyword('while');
2780 test = parseExpression();
2788 return delegate.createDoWhileStatement(body, test);
2791 function parseWhileStatement() {
2792 var test, body, oldInIteration;
2794 expectKeyword('while');
2798 test = parseExpression();
2802 oldInIteration = state.inIteration;
2803 state.inIteration = true;
2805 body = parseStatement();
2807 state.inIteration = oldInIteration;
2809 return delegate.createWhileStatement(test, body);
2812 function parseForVariableDeclaration() {
2813 var token, declarations, startToken;
2815 startToken = lookahead;
2817 declarations = parseVariableDeclarationList();
2819 return delegate.markEnd(delegate.createVariableDeclaration(declarations, token.value), startToken);
2822 function parseForStatement() {
2823 var init, test, update, left, right, body, oldInIteration;
2825 init = test = update = null;
2827 expectKeyword('for');
2834 if (matchKeyword('var') || matchKeyword('let')) {
2835 state.allowIn = false;
2836 init = parseForVariableDeclaration();
2837 state.allowIn = true;
2839 if (init.declarations.length === 1 && matchKeyword('in')) {
2842 right = parseExpression();
2846 state.allowIn = false;
2847 init = parseExpression();
2848 state.allowIn = true;
2850 if (matchKeyword('in')) {
2851 // LeftHandSideExpression
2852 if (!isLeftHandSide(init)) {
2853 throwErrorTolerant({}, Messages.InvalidLHSInForIn);
2858 right = parseExpression();
2863 if (typeof left === 'undefined') {
2868 if (typeof left === 'undefined') {
2871 test = parseExpression();
2876 update = parseExpression();
2882 oldInIteration = state.inIteration;
2883 state.inIteration = true;
2885 body = parseStatement();
2887 state.inIteration = oldInIteration;
2889 return (typeof left === 'undefined') ?
2890 delegate.createForStatement(init, test, update, body) :
2891 delegate.createForInStatement(left, right, body);
2894 // 12.7 The continue statement
2896 function parseContinueStatement() {
2897 var label = null, key;
2899 expectKeyword('continue');
2901 // Optimize the most common form: 'continue;'.
2902 if (source.charCodeAt(index) === 0x3B) {
2905 if (!state.inIteration) {
2906 throwError({}, Messages.IllegalContinue);
2909 return delegate.createContinueStatement(null);
2912 if (peekLineTerminator()) {
2913 if (!state.inIteration) {
2914 throwError({}, Messages.IllegalContinue);
2917 return delegate.createContinueStatement(null);
2920 if (lookahead.type === Token.Identifier) {
2921 label = parseVariableIdentifier();
2923 key = '$' + label.name;
2924 if (!Object.prototype.hasOwnProperty.call(state.labelSet, key)) {
2925 throwError({}, Messages.UnknownLabel, label.name);
2931 if (label === null && !state.inIteration) {
2932 throwError({}, Messages.IllegalContinue);
2935 return delegate.createContinueStatement(label);
2938 // 12.8 The break statement
2940 function parseBreakStatement() {
2941 var label = null, key;
2943 expectKeyword('break');
2945 // Catch the very common case first: immediately a semicolon (U+003B).
2946 if (source.charCodeAt(index) === 0x3B) {
2949 if (!(state.inIteration || state.inSwitch)) {
2950 throwError({}, Messages.IllegalBreak);
2953 return delegate.createBreakStatement(null);
2956 if (peekLineTerminator()) {
2957 if (!(state.inIteration || state.inSwitch)) {
2958 throwError({}, Messages.IllegalBreak);
2961 return delegate.createBreakStatement(null);
2964 if (lookahead.type === Token.Identifier) {
2965 label = parseVariableIdentifier();
2967 key = '$' + label.name;
2968 if (!Object.prototype.hasOwnProperty.call(state.labelSet, key)) {
2969 throwError({}, Messages.UnknownLabel, label.name);
2975 if (label === null && !(state.inIteration || state.inSwitch)) {
2976 throwError({}, Messages.IllegalBreak);
2979 return delegate.createBreakStatement(label);
2982 // 12.9 The return statement
2984 function parseReturnStatement() {
2985 var argument = null;
2987 expectKeyword('return');
2989 if (!state.inFunctionBody) {
2990 throwErrorTolerant({}, Messages.IllegalReturn);
2993 // 'return' followed by a space and an identifier is very common.
2994 if (source.charCodeAt(index) === 0x20) {
2995 if (isIdentifierStart(source.charCodeAt(index + 1))) {
2996 argument = parseExpression();
2998 return delegate.createReturnStatement(argument);
3002 if (peekLineTerminator()) {
3003 return delegate.createReturnStatement(null);
3007 if (!match('}') && lookahead.type !== Token.EOF) {
3008 argument = parseExpression();
3014 return delegate.createReturnStatement(argument);
3017 // 12.10 The with statement
3019 function parseWithStatement() {
3023 // TODO(ikarienator): Should we update the test cases instead?
3025 throwErrorTolerant({}, Messages.StrictModeWith);
3028 expectKeyword('with');
3032 object = parseExpression();
3036 body = parseStatement();
3038 return delegate.createWithStatement(object, body);
3041 // 12.10 The swith statement
3043 function parseSwitchCase() {
3044 var test, consequent = [], statement, startToken;
3046 startToken = lookahead;
3047 if (matchKeyword('default')) {
3051 expectKeyword('case');
3052 test = parseExpression();
3056 while (index < length) {
3057 if (match('}') || matchKeyword('default') || matchKeyword('case')) {
3060 statement = parseStatement();
3061 consequent.push(statement);
3064 return delegate.markEnd(delegate.createSwitchCase(test, consequent), startToken);
3067 function parseSwitchStatement() {
3068 var discriminant, cases, clause, oldInSwitch, defaultFound;
3070 expectKeyword('switch');
3074 discriminant = parseExpression();
3084 return delegate.createSwitchStatement(discriminant, cases);
3087 oldInSwitch = state.inSwitch;
3088 state.inSwitch = true;
3089 defaultFound = false;
3091 while (index < length) {
3095 clause = parseSwitchCase();
3096 if (clause.test === null) {
3098 throwError({}, Messages.MultipleDefaultsInSwitch);
3100 defaultFound = true;
3105 state.inSwitch = oldInSwitch;
3109 return delegate.createSwitchStatement(discriminant, cases);
3112 // 12.13 The throw statement
3114 function parseThrowStatement() {
3117 expectKeyword('throw');
3119 if (peekLineTerminator()) {
3120 throwError({}, Messages.NewlineAfterThrow);
3123 argument = parseExpression();
3127 return delegate.createThrowStatement(argument);
3130 // 12.14 The try statement
3132 function parseCatchClause() {
3133 var param, body, startToken;
3135 startToken = lookahead;
3136 expectKeyword('catch');
3140 throwUnexpected(lookahead);
3143 param = parseVariableIdentifier();
3145 if (strict && isRestrictedWord(param.name)) {
3146 throwErrorTolerant({}, Messages.StrictCatchVariable);
3150 body = parseBlock();
3151 return delegate.markEnd(delegate.createCatchClause(param, body), startToken);
3154 function parseTryStatement() {
3155 var block, handlers = [], finalizer = null;
3157 expectKeyword('try');
3159 block = parseBlock();
3161 if (matchKeyword('catch')) {
3162 handlers.push(parseCatchClause());
3165 if (matchKeyword('finally')) {
3167 finalizer = parseBlock();
3170 if (handlers.length === 0 && !finalizer) {
3171 throwError({}, Messages.NoCatchOrFinally);
3174 return delegate.createTryStatement(block, [], handlers, finalizer);
3177 // 12.15 The debugger statement
3179 function parseDebuggerStatement() {
3180 expectKeyword('debugger');
3184 return delegate.createDebuggerStatement();
3189 function parseStatement() {
3190 var type = lookahead.type,
3196 if (type === Token.EOF) {
3197 throwUnexpected(lookahead);
3200 if (type === Token.Punctuator && lookahead.value === '{') {
3201 return parseBlock();
3204 startToken = lookahead;
3206 if (type === Token.Punctuator) {
3207 switch (lookahead.value) {
3209 return delegate.markEnd(parseEmptyStatement(), startToken);
3211 return delegate.markEnd(parseExpressionStatement(), startToken);
3217 if (type === Token.Keyword) {
3218 switch (lookahead.value) {
3220 return delegate.markEnd(parseBreakStatement(), startToken);
3222 return delegate.markEnd(parseContinueStatement(), startToken);
3224 return delegate.markEnd(parseDebuggerStatement(), startToken);
3226 return delegate.markEnd(parseDoWhileStatement(), startToken);
3228 return delegate.markEnd(parseForStatement(), startToken);
3230 return delegate.markEnd(parseFunctionDeclaration(), startToken);
3232 return delegate.markEnd(parseIfStatement(), startToken);
3234 return delegate.markEnd(parseReturnStatement(), startToken);
3236 return delegate.markEnd(parseSwitchStatement(), startToken);
3238 return delegate.markEnd(parseThrowStatement(), startToken);
3240 return delegate.markEnd(parseTryStatement(), startToken);
3242 return delegate.markEnd(parseVariableStatement(), startToken);
3244 return delegate.markEnd(parseWhileStatement(), startToken);
3246 return delegate.markEnd(parseWithStatement(), startToken);
3252 expr = parseExpression();
3254 // 12.12 Labelled Statements
3255 if ((expr.type === Syntax.Identifier) && match(':')) {
3258 key = '$' + expr.name;
3259 if (Object.prototype.hasOwnProperty.call(state.labelSet, key)) {
3260 throwError({}, Messages.Redeclaration, 'Label', expr.name);
3263 state.labelSet[key] = true;
3264 labeledBody = parseStatement();
3265 delete state.labelSet[key];
3266 return delegate.markEnd(delegate.createLabeledStatement(expr, labeledBody), startToken);
3271 return delegate.markEnd(delegate.createExpressionStatement(expr), startToken);
3274 // 13 Function Definition
3276 function parseFunctionSourceElements() {
3277 var sourceElement, sourceElements = [], token, directive, firstRestricted,
3278 oldLabelSet, oldInIteration, oldInSwitch, oldInFunctionBody, startToken;
3280 startToken = lookahead;
3283 while (index < length) {
3284 if (lookahead.type !== Token.StringLiteral) {
3289 sourceElement = parseSourceElement();
3290 sourceElements.push(sourceElement);
3291 if (sourceElement.expression.type !== Syntax.Literal) {
3292 // this is not directive
3295 directive = source.slice(token.start + 1, token.end - 1);
3296 if (directive === 'use strict') {
3298 if (firstRestricted) {
3299 throwErrorTolerant(firstRestricted, Messages.StrictOctalLiteral);
3302 if (!firstRestricted && token.octal) {
3303 firstRestricted = token;
3308 oldLabelSet = state.labelSet;
3309 oldInIteration = state.inIteration;
3310 oldInSwitch = state.inSwitch;
3311 oldInFunctionBody = state.inFunctionBody;
3313 state.labelSet = {};
3314 state.inIteration = false;
3315 state.inSwitch = false;
3316 state.inFunctionBody = true;
3318 while (index < length) {
3322 sourceElement = parseSourceElement();
3323 if (typeof sourceElement === 'undefined') {
3326 sourceElements.push(sourceElement);
3331 state.labelSet = oldLabelSet;
3332 state.inIteration = oldInIteration;
3333 state.inSwitch = oldInSwitch;
3334 state.inFunctionBody = oldInFunctionBody;
3336 return delegate.markEnd(delegate.createBlockStatement(sourceElements), startToken);
3339 function parseParams(firstRestricted) {
3340 var param, params = [], token, stricted, paramSet, key, message;
3345 while (index < length) {
3347 param = parseVariableIdentifier();
3348 key = '$' + token.value;
3350 if (isRestrictedWord(token.value)) {
3352 message = Messages.StrictParamName;
3354 if (Object.prototype.hasOwnProperty.call(paramSet, key)) {
3356 message = Messages.StrictParamDupe;
3358 } else if (!firstRestricted) {
3359 if (isRestrictedWord(token.value)) {
3360 firstRestricted = token;
3361 message = Messages.StrictParamName;
3362 } else if (isStrictModeReservedWord(token.value)) {
3363 firstRestricted = token;
3364 message = Messages.StrictReservedWord;
3365 } else if (Object.prototype.hasOwnProperty.call(paramSet, key)) {
3366 firstRestricted = token;
3367 message = Messages.StrictParamDupe;
3371 paramSet[key] = true;
3384 firstRestricted: firstRestricted,
3389 function parseFunctionDeclaration() {
3390 var id, params = [], body, token, stricted, tmp, firstRestricted, message, previousStrict, startToken;
3392 startToken = lookahead;
3394 expectKeyword('function');
3396 id = parseVariableIdentifier();
3398 if (isRestrictedWord(token.value)) {
3399 throwErrorTolerant(token, Messages.StrictFunctionName);
3402 if (isRestrictedWord(token.value)) {
3403 firstRestricted = token;
3404 message = Messages.StrictFunctionName;
3405 } else if (isStrictModeReservedWord(token.value)) {
3406 firstRestricted = token;
3407 message = Messages.StrictReservedWord;
3411 tmp = parseParams(firstRestricted);
3412 params = tmp.params;
3413 stricted = tmp.stricted;
3414 firstRestricted = tmp.firstRestricted;
3416 message = tmp.message;
3419 previousStrict = strict;
3420 body = parseFunctionSourceElements();
3421 if (strict && firstRestricted) {
3422 throwError(firstRestricted, message);
3424 if (strict && stricted) {
3425 throwErrorTolerant(stricted, message);
3427 strict = previousStrict;
3429 return delegate.markEnd(delegate.createFunctionDeclaration(id, params, [], body), startToken);
3432 function parseFunctionExpression() {
3433 var token, id = null, stricted, firstRestricted, message, tmp, params = [], body, previousStrict, startToken;
3435 startToken = lookahead;
3436 expectKeyword('function');
3440 id = parseVariableIdentifier();
3442 if (isRestrictedWord(token.value)) {
3443 throwErrorTolerant(token, Messages.StrictFunctionName);
3446 if (isRestrictedWord(token.value)) {
3447 firstRestricted = token;
3448 message = Messages.StrictFunctionName;
3449 } else if (isStrictModeReservedWord(token.value)) {
3450 firstRestricted = token;
3451 message = Messages.StrictReservedWord;
3456 tmp = parseParams(firstRestricted);
3457 params = tmp.params;
3458 stricted = tmp.stricted;
3459 firstRestricted = tmp.firstRestricted;
3461 message = tmp.message;
3464 previousStrict = strict;
3465 body = parseFunctionSourceElements();
3466 if (strict && firstRestricted) {
3467 throwError(firstRestricted, message);
3469 if (strict && stricted) {
3470 throwErrorTolerant(stricted, message);
3472 strict = previousStrict;
3474 return delegate.markEnd(delegate.createFunctionExpression(id, params, [], body), startToken);
3479 function parseSourceElement() {
3480 if (lookahead.type === Token.Keyword) {
3481 switch (lookahead.value) {
3484 return parseConstLetDeclaration(lookahead.value);
3486 return parseFunctionDeclaration();
3488 return parseStatement();
3492 if (lookahead.type !== Token.EOF) {
3493 return parseStatement();
3497 function parseSourceElements() {
3498 var sourceElement, sourceElements = [], token, directive, firstRestricted;
3500 while (index < length) {
3502 if (token.type !== Token.StringLiteral) {
3506 sourceElement = parseSourceElement();
3507 sourceElements.push(sourceElement);
3508 if (sourceElement.expression.type !== Syntax.Literal) {
3509 // this is not directive
3512 directive = source.slice(token.start + 1, token.end - 1);
3513 if (directive === 'use strict') {
3515 if (firstRestricted) {
3516 throwErrorTolerant(firstRestricted, Messages.StrictOctalLiteral);
3519 if (!firstRestricted && token.octal) {
3520 firstRestricted = token;
3525 while (index < length) {
3526 sourceElement = parseSourceElement();
3527 /* istanbul ignore if */
3528 if (typeof sourceElement === 'undefined') {
3531 sourceElements.push(sourceElement);
3533 return sourceElements;
3536 function parseProgram() {
3537 var body, startToken;
3541 startToken = lookahead;
3544 body = parseSourceElements();
3545 return delegate.markEnd(delegate.createProgram(body), startToken);
3548 function filterTokenLocation() {
3549 var i, entry, token, tokens = [];
3551 for (i = 0; i < extra.tokens.length; ++i) {
3552 entry = extra.tokens[i];
3558 token.range = entry.range;
3561 token.loc = entry.loc;
3566 extra.tokens = tokens;
3569 function tokenize(code, options) {
3575 if (typeof code !== 'string' && !(code instanceof String)) {
3576 code = toString(code);
3579 delegate = SyntaxTreeDelegate;
3582 lineNumber = (source.length > 0) ? 1 : 0;
3584 length = source.length;
3589 inFunctionBody: false,
3592 lastCommentStart: -1
3597 // Options matching.
3598 options = options || {};
3600 // Of course we collect tokens here.
3601 options.tokens = true;
3603 extra.tokenize = true;
3604 // The following two fields are necessary to compute the Regex tokens.
3605 extra.openParenToken = -1;
3606 extra.openCurlyToken = -1;
3608 extra.range = (typeof options.range === 'boolean') && options.range;
3609 extra.loc = (typeof options.loc === 'boolean') && options.loc;
3611 if (typeof options.comment === 'boolean' && options.comment) {
3612 extra.comments = [];
3614 if (typeof options.tolerant === 'boolean' && options.tolerant) {
3620 if (lookahead.type === Token.EOF) {
3621 return extra.tokens;
3625 while (lookahead.type !== Token.EOF) {
3628 } catch (lexError) {
3631 extra.errors.push(lexError);
3632 // We have to break on the first error
3633 // to avoid infinite loops.
3641 filterTokenLocation();
3642 tokens = extra.tokens;
3643 if (typeof extra.comments !== 'undefined') {
3644 tokens.comments = extra.comments;
3646 if (typeof extra.errors !== 'undefined') {
3647 tokens.errors = extra.errors;
3657 function parse(code, options) {
3658 var program, toString;
3661 if (typeof code !== 'string' && !(code instanceof String)) {
3662 code = toString(code);
3665 delegate = SyntaxTreeDelegate;
3668 lineNumber = (source.length > 0) ? 1 : 0;
3670 length = source.length;
3675 inFunctionBody: false,
3678 lastCommentStart: -1
3682 if (typeof options !== 'undefined') {
3683 extra.range = (typeof options.range === 'boolean') && options.range;
3684 extra.loc = (typeof options.loc === 'boolean') && options.loc;
3685 extra.attachComment = (typeof options.attachComment === 'boolean') && options.attachComment;
3687 if (extra.loc && options.source !== null && options.source !== undefined) {
3688 extra.source = toString(options.source);
3691 if (typeof options.tokens === 'boolean' && options.tokens) {
3694 if (typeof options.comment === 'boolean' && options.comment) {
3695 extra.comments = [];
3697 if (typeof options.tolerant === 'boolean' && options.tolerant) {
3700 if (extra.attachComment) {
3702 extra.comments = [];
3703 extra.bottomRightStack = [];
3704 extra.trailingComments = [];
3705 extra.leadingComments = [];
3710 program = parseProgram();
3711 if (typeof extra.comments !== 'undefined') {
3712 program.comments = extra.comments;
3714 if (typeof extra.tokens !== 'undefined') {
3715 filterTokenLocation();
3716 program.tokens = extra.tokens;
3718 if (typeof extra.errors !== 'undefined') {
3719 program.errors = extra.errors;
3730 // Sync with *.json manifests.
3731 exports.version = '1.2.2';
3733 exports.tokenize = tokenize;
3735 exports.parse = parse;
3738 /* istanbul ignore next */
3739 exports.Syntax = (function () {
3740 var name, types = {};
3742 if (typeof Object.create === 'function') {
3743 types = Object.create(null);
3746 for (name in Syntax) {
3747 if (Syntax.hasOwnProperty(name)) {
3748 types[name] = Syntax[name];
3752 if (typeof Object.freeze === 'function') {
3753 Object.freeze(types);
3760 /* vim: set sw=4 ts=4 et tw=80 : */
3764 * falafel (c) James Halliday / MIT License
3765 * https://github.com/substack/node-falafel
3768 (function(require,module){
3769 var parse = require('esprima').parse;
3770 var objectKeys = Object.keys || function (obj) {
3772 for (var key in obj) keys.push(key);
3775 var forEach = function (xs, fn) {
3776 if (xs.forEach) return xs.forEach(fn);
3777 for (var i = 0; i < xs.length; i++) {
3778 fn.call(xs, xs[i], i, xs);
3782 var isArray = Array.isArray || function (xs) {
3783 return Object.prototype.toString.call(xs) === '[object Array]';
3786 module.exports = function (src, opts, fn) {
3787 if (typeof opts === 'function') {
3791 if (typeof src === 'object') {
3796 src = src === undefined ? opts.source : src;
3798 if (typeof src !== 'string') src = String(src);
3800 var ast = parse(src, opts);
3803 chunks : src.split(''),
3804 toString : function () { return result.chunks.join('') },
3805 inspect : function () { return result.toString() }
3809 (function walk (node, parent) {
3810 insertHelpers(node, parent, result.chunks);
3812 forEach(objectKeys(node), function (key) {
3813 if (key === 'parent') return;
3815 var child = node[key];
3816 if (isArray(child)) {
3817 forEach(child, function (c) {
3818 if (c && typeof c.type === 'string') {
3823 else if (child && typeof child.type === 'string') {
3824 insertHelpers(child, node, result.chunks);
3834 function insertHelpers (node, parent, chunks) {
3835 if (!node.range) return;
3837 node.parent = parent;
3839 node.source = function () {
3840 return chunks.slice(
3841 node.range[0], node.range[1]
3845 if (node.update && typeof node.update === 'object') {
3846 var prev = node.update;
3847 forEach(objectKeys(prev), function (key) {
3848 update[key] = prev[key];
3850 node.update = update;
3853 node.update = update;
3856 function update (s) {
3857 chunks[node.range[0]] = s;
3858 for (var i = node.range[0] + 1; i < node.range[1]; i++) {
3864 window.falafel = module.exports;})(function(){return {parse: esprima.parse};},{exports: {}});
3865 var inBrowser = typeof window !== 'undefined' && this === window;
3866 var parseAndModify = (inBrowser ? window.falafel : require("falafel"));
3868 (inBrowser ? window : exports).blanket = (function(){
3869 var linesToAddTracking = [
3870 "ExpressionStatement",
3872 "ContinueStatement" ,
3873 "VariableDeclaration",
3877 "FunctionDeclaration" ,
3880 "DoWhileStatement" ,
3886 linesToAddBrackets = [
3889 "DoWhileStatement" ,
3895 copynumber = Math.floor(Math.random()*1000),
3896 coverageInfo = {},options = {
3900 customVariable: null,
3902 ignoreScriptError: false,
3903 existingRequireJS:false,
3907 branchTracking: false,
3911 testReadyCallback:null,
3913 instrumentCache:false,
3917 if (inBrowser && typeof window.blanket !== 'undefined'){
3918 __blanket = window.blanket.noConflict();
3922 noConflict: function(){
3928 _getCopyNumber: function(){
3930 //for differentiating between instances
3933 extend: function(obj) {
3934 //borrowed from underscore
3935 _blanket._extend(_blanket,obj);
3937 _extend: function(dest,source){
3939 for (var prop in source) {
3940 if ( dest[prop] instanceof Object && typeof dest[prop] !== "function"){
3941 _blanket._extend(dest[prop],source[prop]);
3943 dest[prop] = source[prop];
3948 getCovVar: function(){
3949 var opt = _blanket.options("customVariable");
3951 if (_blanket.options("debug")) {console.log("BLANKET-Using custom tracking variable:",opt);}
3952 return inBrowser ? "window."+opt : opt;
3954 return inBrowser ? "window._$blanket" : "_$jscoverage";
3956 options: function(key,value){
3957 if (typeof key !== "string"){
3958 _blanket._extend(options,key);
3959 }else if (typeof value === 'undefined'){
3960 return options[key];
3965 instrument: function(config, next){
3966 //check instrumented hash table,
3967 //return instrumented code if available.
3968 var inFile = config.inputFile,
3969 inFileName = config.inputFileName;
3970 //check instrument cache
3971 if (_blanket.options("instrumentCache") && sessionStorage && sessionStorage.getItem("blanket_instrument_store-"+inFileName)){
3972 if (_blanket.options("debug")) {console.log("BLANKET-Reading instrumentation from cache: ",inFileName);}
3973 next(sessionStorage.getItem("blanket_instrument_store-"+inFileName));
3975 var sourceArray = _blanket._prepareSource(inFile);
3976 _blanket._trackingArraySetup=[];
3978 inFile = inFile.replace(/^\#\!.*/, "");
3979 var instrumented = parseAndModify(inFile,{loc:true,comment:true}, _blanket._addTracking(inFileName));
3980 instrumented = _blanket._trackingSetup(inFileName,sourceArray)+instrumented;
3981 if (_blanket.options("sourceURL")){
3982 instrumented += "\n//@ sourceURL="+inFileName.replace("http://","");
3984 if (_blanket.options("debug")) {console.log("BLANKET-Instrumented file: ",inFileName);}
3985 if (_blanket.options("instrumentCache") && sessionStorage){
3986 if (_blanket.options("debug")) {console.log("BLANKET-Saving instrumentation to cache: ",inFileName);}
3987 sessionStorage.setItem("blanket_instrument_store-"+inFileName,instrumented);
3992 _trackingArraySetup: [],
3993 _branchingArraySetup: [],
3994 _prepareSource: function(source){
3995 return source.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/(\r\n|\n|\r)/gm,"\n").split('\n');
3997 _trackingSetup: function(filename,sourceArray){
3998 var branches = _blanket.options("branchTracking");
3999 var sourceString = sourceArray.join("',\n'");
4001 var covVar = _blanket.getCovVar();
4003 intro += "if (typeof "+covVar+" === 'undefined') "+covVar+" = {};\n";
4005 intro += "var _$branchFcn=function(f,l,c,r){ ";
4006 intro += "if (!!r) { ";
4007 intro += covVar+"[f].branchData[l][c][0] = "+covVar+"[f].branchData[l][c][0] || [];";
4008 intro += covVar+"[f].branchData[l][c][0].push(r); }";
4010 intro += covVar+"[f].branchData[l][c][1] = "+covVar+"[f].branchData[l][c][1] || [];";
4011 intro += covVar+"[f].branchData[l][c][1].push(r); }";
4012 intro += "return r;};\n";
4014 intro += "if (typeof "+covVar+"['"+filename+"'] === 'undefined'){";
4016 intro += covVar+"['"+filename+"']=[];\n";
4018 intro += covVar+"['"+filename+"'].branchData=[];\n";
4020 intro += covVar+"['"+filename+"'].source=['"+sourceString+"'];\n";
4021 //initialize array values
4022 _blanket._trackingArraySetup.sort(function(a,b){
4023 return parseInt(a,10) > parseInt(b,10);
4024 }).forEach(function(item){
4025 intro += covVar+"['"+filename+"']["+item+"]=0;\n";
4028 _blanket._branchingArraySetup.sort(function(a,b){
4029 return a.line > b.line;
4030 }).sort(function(a,b){
4031 return a.column > b.column;
4032 }).forEach(function(item){
4033 if (item.file === filename){
4034 intro += "if (typeof "+ covVar+"['"+filename+"'].branchData["+item.line+"] === 'undefined'){\n";
4035 intro += covVar+"['"+filename+"'].branchData["+item.line+"]=[];\n";
4037 intro += covVar+"['"+filename+"'].branchData["+item.line+"]["+item.column+"] = [];\n";
4038 intro += covVar+"['"+filename+"'].branchData["+item.line+"]["+item.column+"].consequent = "+JSON.stringify(item.consequent)+";\n";
4039 intro += covVar+"['"+filename+"'].branchData["+item.line+"]["+item.column+"].alternate = "+JSON.stringify(item.alternate)+";\n";
4047 _blockifyIf: function(node){
4048 if (linesToAddBrackets.indexOf(node.type) > -1){
4049 var bracketsExistObject = node.consequent || node.body;
4050 var bracketsExistAlt = node.alternate;
4051 if( bracketsExistAlt && bracketsExistAlt.type !== "BlockStatement") {
4052 bracketsExistAlt.update("{\n"+bracketsExistAlt.source()+"}\n");
4054 if( bracketsExistObject && bracketsExistObject.type !== "BlockStatement") {
4055 bracketsExistObject.update("{\n"+bracketsExistObject.source()+"}\n");
4059 _trackBranch: function(node,filename){
4060 //recursive on consequent and alternative
4061 var line = node.loc.start.line;
4062 var col = node.loc.start.column;
4064 _blanket._branchingArraySetup.push({
4068 consequent: node.consequent.loc,
4069 alternate: node.alternate.loc
4072 var updated = "_$branchFcn"+
4073 "('"+filename+"',"+line+","+col+","+node.test.source()+
4074 ")?"+node.consequent.source()+":"+node.alternate.source();
4075 node.update(updated);
4077 _addTracking: function (filename) {
4078 //falafel doesn't take a file name
4079 //so we include the filename in a closure
4080 //and return the function to falafel
4081 var covVar = _blanket.getCovVar();
4083 return function(node){
4084 _blanket._blockifyIf(node);
4086 if (linesToAddTracking.indexOf(node.type) > -1 && node.parent.type !== "LabeledStatement") {
4087 _blanket._checkDefs(node,filename);
4088 if (node.type === "VariableDeclaration" &&
4089 (node.parent.type === "ForStatement" || node.parent.type === "ForInStatement")){
4092 if (node.loc && node.loc.start){
4093 node.update(covVar+"['"+filename+"']["+node.loc.start.line+"]++;\n"+node.source());
4094 _blanket._trackingArraySetup.push(node.loc.start.line);
4096 //I don't think we can handle a node with no location
4097 throw new Error("The instrumenter encountered a node with no location: "+Object.keys(node));
4099 }else if (_blanket.options("branchTracking") && node.type === "ConditionalExpression"){
4100 _blanket._trackBranch(node,filename);
4104 _checkDefs: function(node,filename){
4105 // Make sure developers don't redefine window. if they do, inform them it is wrong.
4107 if (node.type === "VariableDeclaration" && node.declarations) {
4108 node.declarations.forEach(function(declaration) {
4109 if (declaration.id.name === "window") {
4110 throw new Error("Instrumentation error, you cannot redefine the 'window' variable in " + filename + ":" + node.loc.start.line);
4114 if (node.type === "FunctionDeclaration" && node.params) {
4115 node.params.forEach(function(param) {
4116 if (param.name === "window") {
4117 throw new Error("Instrumentation error, you cannot redefine the 'window' variable in " + filename + ":" + node.loc.start.line);
4121 //Make sure developers don't redefine the coverage variable
4122 if (node.type === "ExpressionStatement" &&
4123 node.expression && node.expression.left &&
4124 node.expression.left.object && node.expression.left.property &&
4125 node.expression.left.object.name +
4126 "." + node.expression.left.property.name === _blanket.getCovVar()) {
4127 throw new Error("Instrumentation error, you cannot redefine the coverage variable in " + filename + ":" + node.loc.start.line);
4130 //Make sure developers don't redefine the coverage variable in node
4131 if (node.type === "ExpressionStatement" &&
4132 node.expression && node.expression.left &&
4133 !node.expression.left.object && !node.expression.left.property &&
4134 node.expression.left.name === _blanket.getCovVar()) {
4135 throw new Error("Instrumentation error, you cannot redefine the coverage variable in " + filename + ":" + node.loc.start.line);
4139 setupCoverage: function(){
4140 coverageInfo.instrumentation = "blanket";
4141 coverageInfo.stats = {
4150 _checkIfSetup: function(){
4151 if (!coverageInfo.stats){
4152 throw new Error("You must call blanket.setupCoverage() first.");
4155 onTestStart: function(){
4156 if (_blanket.options("debug")) {console.log("BLANKET-Test event started");}
4157 this._checkIfSetup();
4158 coverageInfo.stats.tests++;
4159 coverageInfo.stats.pending++;
4161 onTestDone: function(total,passed){
4162 this._checkIfSetup();
4163 if(passed === total){
4164 coverageInfo.stats.passes++;
4166 coverageInfo.stats.failures++;
4168 coverageInfo.stats.pending--;
4170 onModuleStart: function(){
4171 this._checkIfSetup();
4172 coverageInfo.stats.suites++;
4174 onTestsDone: function(){
4175 if (_blanket.options("debug")) {console.log("BLANKET-Test event done");}
4176 this._checkIfSetup();
4177 coverageInfo.stats.end = new Date();
4180 this.report(coverageInfo);
4182 if (!_blanket.options("branchTracking")){
4183 delete (inBrowser ? window : global)[_blanket.getCovVar()].branchFcn;
4185 this.options("reporter").call(this,coverageInfo);
4192 (function(_blanket){
4193 var oldOptions = _blanket.options;
4195 outstandingRequireFiles:[],
4196 options: function(key,value){
4199 if (typeof key !== "string"){
4200 //key is key/value map
4203 }else if (typeof value === 'undefined'){
4205 return oldOptions(key);
4208 oldOptions(key,value);
4209 newVal[key] = value;
4212 if (newVal.adapter){
4213 _blanket._loadFile(newVal.adapter);
4216 _blanket._loadFile(newVal.loader);
4219 requiringFile: function(filename,done){
4220 if (typeof filename === "undefined"){
4221 _blanket.outstandingRequireFiles=[];
4222 }else if (typeof done === "undefined"){
4223 _blanket.outstandingRequireFiles.push(filename);
4225 _blanket.outstandingRequireFiles.splice(_blanket.outstandingRequireFiles.indexOf(filename),1);
4228 requireFilesLoaded: function(){
4229 return _blanket.outstandingRequireFiles.length === 0;
4231 showManualLoader: function(){
4232 if (document.getElementById("blanketLoaderDialog")){
4235 //copied from http://blog.avtex.com/2012/01/26/cross-browser-css-only-modal-box/
4236 var loader = "<div class='blanketDialogOverlay'>";
4237 loader += " </div>";
4238 loader += "<div class='blanketDialogVerticalOffset'>";
4239 loader += "<div class='blanketDialogBox'>";
4240 loader += "<b>Error:</b> Blanket.js encountered a cross origin request error while instrumenting the source files. ";
4241 loader += "<br><br>This is likely caused by the source files being referenced locally (using the file:// protocol). ";
4242 loader += "<br><br>Some solutions include <a href='http://askubuntu.com/questions/160245/making-google-chrome-option-allow-file-access-from-files-permanent' target='_blank'>starting Chrome with special flags</a>, <a target='_blank' href='https://github.com/remy/servedir'>running a server locally</a>, or using a browser without these CORS restrictions (Safari).";
4244 if (typeof FileReader !== "undefined"){
4245 loader += "<br>Or, try the experimental loader. When prompted, simply click on the directory containing all the source files you want covered.";
4246 loader += "<a href='javascript:document.getElementById(\"fileInput\").click();'>Start Loader</a>";
4247 loader += "<input type='file' type='application/x-javascript' accept='application/x-javascript' webkitdirectory id='fileInput' multiple onchange='window.blanket.manualFileLoader(this.files)' style='visibility:hidden;position:absolute;top:-50;left:-50'/>";
4249 loader += "<br><span style='float:right;cursor:pointer;' onclick=document.getElementById('blanketLoaderDialog').style.display='none';>Close</span>";
4250 loader += "<div style='clear:both'></div>";
4251 loader += "</div></div>";
4253 var css = ".blanketDialogWrapper {";
4254 css += "display:block;";
4255 css += "position:fixed;";
4256 css += "z-index:40001; }";
4258 css += ".blanketDialogOverlay {";
4259 css += "position:fixed;";
4260 css += "width:100%;";
4261 css += "height:100%;";
4262 css += "background-color:black;";
4263 css += "opacity:.5; ";
4264 css += "-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=50)'; ";
4265 css += "filter:alpha(opacity=50); ";
4266 css += "z-index:40001; }";
4268 css += ".blanketDialogVerticalOffset { ";
4269 css += "position:fixed;";
4271 css += "width:100%;";
4272 css += "z-index:40002; }";
4274 css += ".blanketDialogBox { ";
4275 css += "width:405px; ";
4276 css += "position:relative;";
4277 css += "margin:0 auto;";
4278 css += "background-color:white;";
4279 css += "padding:10px;";
4280 css += "border:1px solid black; }";
4282 var dom = document.createElement("style");
4283 dom.innerHTML = css;
4284 document.head.appendChild(dom);
4286 var div = document.createElement("div");
4287 div.id = "blanketLoaderDialog";
4288 div.className = "blanketDialogWrapper";
4289 div.innerHTML = loader;
4290 document.body.insertBefore(div,document.body.firstChild);
4293 manualFileLoader: function(files){
4294 var toArray =Array.prototype.slice;
4295 files = toArray.call(files).filter(function(item){
4296 return item.type !== "";
4298 var sessionLength = files.length-1;
4300 var sessionArray = {};
4301 if (sessionStorage["blanketSessionLoader"]){
4302 sessionArray = JSON.parse(sessionStorage["blanketSessionLoader"]);
4306 var fileLoader = function(event){
4307 var fileContent = event.currentTarget.result;
4308 var file = files[sessionIndx];
4309 var filename = file.webkitRelativePath && file.webkitRelativePath !== '' ? file.webkitRelativePath : file.name;
4310 sessionArray[filename] = fileContent;
4312 if (sessionIndx === sessionLength){
4313 sessionStorage.setItem("blanketSessionLoader", JSON.stringify(sessionArray));
4314 document.location.reload();
4316 readFile(files[sessionIndx]);
4319 function readFile(file){
4320 var reader = new FileReader();
4321 reader.onload = fileLoader;
4322 reader.readAsText(file);
4324 readFile(files[sessionIndx]);
4326 _loadFile: function(path){
4327 if (typeof path !== "undefined"){
4328 var request = new XMLHttpRequest();
4329 request.open('GET', path, false);
4331 _blanket._addScript(request.responseText);
4334 _addScript: function(data){
4335 var script = document.createElement("script");
4336 script.type = "text/javascript";
4338 (document.body || document.getElementsByTagName('head')[0]).appendChild(script);
4340 hasAdapter: function(callback){
4341 return _blanket.options("adapter") !== null;
4343 report: function(coverage_data){
4344 if (!document.getElementById("blanketLoaderDialog")){
4345 //all found, clear it
4346 _blanket.blanketSession = null;
4348 coverage_data.files = window._$blanket;
4349 var require = blanket.options("commonJS") ? blanket._commonjs.require : window.require;
4351 // Check if we have any covered files that requires reporting
4352 // otherwise just exit gracefully.
4353 if (!coverage_data.files || !Object.keys(coverage_data.files).length) {
4354 if (_blanket.options("debug")) {console.log("BLANKET-Reporting No files were instrumented.");}
4358 if (typeof coverage_data.files.branchFcn !== "undefined"){
4359 delete coverage_data.files.branchFcn;
4361 if (typeof _blanket.options("reporter") === "string"){
4362 _blanket._loadFile(_blanket.options("reporter"));
4363 _blanket.customReporter(coverage_data,_blanket.options("reporter_options"));
4364 }else if (typeof _blanket.options("reporter") === "function"){
4365 _blanket.options("reporter")(coverage_data,_blanket.options("reporter_options"));
4366 }else if (typeof _blanket.defaultReporter === 'function'){
4367 _blanket.defaultReporter(coverage_data,_blanket.options("reporter_options"));
4369 throw new Error("no reporter defined.");
4372 _bindStartTestRunner: function(bindEvent,startEvent){
4374 bindEvent(startEvent);
4376 window.addEventListener("load",startEvent,false);
4379 _loadSourceFiles: function(callback){
4380 var require = blanket.options("commonJS") ? blanket._commonjs.require : window.require;
4382 var _copy = Object.create( Object.getPrototypeOf(o) );
4383 var propNames = Object.getOwnPropertyNames(o);
4385 propNames.forEach(function(name){
4386 var desc = Object.getOwnPropertyDescriptor(o, name);
4387 Object.defineProperty(_copy, name, desc);
4392 if (_blanket.options("debug")) {console.log("BLANKET-Collecting page scripts");}
4393 var scripts = _blanket.utils.collectPageScripts();
4394 //_blanket.options("filter",scripts);
4395 if (scripts.length === 0){
4399 //check session state
4400 if (sessionStorage["blanketSessionLoader"]){
4401 _blanket.blanketSession = JSON.parse(sessionStorage["blanketSessionLoader"]);
4404 scripts.forEach(function(file,indx){
4405 _blanket.utils.cache[file]={
4411 _blanket.utils.loadAll(function(test){
4413 return typeof scripts[currScript+1] !== 'undefined';
4416 if (currScript >= scripts.length){
4419 return scripts[currScript];
4423 beforeStartTestRunner: function(opts){
4425 opts.checkRequirejs = typeof opts.checkRequirejs === "undefined" ? true : opts.checkRequirejs;
4426 opts.callback = opts.callback || function() { };
4427 opts.coverage = typeof opts.coverage === "undefined" ? true : opts.coverage;
4428 if (opts.coverage) {
4429 _blanket._bindStartTestRunner(opts.bindEvent,
4431 _blanket._loadSourceFiles(function() {
4433 var allLoaded = function(){
4434 return opts.condition ? opts.condition() : _blanket.requireFilesLoaded();
4436 var check = function() {
4438 if (_blanket.options("debug")) {console.log("BLANKET-All files loaded, init start test runner callback.");}
4439 var cb = _blanket.options("testReadyCallback");
4442 if (typeof cb === "function"){
4444 }else if (typeof cb === "string"){
4445 _blanket._addScript(cb);
4452 setTimeout(check, 13);
4463 qualifyURL: function (url) {
4464 //http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
4465 var a = document.createElement('a');
4474 blanket.defaultReporter = function(coverage){
4475 var cssSytle = "#blanket-main {margin:2px;background:#EEE;color:#333;clear:both;font-family:'Helvetica Neue Light', 'HelveticaNeue-Light', 'Helvetica Neue', Calibri, Helvetica, Arial, sans-serif; font-size:17px;} #blanket-main a {color:#333;text-decoration:none;} #blanket-main a:hover {text-decoration:underline;} .blanket {margin:0;padding:5px;clear:both;border-bottom: 1px solid #FFFFFF;} .bl-error {color:red;}.bl-success {color:#5E7D00;} .bl-file{width:auto;} .bl-cl{float:left;} .blanket div.rs {margin-left:50px; width:150px; float:right} .bl-nb {padding-right:10px;} #blanket-main a.bl-logo {color: #EB1764;cursor: pointer;font-weight: bold;text-decoration: none} .bl-source{ overflow-x:scroll; background-color: #FFFFFF; border: 1px solid #CBCBCB; color: #363636; margin: 25px 20px; width: 80%;} .bl-source div{white-space: pre;font-family: monospace;} .bl-source > div > span:first-child{background-color: #EAEAEA;color: #949494;display: inline-block;padding: 0 10px;text-align: center;width: 30px;} .bl-source .miss{background-color:#e6c3c7} .bl-source span.branchWarning{color:#000;background-color:yellow;} .bl-source span.branchOkay{color:#000;background-color:transparent;}",
4477 head = document.head,
4479 body = document.body,
4481 hasBranchTracking = Object.keys(coverage.files).some(function(elem){
4482 return typeof coverage.files[elem].branchData !== 'undefined';
4484 bodyContent = "<div id='blanket-main'><div class='blanket bl-title'><div class='bl-cl bl-file'><a href='http://alex-seville.github.com/blanket/' target='_blank' class='bl-logo'>Blanket.js</a> results</div><div class='bl-cl rs'>Coverage (%)</div><div class='bl-cl rs'>Covered/Total Smts.</div>"+(hasBranchTracking ? "<div class='bl-cl rs'>Covered/Total Branches</div>":"")+"<div style='clear:both;'></div></div>",
4485 fileTemplate = "<div class='blanket {{statusclass}}'><div class='bl-cl bl-file'><span class='bl-nb'>{{fileNumber}}.</span><a href='javascript:blanket_toggleSource(\"file-{{fileNumber}}\")'>{{file}}</a></div><div class='bl-cl rs'>{{percentage}} %</div><div class='bl-cl rs'>{{numberCovered}}/{{totalSmts}}</div>"+( hasBranchTracking ? "<div class='bl-cl rs'>{{passedBranches}}/{{totalBranches}}</div>" : "" )+"<div id='file-{{fileNumber}}' class='bl-source' style='display:none;'>{{source}}</div><div style='clear:both;'></div></div>";
4486 grandTotalTemplate = "<div class='blanket grand-total {{statusclass}}'><div class='bl-cl'>{{rowTitle}}</div><div class='bl-cl rs'>{{percentage}} %</div><div class='bl-cl rs'>{{numberCovered}}/{{totalSmts}}</div>"+( hasBranchTracking ? "<div class='bl-cl rs'>{{passedBranches}}/{{totalBranches}}</div>" : "" ) + "<div style='clear:both;'></div></div>";
4488 function blanket_toggleSource(id) {
4489 var element = document.getElementById(id);
4490 if(element.style.display === 'block') {
4491 element.style.display = 'none';
4493 element.style.display = 'block';
4498 var script = document.createElement("script");
4499 script.type = "text/javascript";
4500 script.text = blanket_toggleSource.toString().replace('function ' + blanket_toggleSource.name, 'function blanket_toggleSource');
4501 body.appendChild(script);
4503 var percentage = function(number, total) {
4504 return (Math.round(((number/total) * 100)*100)/100);
4507 var appendTag = function (type, el, str) {
4508 var dom = document.createElement(type);
4509 dom.innerHTML = str;
4510 el.appendChild(dom);
4513 function escapeInvalidXmlChars(str) {
4514 return str.replace(/\&/g, "&")
4515 .replace(/</g, "<")
4516 .replace(/\>/g, ">")
4517 .replace(/\"/g, """)
4518 .replace(/\'/g, "'");
4521 function isBranchFollowed(data,bool){
4522 var mode = bool ? 0 : 1;
4523 if (typeof data === 'undefined' ||
4524 typeof data === null ||
4525 typeof data[mode] === 'undefined'){
4528 return data[mode].length > 0;
4531 var branchStack = [];
4533 function branchReport(colsIndex,src,cols,offset,lineNum){
4536 if (branchStack.length > 0){
4537 newsrc += "<span class='" + (isBranchFollowed(branchStack[0][1],branchStack[0][1].consequent === branchStack[0][0]) ? 'branchOkay' : 'branchWarning') + "'>";
4538 if (branchStack[0][0].end.line === lineNum){
4539 newsrc += escapeInvalidXmlChars(src.slice(0,branchStack[0][0].end.column)) + "</span>";
4540 src = src.slice(branchStack[0][0].end.column);
4541 branchStack.shift();
4542 if (branchStack.length > 0){
4543 newsrc += "<span class='" + (isBranchFollowed(branchStack[0][1],false) ? 'branchOkay' : 'branchWarning') + "'>";
4544 if (branchStack[0][0].end.line === lineNum){
4545 newsrc += escapeInvalidXmlChars(src.slice(0,branchStack[0][0].end.column)) + "</span>";
4546 src = src.slice(branchStack[0][0].end.column);
4547 branchStack.shift();
4549 return {src: newsrc + escapeInvalidXmlChars(src) ,cols:cols};
4553 return {src: newsrc + escapeInvalidXmlChars(src) + "</span>",cols:cols};
4556 postfix = "</span>";
4559 return {src: newsrc + escapeInvalidXmlChars(src) ,cols:cols};
4562 return {src: newsrc + escapeInvalidXmlChars(src) + "</span>",cols:cols};
4564 postfix = "</span>";
4567 var thisline = cols[colsIndex];
4570 var cons = thisline.consequent;
4571 if (cons.start.line > lineNum){
4572 branchStack.unshift([thisline.alternate,thisline]);
4573 branchStack.unshift([cons,thisline]);
4574 src = escapeInvalidXmlChars(src);
4576 var style = "<span class='" + (isBranchFollowed(thisline,true) ? 'branchOkay' : 'branchWarning') + "'>";
4577 newsrc += escapeInvalidXmlChars(src.slice(0,cons.start.column-offset)) + style;
4579 if (cols.length > colsIndex+1 &&
4580 cols[colsIndex+1].consequent.start.line === lineNum &&
4581 cols[colsIndex+1].consequent.start.column-offset < cols[colsIndex].consequent.end.column-offset)
4583 var res = branchReport(colsIndex+1,src.slice(cons.start.column-offset,cons.end.column-offset),cols,cons.start.column-offset,lineNum);
4586 cols[colsIndex+1] = cols[colsIndex+2];
4589 newsrc += escapeInvalidXmlChars(src.slice(cons.start.column-offset,cons.end.column-offset));
4591 newsrc += "</span>";
4593 var alt = thisline.alternate;
4594 if (alt.start.line > lineNum){
4595 newsrc += escapeInvalidXmlChars(src.slice(cons.end.column-offset));
4596 branchStack.unshift([alt,thisline]);
4598 newsrc += escapeInvalidXmlChars(src.slice(cons.end.column-offset,alt.start.column-offset));
4599 style = "<span class='" + (isBranchFollowed(thisline,false) ? 'branchOkay' : 'branchWarning') + "'>";
4601 if (cols.length > colsIndex+1 &&
4602 cols[colsIndex+1].consequent.start.line === lineNum &&
4603 cols[colsIndex+1].consequent.start.column-offset < cols[colsIndex].alternate.end.column-offset)
4605 var res2 = branchReport(colsIndex+1,src.slice(alt.start.column-offset,alt.end.column-offset),cols,alt.start.column-offset,lineNum);
4608 cols[colsIndex+1] = cols[colsIndex+2];
4611 newsrc += escapeInvalidXmlChars(src.slice(alt.start.column-offset,alt.end.column-offset));
4613 newsrc += "</span>";
4614 newsrc += escapeInvalidXmlChars(src.slice(alt.end.column-offset));
4618 return {src:src+postfix, cols:cols};
4621 var isUndefined = function(item){
4622 return typeof item !== 'undefined';
4625 var files = coverage.files;
4628 numberOfFilesCovered: 0,
4631 moduleTotalStatements : {},
4632 moduleTotalCoveredStatements : {},
4633 moduleTotalBranches : {},
4634 moduleTotalCoveredBranches : {}
4637 // check if a data-cover-modulepattern was provided for per-module coverage reporting
4638 var modulePattern = _blanket.options("modulePattern");
4639 var modulePatternRegex = ( modulePattern ? new RegExp(modulePattern) : null );
4641 for(var file in files)
4643 if (!files.hasOwnProperty(file)) {
4649 var statsForFile = files[file],
4651 numberOfFilesCovered = 0,
4657 for(i = 0; i < statsForFile.source.length; i +=1){
4658 var src = statsForFile.source[i];
4660 if (branchStack.length > 0 ||
4661 typeof statsForFile.branchData !== 'undefined')
4663 if (typeof statsForFile.branchData[i+1] !== 'undefined')
4665 var cols = statsForFile.branchData[i+1].filter(isUndefined);
4669 src = branchReport(colsIndex,src,cols,0,i+1).src;
4671 }else if (branchStack.length){
4672 src = branchReport(0,src,null,0,i+1).src;
4674 src = escapeInvalidXmlChars(src);
4677 src = escapeInvalidXmlChars(src);
4680 if(statsForFile[i+1]) {
4681 numberOfFilesCovered += 1;
4685 if(statsForFile[i+1] === 0){
4690 code[i + 1] = "<div class='"+lineClass+"'><span class=''>"+(i + 1)+"</span>"+src+"</div>";
4692 totals.totalSmts += totalSmts;
4693 totals.numberOfFilesCovered += numberOfFilesCovered;
4694 var totalBranches=0;
4695 var passedBranches=0;
4696 if (typeof statsForFile.branchData !== 'undefined'){
4697 for(var j=0;j<statsForFile.branchData.length;j++){
4698 if (typeof statsForFile.branchData[j] !== 'undefined'){
4699 for(var k=0;k<statsForFile.branchData[j].length;k++){
4700 if (typeof statsForFile.branchData[j][k] !== 'undefined'){
4702 if (typeof statsForFile.branchData[j][k][0] !== 'undefined' &&
4703 statsForFile.branchData[j][k][0].length > 0 &&
4704 typeof statsForFile.branchData[j][k][1] !== 'undefined' &&
4705 statsForFile.branchData[j][k][1].length > 0){
4713 totals.passedBranches += passedBranches;
4714 totals.totalBranches += totalBranches;
4716 // if "data-cover-modulepattern" was provided,
4717 // track totals per module name as well as globally
4718 if (modulePatternRegex) {
4719 var moduleName = file.match(modulePatternRegex)[1];
4721 if(!totals.moduleTotalStatements.hasOwnProperty(moduleName)) {
4722 totals.moduleTotalStatements[moduleName] = 0;
4723 totals.moduleTotalCoveredStatements[moduleName] = 0;
4726 totals.moduleTotalStatements[moduleName] += totalSmts;
4727 totals.moduleTotalCoveredStatements[moduleName] += numberOfFilesCovered;
4729 if(!totals.moduleTotalBranches.hasOwnProperty(moduleName)) {
4730 totals.moduleTotalBranches[moduleName] = 0;
4731 totals.moduleTotalCoveredBranches[moduleName] = 0;
4734 totals.moduleTotalBranches[moduleName] += totalBranches;
4735 totals.moduleTotalCoveredBranches[moduleName] += passedBranches;
4738 var result = percentage(numberOfFilesCovered, totalSmts);
4740 var output = fileTemplate.replace("{{file}}", file)
4741 .replace("{{percentage}}",result)
4742 .replace("{{numberCovered}}", numberOfFilesCovered)
4743 .replace(/\{\{fileNumber\}\}/g, fileNumber)
4744 .replace("{{totalSmts}}", totalSmts)
4745 .replace("{{totalBranches}}", totalBranches)
4746 .replace("{{passedBranches}}", passedBranches)
4747 .replace("{{source}}", code.join(" "));
4748 if(result < successRate)
4750 output = output.replace("{{statusclass}}", "bl-error");
4752 output = output.replace("{{statusclass}}", "bl-success");
4754 bodyContent += output;
4757 // create temporary function for use by the global totals reporter,
4758 // as well as the per-module totals reporter
4759 var createAggregateTotal = function(numSt, numCov, numBranch, numCovBr, moduleName) {
4761 var totalPercent = percentage(numCov, numSt);
4762 var statusClass = totalPercent < successRate ? "bl-error" : "bl-success";
4763 var rowTitle = ( moduleName ? "Total for module: " + moduleName : "Global total" );
4764 var totalsOutput = grandTotalTemplate.replace("{{rowTitle}}", rowTitle)
4765 .replace("{{percentage}}", totalPercent)
4766 .replace("{{numberCovered}}", numCov)
4767 .replace("{{totalSmts}}", numSt)
4768 .replace("{{passedBranches}}", numCovBr)
4769 .replace("{{totalBranches}}", numBranch)
4770 .replace("{{statusclass}}", statusClass);
4772 bodyContent += totalsOutput;
4775 // if "data-cover-modulepattern" was provided,
4776 // output the per-module totals alongside the global totals
4777 if (modulePatternRegex) {
4778 for (var thisModuleName in totals.moduleTotalStatements) {
4779 if (totals.moduleTotalStatements.hasOwnProperty(thisModuleName)) {
4781 var moduleTotalSt = totals.moduleTotalStatements[thisModuleName];
4782 var moduleTotalCovSt = totals.moduleTotalCoveredStatements[thisModuleName];
4784 var moduleTotalBr = totals.moduleTotalBranches[thisModuleName];
4785 var moduleTotalCovBr = totals.moduleTotalCoveredBranches[thisModuleName];
4787 createAggregateTotal(moduleTotalSt, moduleTotalCovSt, moduleTotalBr, moduleTotalCovBr, thisModuleName);
4792 createAggregateTotal(totals.totalSmts, totals.numberOfFilesCovered, totals.totalBranches, totals.passedBranches, null);
4793 bodyContent += "</div>"; //closing main
4796 appendTag('style', head, cssSytle);
4797 //appendStyle(body, headerContent);
4798 if (document.getElementById("blanket-main")){
4799 document.getElementById("blanket-main").innerHTML=
4800 bodyContent.slice(23,-6);
4802 appendTag('div', body, bodyContent);
4804 //appendHtml(body, '</div>');
4809 //http://stackoverflow.com/a/2954896
4810 var toArray =Array.prototype.slice;
4811 var scripts = toArray.call(document.scripts);
4812 toArray.call(scripts[scripts.length - 1].attributes)
4813 .forEach(function(es){
4814 if(es.nodeName === "data-cover-only"){
4815 newOptions.filter = es.nodeValue;
4817 if(es.nodeName === "data-cover-never"){
4818 newOptions.antifilter = es.nodeValue;
4820 if(es.nodeName === "data-cover-reporter"){
4821 newOptions.reporter = es.nodeValue;
4823 if (es.nodeName === "data-cover-adapter"){
4824 newOptions.adapter = es.nodeValue;
4826 if (es.nodeName === "data-cover-loader"){
4827 newOptions.loader = es.nodeValue;
4829 if (es.nodeName === "data-cover-timeout"){
4830 newOptions.timeout = es.nodeValue;
4832 if (es.nodeName === "data-cover-modulepattern") {
4833 newOptions.modulePattern = es.nodeValue;
4835 if (es.nodeName === "data-cover-reporter-options"){
4837 newOptions.reporter_options = JSON.parse(es.nodeValue);
4839 if (blanket.options("debug")){
4840 throw new Error("Invalid reporter options. Must be a valid stringified JSON object.");
4844 if (es.nodeName === "data-cover-testReadyCallback"){
4845 newOptions.testReadyCallback = es.nodeValue;
4847 if (es.nodeName === "data-cover-customVariable"){
4848 newOptions.customVariable = es.nodeValue;
4850 if (es.nodeName === "data-cover-flags"){
4851 var flags = " "+es.nodeValue+" ";
4852 if (flags.indexOf(" ignoreError ") > -1){
4853 newOptions.ignoreScriptError = true;
4855 if (flags.indexOf(" autoStart ") > -1){
4856 newOptions.autoStart = true;
4858 if (flags.indexOf(" ignoreCors ") > -1){
4859 newOptions.ignoreCors = true;
4861 if (flags.indexOf(" branchTracking ") > -1){
4862 newOptions.branchTracking = true;
4864 if (flags.indexOf(" sourceURL ") > -1){
4865 newOptions.sourceURL = true;
4867 if (flags.indexOf(" debug ") > -1){
4868 newOptions.debug = true;
4870 if (flags.indexOf(" engineOnly ") > -1){
4871 newOptions.engineOnly = true;
4873 if (flags.indexOf(" commonJS ") > -1){
4874 newOptions.commonJS = true;
4876 if (flags.indexOf(" instrumentCache ") > -1){
4877 newOptions.instrumentCache = true;
4881 blanket.options(newOptions);
4883 if (typeof requirejs !== 'undefined'){
4884 blanket.options("existingRequireJS",true);
4886 /* setup requirejs loader, if needed */
4888 if (blanket.options("commonJS")){
4889 blanket._commonjs = {};
4892 (function(_blanket){
4895 normalizeBackslashes: function(str) {
4896 return str.replace(/\\/g, '/');
4898 matchPatternAttribute: function(filename,pattern){
4899 if (typeof pattern === 'string'){
4900 if (pattern.indexOf("[") === 0){
4902 var pattenArr = pattern.slice(1,pattern.length-1).split(",");
4903 return pattenArr.some(function(elem){
4904 return _blanket.utils.matchPatternAttribute(filename,_blanket.utils.normalizeBackslashes(elem.slice(1,-1)));
4905 //return filename.indexOf(_blanket.utils.normalizeBackslashes(elem.slice(1,-1))) > -1;
4907 }else if ( pattern.indexOf("//") === 0){
4908 var ex = pattern.slice(2,pattern.lastIndexOf('/'));
4909 var mods = pattern.slice(pattern.lastIndexOf('/')+1);
4910 var regex = new RegExp(ex,mods);
4911 return regex.test(filename);
4912 }else if (pattern.indexOf("#") === 0){
4913 return window[pattern.slice(1)].call(window,filename);
4915 return filename.indexOf(_blanket.utils.normalizeBackslashes(pattern)) > -1;
4917 }else if ( pattern instanceof Array ){
4918 return pattern.some(function(elem){
4919 return _blanket.utils.matchPatternAttribute(filename,elem);
4921 }else if (pattern instanceof RegExp){
4922 return pattern.test(filename);
4923 }else if (typeof pattern === "function"){
4924 return pattern.call(window,filename);
4927 blanketEval: function(data){
4928 _blanket._addScript(data);
4930 collectPageScripts: function(){
4931 var toArray = Array.prototype.slice;
4932 var scripts = toArray.call(document.scripts);
4933 var selectedScripts=[],scriptNames=[];
4934 var filter = _blanket.options("filter");
4936 //global filter in place, data-cover-only
4937 var antimatch = _blanket.options("antifilter");
4938 selectedScripts = toArray.call(document.scripts)
4939 .filter(function(s){
4940 return toArray.call(s.attributes).filter(function(sn){
4941 return sn.nodeName === "src" && _blanket.utils.matchPatternAttribute(sn.nodeValue,filter) &&
4942 (typeof antimatch === "undefined" || !_blanket.utils.matchPatternAttribute(sn.nodeValue,antimatch));
4946 selectedScripts = toArray.call(document.querySelectorAll("script[data-cover]"));
4948 scriptNames = selectedScripts.map(function(s){
4949 return _blanket.utils.qualifyURL(
4950 toArray.call(s.attributes).filter(
4952 return sn.nodeName === "src";
4956 _blanket.options("filter","['"+scriptNames.join("','")+"']");
4960 loadAll: function(nextScript,cb,preprocessor){
4963 * @param {nextScript} factory for priority level
4964 * @param {cb} the done callback
4966 var currScript=nextScript();
4967 var isLoaded = _blanket.utils.scriptIsLoaded(
4969 _blanket.utils.ifOrdered,
4974 if (!(_blanket.utils.cache[currScript] && _blanket.utils.cache[currScript].loaded)){
4975 var attach = function(){
4976 if (_blanket.options("debug")) {console.log("BLANKET-Mark script:"+currScript+", as loaded and move to next script.");}
4979 var whenDone = function(result){
4980 if (_blanket.options("debug")) {console.log("BLANKET-File loading finished");}
4981 if (typeof result !== 'undefined'){
4982 if (_blanket.options("debug")) {console.log("BLANKET-Add file to DOM.");}
4983 _blanket._addScript(result);
4988 _blanket.utils.attachScript(
4993 _blanket.utils.processFile(
5005 attachScript: function(options,cb){
5006 var timeout = _blanket.options("timeout") || 3000;
5007 setTimeout(function(){
5008 if (!_blanket.utils.cache[options.url].loaded){
5009 throw new Error("error loading source script");
5012 _blanket.utils.getFile(
5015 function(){ throw new Error("error loading source script");}
5018 ifOrdered: function(nextScript,cb){
5020 * ordered loading callback
5021 * @param {nextScript} factory for priority level
5022 * @param {cb} the done callback
5024 var currScript = nextScript(true);
5026 _blanket.utils.loadAll(nextScript,cb);
5028 cb(new Error("Error in loading chain."));
5031 scriptIsLoaded: function(url,orderedCb,nextScript,cb){
5033 * returns a callback that checks a loading list to see if a script is loaded.
5034 * @param {orderedCb} callback if ordered loading is being done
5035 * @param {nextScript} factory for next priority level
5036 * @param {cb} the done callback
5038 if (_blanket.options("debug")) {console.log("BLANKET-Returning function");}
5040 if (_blanket.options("debug")) {console.log("BLANKET-Marking file as loaded: "+url);}
5042 _blanket.utils.cache[url].loaded=true;
5044 if (_blanket.utils.allLoaded()){
5045 if (_blanket.options("debug")) {console.log("BLANKET-All files loaded");}
5047 }else if (orderedCb){
5048 //if it's ordered we need to
5049 //traverse down to the next
5051 if (_blanket.options("debug")) {console.log("BLANKET-Load next file.");}
5052 orderedCb(nextScript,cb);
5057 allLoaded: function (){
5059 * check if depdencies are loaded in cache
5061 var cached = Object.keys(_blanket.utils.cache);
5062 for (var i=0;i<cached.length;i++){
5063 if (!_blanket.utils.cache[cached[i]].loaded){
5069 processFile: function (content,url,cb,oldCb) {
5070 var match = _blanket.options("filter");
5071 //we check the never matches first
5072 var antimatch = _blanket.options("antifilter");
5073 if (typeof antimatch !== "undefined" &&
5074 _blanket.utils.matchPatternAttribute(url,antimatch)
5077 if (_blanket.options("debug")) {console.log("BLANKET-File will never be instrumented:"+url);}
5078 _blanket.requiringFile(url,true);
5079 }else if (_blanket.utils.matchPatternAttribute(url,match)){
5080 if (_blanket.options("debug")) {console.log("BLANKET-Attempting instrument of:"+url);}
5081 _blanket.instrument({
5084 },function(instrumented){
5086 if (_blanket.options("debug")) {console.log("BLANKET-instrument of:"+url+" was successfull.");}
5087 _blanket.utils.blanketEval(instrumented);
5089 _blanket.requiringFile(url,true);
5092 if (_blanket.options("ignoreScriptError")){
5093 //we can continue like normal if
5094 //we're ignoring script errors,
5095 //but otherwise we don't want
5096 //to completeLoad or the error might be
5098 if (_blanket.options("debug")) { console.log("BLANKET-There was an error loading the file:"+url); }
5100 _blanket.requiringFile(url,true);
5102 throw new Error("Error parsing instrumented code: "+err);
5107 if (_blanket.options("debug")) { console.log("BLANKET-Loading (without instrumenting) the file:"+url);}
5109 _blanket.requiringFile(url,true);
5113 cacheXhrConstructor: function(){
5114 var Constructor, createXhr, i, progId;
5115 if (typeof XMLHttpRequest !== "undefined") {
5116 Constructor = XMLHttpRequest;
5117 this.createXhr = function() { return new Constructor(); };
5118 } else if (typeof ActiveXObject !== "undefined") {
5119 Constructor = ActiveXObject;
5120 for (i = 0; i < 3; i += 1) {
5121 progId = progIds[i];
5123 new ActiveXObject(progId);
5127 this.createXhr = function() { return new Constructor(progId); };
5130 craeteXhr: function () {
5131 throw new Error("cacheXhrConstructor is supposed to overwrite this function.");
5133 getFile: function(url, callback, errback, onXhr){
5134 var foundInSession = false;
5135 if (_blanket.blanketSession){
5136 var files = Object.keys(_blanket.blanketSession);
5137 for (var i=0; i<files.length;i++ ){
5139 if (url.indexOf(key) > -1){
5140 callback(_blanket.blanketSession[key]);
5141 foundInSession=true;
5146 if (!foundInSession){
5147 var xhr = _blanket.utils.createXhr();
5148 xhr.open('GET', url, true);
5150 //Allow overrides specified in config
5155 xhr.onreadystatechange = function (evt) {
5158 //Do not explicitly handle errors, those should be
5159 //visible via console output in the browser.
5160 if (xhr.readyState === 4) {
5161 status = xhr.status;
5162 if ((status > 399 && status < 600) /*||
5164 navigator.userAgent.toLowerCase().indexOf('firefox') > -1)
5166 //An http 4xx or 5xx error. Signal an error.
5167 err = new Error(url + ' HTTP status: ' + status);
5171 callback(xhr.responseText);
5178 if (e.code && (e.code === 101 || e.code === 1012) && _blanket.options("ignoreCors") === false){
5179 //running locally and getting error from browser
5180 _blanket.showManualLoader();
5191 var require = blanket.options("commonJS") ? blanket._commonjs.require : window.require;
5192 var requirejs = blanket.options("commonJS") ? blanket._commonjs.requirejs : window.requirejs;
5193 if (!_blanket.options("engineOnly") && _blanket.options("existingRequireJS")){
5195 _blanket.utils.oldloader = requirejs.load;
5197 requirejs.load = function (context, moduleName, url) {
5198 _blanket.requiringFile(url);
5199 _blanket.utils.getFile(url,
5201 _blanket.utils.processFile(
5204 function newLoader(){
5205 context.completeLoad(moduleName);
5207 function oldLoader(){
5208 _blanket.utils.oldloader(context, moduleName, url);
5212 _blanket.requiringFile();
5217 // Save the XHR constructor, just in case frameworks like Sinon would sandbox it.
5218 _blanket.utils.cacheXhrConstructor();
5224 if (typeof QUnit !== 'undefined'){
5225 //check to make sure requirejs is completed before we start the test runner
5226 var allLoaded = function() {
5227 return window.QUnit.config.queue.length > 0 && blanket.noConflict().requireFilesLoaded();
5230 if (!QUnit.config.urlConfig[0].tooltip){
5231 //older versions we run coverage automatically
5232 //and we change how events are binded
5233 QUnit.begin=function(){
5234 blanket.noConflict().setupCoverage();
5237 QUnit.done=function(failures, total) {
5238 blanket.noConflict().onTestsDone();
5240 QUnit.moduleStart=function( details ) {
5241 blanket.noConflict().onModuleStart();
5243 QUnit.testStart=function( details ) {
5244 blanket.noConflict().onTestStart();
5246 QUnit.testDone=function( details ) {
5247 blanket.noConflict().onTestDone(details.total,details.passed);
5249 blanket.beforeStartTestRunner({
5250 condition: allLoaded,
5251 callback: QUnit.start
5254 QUnit.config.urlConfig.push({
5256 label: "Enable coverage",
5257 tooltip: "Enable code coverage."
5260 if ( QUnit.urlParams.coverage || blanket.options("autoStart") ) {
5261 QUnit.begin(function(){
5262 blanket.noConflict().setupCoverage();
5265 QUnit.done(function(failures, total) {
5266 blanket.noConflict().onTestsDone();
5268 QUnit.moduleStart(function( details ) {
5269 blanket.noConflict().onModuleStart();
5271 QUnit.testStart(function( details ) {
5272 blanket.noConflict().onTestStart();
5274 QUnit.testDone(function( details ) {
5275 blanket.noConflict().onTestDone(details.total,details.passed);
5277 blanket.noConflict().beforeStartTestRunner({
5278 condition: allLoaded,
5279 callback: function(){
5280 if (!(blanket.options("existingRequireJS") && !blanket.options("autoStart"))){
5286 if (blanket.options("existingRequireJS")){ requirejs.load = _blanket.utils.oldloader; }
5287 blanket.noConflict().beforeStartTestRunner({
5288 condition: allLoaded,
5289 callback: function(){
5290 if (!(blanket.options("existingRequireJS") && !blanket.options("autoStart"))){