2 Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com>
3 Copyright (C) 2012 Joost-Wim Boekesteijn <joost-wim@boekesteijn.nl>
4 Copyright (C) 2012 Yusuke Suzuki <utatane.tea@gmail.com>
5 Copyright (C) 2012 Arpad Borsos <arpad.borsos@googlemail.com>
6 Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
7 Copyright (C) 2011 Yusuke Suzuki <utatane.tea@gmail.com>
8 Copyright (C) 2011 Arpad Borsos <arpad.borsos@googlemail.com>
10 Redistribution and use in source and binary forms, with or without
11 modification, are permitted provided that the following conditions are met:
13 * Redistributions of source code must retain the above copyright
14 notice, this list of conditions and the following disclaimer.
15 * Redistributions in binary form must reproduce the above copyright
16 notice, this list of conditions and the following disclaimer in the
17 documentation and/or other materials provided with the distribution.
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
23 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 /*jslint browser:true node:true */
32 /*global esprima:true, testFixture:true */
36 // Special handling for regular expression literal since we need to
37 // convert it to a string literal, otherwise it will be decoded
38 // as object "{}" and the regular expression would be lost.
39 function adjustRegexLiteral(key, value) {
41 if (key === 'value' && value instanceof RegExp) {
42 value = value.toString();
47 function NotMatchingError(expected, actual) {
49 Error.call(this, 'Expected ');
50 this.expected = expected;
53 NotMatchingError.prototype = new Error();
55 function errorToObject(e) {
57 var msg = e.toString();
59 // Opera 9.64 produces an non-standard string in toString().
60 if (msg.substr(0, 6) !== 'Error:') {
61 if (typeof e.message === 'string') {
62 msg = 'Error: ' + e.message;
68 lineNumber: e.lineNumber,
74 function sortedObject(o) {
78 if (o instanceof Array) {
79 return o.map(sortedObject);
81 if (typeof o !== 'object') {
84 if (o instanceof RegExp) {
87 var keys = Object.keys(o);
92 keys.forEach(function (key) {
93 if (o.hasOwnProperty(key)){
94 result[key] = sortedObject(o[key]);
100 function hasAttachedComment(syntax) {
102 for (key in syntax) {
103 if (key === 'leadingComments' || key === 'trailingComments') {
106 if (typeof syntax[key] === 'object' && syntax[key] !== null) {
107 if (hasAttachedComment(syntax[key])) {
115 function testParse(esprima, code, syntax) {
117 var expected, tree, actual, options, StringObject, i, len, err;
119 // alias, so that JSLint does not complain.
120 StringObject = String;
123 comment: (typeof syntax.comments !== 'undefined'),
126 tokens: (typeof syntax.tokens !== 'undefined'),
128 tolerant: (typeof syntax.errors !== 'undefined'),
132 if (options.comment) {
133 options.attachComment = hasAttachedComment(syntax);
136 if (typeof syntax.tokens !== 'undefined') {
137 if (syntax.tokens.length > 0) {
138 options.range = (typeof syntax.tokens[0].range !== 'undefined');
139 options.loc = (typeof syntax.tokens[0].loc !== 'undefined');
143 if (typeof syntax.comments !== 'undefined') {
144 if (syntax.comments.length > 0) {
145 options.range = (typeof syntax.comments[0].range !== 'undefined');
146 options.loc = (typeof syntax.comments[0].loc !== 'undefined');
151 options.source = syntax.loc.source;
154 syntax = sortedObject(syntax);
155 expected = JSON.stringify(syntax, null, 4);
157 // Some variations of the options.
158 tree = esprima.parse(code, { tolerant: options.tolerant });
159 tree = esprima.parse(code, { tolerant: options.tolerant, range: true });
160 tree = esprima.parse(code, { tolerant: options.tolerant, loc: true });
162 tree = esprima.parse(code, options);
163 tree = (options.comment || options.tokens || options.tolerant) ? tree : tree.body[0];
165 if (options.tolerant) {
166 for (i = 0, len = tree.errors.length; i < len; i += 1) {
167 tree.errors[i] = errorToObject(tree.errors[i]);
170 tree = sortedObject(tree);
171 actual = JSON.stringify(tree, adjustRegexLiteral, 4);
173 // Only to ensure that there is no error when using string object.
174 esprima.parse(new StringObject(code), options);
177 throw new NotMatchingError(expected, e.toString());
179 if (expected !== actual) {
180 throw new NotMatchingError(expected, actual);
183 function filter(key, value) {
184 if (key === 'value' && value instanceof RegExp) {
185 value = value.toString();
187 return (key === 'loc' || key === 'range') ? undefined : value;
190 if (options.tolerant) {
195 // Check again without any location info.
196 options.range = false;
198 syntax = sortedObject(syntax);
199 expected = JSON.stringify(syntax, filter, 4);
201 tree = esprima.parse(code, options);
202 tree = (options.comment || options.tokens) ? tree : tree.body[0];
204 if (options.tolerant) {
205 for (i = 0, len = tree.errors.length; i < len; i += 1) {
206 tree.errors[i] = errorToObject(tree.errors[i]);
209 tree = sortedObject(tree);
210 actual = JSON.stringify(tree, filter, 4);
212 throw new NotMatchingError(expected, e.toString());
214 if (expected !== actual) {
215 throw new NotMatchingError(expected, actual);
219 function testTokenize(esprima, code, tokens) {
221 var options, expected, actual, tree;
230 expected = JSON.stringify(tokens, null, 4);
233 tree = esprima.tokenize(code, options);
234 actual = JSON.stringify(tree, null, 4);
236 throw new NotMatchingError(expected, e.toString());
238 if (expected !== actual) {
239 throw new NotMatchingError(expected, actual);
243 function testError(esprima, code, exception) {
245 var i, options, expected, actual, err, handleInvalidRegexFlag, tokenize;
247 // Different parsing options should give the same error.
252 { raw: true, comment: true }
255 // If handleInvalidRegexFlag is true, an invalid flag in a regular expression
256 // will throw an exception. In some old version V8, this is not the case
257 // and hence handleInvalidRegexFlag is false.
258 handleInvalidRegexFlag = false;
260 'test'.match(new RegExp('[a-z]', 'x'));
262 handleInvalidRegexFlag = true;
265 exception.description = exception.message.replace(/Error: Line [0-9]+: /, '');
267 if (exception.tokenize) {
269 exception.tokenize = undefined;
271 expected = JSON.stringify(exception);
273 for (i = 0; i < options.length; i += 1) {
277 esprima.tokenize(code, options[i])
279 esprima.parse(code, options[i]);
282 err = errorToObject(e);
283 err.description = e.description;
284 actual = JSON.stringify(err);
287 if (expected !== actual) {
289 // Compensate for old V8 which does not handle invalid flag.
290 if (exception.message.indexOf('Invalid regular expression') > 0) {
291 if (typeof actual === 'undefined' && !handleInvalidRegexFlag) {
296 throw new NotMatchingError(expected, actual);
302 function testAPI(esprima, code, result) {
304 var expected, res, actual;
306 expected = JSON.stringify(result.result, null, 4);
308 if (typeof result.property !== 'undefined') {
309 res = esprima[result.property];
311 res = esprima[result.call].apply(esprima, result.args);
313 actual = JSON.stringify(res, adjustRegexLiteral, 4);
315 throw new NotMatchingError(expected, e.toString());
317 if (expected !== actual) {
318 throw new NotMatchingError(expected, actual);
322 function runTest(esprima, code, result) {
324 if (result.hasOwnProperty('lineNumber')) {
325 testError(esprima, code, result);
326 } else if (result.hasOwnProperty('result')) {
327 testAPI(esprima, code, result);
328 } else if (result instanceof Array) {
329 testTokenize(esprima, code, result);
331 testParse(esprima, code, result);
335 if (typeof window !== 'undefined') {
336 // Run all tests in a browser environment.
337 runTests = function () {
349 function setText(el, str) {
350 if (typeof el.innerText === 'string') {
353 el.textContent = str;
357 function startCategory(category) {
359 report = document.getElementById('report');
360 e = document.createElement('h4');
361 setText(e, category);
362 report.appendChild(e);
365 function reportSuccess(code) {
367 report = document.getElementById('report');
368 e = document.createElement('pre');
369 e.setAttribute('class', 'code');
371 report.appendChild(e);
374 function reportFailure(code, expected, actual) {
377 report = document.getElementById('report');
379 e = document.createElement('p');
381 report.appendChild(e);
383 e = document.createElement('pre');
384 e.setAttribute('class', 'code');
386 report.appendChild(e);
388 e = document.createElement('p');
389 setText(e, 'Expected');
390 report.appendChild(e);
392 e = document.createElement('pre');
393 e.setAttribute('class', 'expected');
394 setText(e, expected);
395 report.appendChild(e);
397 e = document.createElement('p');
398 setText(e, 'Actual');
399 report.appendChild(e);
401 e = document.createElement('pre');
402 e.setAttribute('class', 'actual');
404 report.appendChild(e);
407 setText(document.getElementById('version'), esprima.version);
410 for (category in testFixture) {
411 if (testFixture.hasOwnProperty(category)) {
412 startCategory(category);
413 fixture = testFixture[category];
414 for (source in fixture) {
415 if (fixture.hasOwnProperty(source)) {
416 expected = fixture[source];
419 runTest(esprima, source, expected);
420 reportSuccess(source, JSON.stringify(expected, null, 4));
423 reportFailure(source, e.expected, e.actual);
429 tick = (new Date()) - tick;
432 document.getElementById('status').className = 'alert-box alert';
433 setText(document.getElementById('status'), total + ' tests. ' +
434 'Failures: ' + failures + '. ' + tick + ' ms.');
436 document.getElementById('status').className = 'alert-box success';
437 setText(document.getElementById('status'), total + ' tests. ' +
438 'No failure. ' + tick + ' ms.');
445 var esprima = require('../esprima'),
448 diff = require('json-diff').diffString,
455 vm.runInThisContext(fs.readFileSync(__dirname + '/test.js', 'utf-8'));
457 Object.keys(testFixture).forEach(function (category) {
458 Object.keys(testFixture[category]).forEach(function (source) {
460 expected = testFixture[category][source];
462 runTest(esprima, source, expected);
469 tick = (new Date()) - tick;
471 header = total + ' tests. ' + failures.length + ' failures. ' +
473 if (failures.length) {
474 console.error(header);
475 failures.forEach(function (failure) {
477 var expectedObject = JSON.parse(failure.expected);
478 var actualObject = JSON.parse(failure.actual);
480 console.error(failure.source + ': Expected\n ' +
481 failure.expected.split('\n').join('\n ') +
482 '\nto match\n ' + failure.actual + '\nDiff:\n' +
483 diff(expectedObject, actualObject));
485 console.error(failure.source + ': Expected\n ' +
486 failure.expected.split('\n').join('\n ') +
487 '\nto match\n ' + failure.actual);
493 process.exit(failures.length === 0 ? 0 : 1);