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 function NotMatchingError(expected, actual) {
38 Error.call(this, 'Expected ');
39 this.expected = expected;
42 NotMatchingError.prototype = new Error();
44 function errorToObject(e) {
46 var msg = e.toString();
48 // Opera 9.64 produces an non-standard string in toString().
49 if (msg.substr(0, 6) !== 'Error:') {
50 if (typeof e.message === 'string') {
51 msg = 'Error: ' + e.message;
57 lineNumber: e.lineNumber,
63 function sortedObject(o) {
67 if (Array.isArray(o)) {
68 return o.map(sortedObject);
70 if (typeof o !== 'object') {
73 if (o instanceof RegExp) {
76 var keys = Object.keys(o);
81 keys.forEach(function (key) {
82 if (o.hasOwnProperty(key)){
83 result[key] = sortedObject(o[key]);
89 function hasAttachedComment(syntax) {
92 if (key === 'leadingComments' || key === 'trailingComments') {
95 if (typeof syntax[key] === 'object' && syntax[key] !== null) {
96 if (hasAttachedComment(syntax[key])) {
104 function testParse(esprima, code, syntax) {
106 var expected, tree, actual, options, StringObject, i, len;
108 // alias, so that JSLint does not complain.
109 StringObject = String;
112 comment: (typeof syntax.comments !== 'undefined'),
115 tokens: (typeof syntax.tokens !== 'undefined'),
117 tolerant: (typeof syntax.errors !== 'undefined'),
119 sourceType: syntax.sourceType
122 if (options.comment) {
123 options.attachComment = hasAttachedComment(syntax);
126 if (typeof syntax.tokens !== 'undefined') {
127 if (syntax.tokens.length > 0) {
128 options.range = (typeof syntax.tokens[0].range !== 'undefined');
129 options.loc = (typeof syntax.tokens[0].loc !== 'undefined');
133 if (typeof syntax.comments !== 'undefined') {
134 if (syntax.comments.length > 0) {
135 options.range = (typeof syntax.comments[0].range !== 'undefined');
136 options.loc = (typeof syntax.comments[0].loc !== 'undefined');
141 options.source = syntax.loc.source;
144 syntax = sortedObject(syntax);
145 expected = JSON.stringify(syntax, null, 4);
147 // Some variations of the options.
148 tree = esprima.parse(code, { tolerant: options.tolerant, sourceType: options.sourceType });
149 tree = esprima.parse(code, { tolerant: options.tolerant, sourceType: options.sourceType, range: true });
150 tree = esprima.parse(code, { tolerant: options.tolerant, sourceType: options.sourceType, loc: true });
152 tree = esprima.parse(code, options);
154 if (options.tolerant) {
155 for (i = 0, len = tree.errors.length; i < len; i += 1) {
156 tree.errors[i] = errorToObject(tree.errors[i]);
159 tree = sortedObject(tree);
160 actual = JSON.stringify(tree, null, 4);
162 // Only to ensure that there is no error when using string object.
163 esprima.parse(new StringObject(code), options);
166 throw new NotMatchingError(expected, e.toString());
168 if (expected !== actual) {
169 throw new NotMatchingError(expected, actual);
172 function filter(key, value) {
173 return (key === 'loc' || key === 'range') ? undefined : value;
176 if (options.tolerant) {
181 // Check again without any location info.
182 options.range = false;
184 syntax = sortedObject(syntax);
185 expected = JSON.stringify(syntax, filter, 4);
187 tree = esprima.parse(code, options);
189 if (options.tolerant) {
190 for (i = 0, len = tree.errors.length; i < len; i += 1) {
191 tree.errors[i] = errorToObject(tree.errors[i]);
194 tree = sortedObject(tree);
195 actual = JSON.stringify(tree, filter, 4);
197 throw new NotMatchingError(expected, e.toString());
199 if (expected !== actual) {
200 throw new NotMatchingError(expected, actual);
204 function testTokenize(esprima, code, tokens) {
206 var options, expected, actual, tree;
215 expected = JSON.stringify(tokens, null, 4);
218 tree = esprima.tokenize(code, options);
219 actual = JSON.stringify(tree, null, 4);
221 throw new NotMatchingError(expected, e.toString());
223 if (expected !== actual) {
224 throw new NotMatchingError(expected, actual);
229 function testModule(esprima, code, exception) {
231 var i, options, expected, actual, err, handleInvalidRegexFlag, tokenize;
233 // Different parsing options should give the same error.
235 { sourceType: 'module' },
236 { sourceType: 'module', comment: true },
237 { sourceType: 'module', raw: true },
238 { sourceType: 'module', raw: true, comment: true }
241 if (!exception.message) {
242 exception.message = 'Error: Line 1: ' + exception.description;
244 exception.description = exception.message.replace(/Error: Line [0-9]+: /, '');
246 expected = JSON.stringify(exception);
248 for (i = 0; i < options.length; i += 1) {
251 esprima.parse(code, options[i]);
253 err = errorToObject(e);
254 err.description = e.description;
255 actual = JSON.stringify(err);
258 if (expected !== actual) {
260 // Compensate for old V8 which does not handle invalid flag.
261 if (exception.message.indexOf('Invalid regular expression') > 0) {
262 if (typeof actual === 'undefined' && !handleInvalidRegexFlag) {
267 throw new NotMatchingError(expected, actual);
273 function testError(esprima, code, exception) {
275 var i, options, expected, actual, err, handleInvalidRegexFlag, tokenize;
277 // Different parsing options should give the same error.
282 { raw: true, comment: true }
285 // If handleInvalidRegexFlag is true, an invalid flag in a regular expression
286 // will throw an exception. In some old version of V8, this is not the case
287 // and hence handleInvalidRegexFlag is false.
288 handleInvalidRegexFlag = false;
290 'test'.match(new RegExp('[a-z]', 'x'));
292 handleInvalidRegexFlag = true;
295 exception.description = exception.message.replace(/Error: Line [0-9]+: /, '');
297 if (exception.tokenize) {
299 exception.tokenize = undefined;
301 expected = JSON.stringify(exception);
303 for (i = 0; i < options.length; i += 1) {
307 esprima.tokenize(code, options[i]);
309 esprima.parse(code, options[i]);
312 err = errorToObject(e);
313 err.description = e.description;
314 actual = JSON.stringify(err);
317 if (expected !== actual) {
319 // Compensate for old V8 which does not handle invalid flag.
320 if (exception.message.indexOf('Invalid regular expression') > 0) {
321 if (typeof actual === 'undefined' && !handleInvalidRegexFlag) {
326 throw new NotMatchingError(expected, actual);
332 function testAPI(esprima, code, expected) {
335 expected = JSON.stringify(expected, null, 4);
338 result = JSON.stringify(result, null, 4);
340 throw new NotMatchingError(expected, e.toString());
342 if (expected !== result) {
343 throw new NotMatchingError(expected, result);
347 function generateTestCase(esprima, testCase) {
348 var tree, fileName = testCase.key + ".tree.json";
350 tree = esprima.parse(testCase.case, {loc: true, range: true});
351 tree = JSON.stringify(tree, null, 4);
353 if (typeof e.index === 'undefined') {
354 console.error("Failed to generate test result.");
357 tree = errorToObject(e);
358 tree.description = e.description;
359 tree = JSON.stringify(tree);
360 fileName = testCase.key + ".failure.json";
362 require('fs').writeFileSync(fileName, tree);
363 console.error("Done.");
366 if (typeof window === 'undefined') {
370 var esprima = require('../esprima'),
373 diff = require('json-diff').diffString,
378 context = {source: '', result: null},
384 function enumerateFixtures(root) {
385 var dirs = fs.readdirSync(root), key, kind,
386 kinds = ['case', 'source', 'module', 'run', 'tree', 'tokens', 'failure', 'result'],
387 suffices = ['js', 'js', 'json', 'js', 'json', 'json', 'json', 'json'];
389 dirs.forEach(function (item) {
391 if (fs.statSync(root + '/' + item).isDirectory()) {
392 enumerateFixtures(root + '/' + item);
395 key = item.slice(0, -3);
396 for (i = 1; i < kinds.length; i++) {
397 var suffix = '.' + kinds[i] + '.' + suffices[i];
398 if (item.slice(-suffix.length) === suffix) {
399 key = item.slice(0, -suffix.length);
403 key = root + '/' + key;
406 cases[key] = { key: key };
408 cases[key][kind] = fs.readFileSync(root + '/' + item, 'utf-8');
413 enumerateFixtures(__dirname + '/fixtures');
415 for (var key in cases) {
416 if (cases.hasOwnProperty(key)) {
417 testCase = cases[key];
419 if (testCase.hasOwnProperty('source')) {
420 testCase.case = eval(testCase.source + ';source');
424 if (testCase.hasOwnProperty('module')) {
425 testModule(esprima, testCase.case, JSON.parse(testCase.module));
426 } else if (testCase.hasOwnProperty('tree')) {
427 testParse(esprima, testCase.case, JSON.parse(testCase.tree));
428 } else if (testCase.hasOwnProperty('tokens')) {
429 testTokenize(esprima, testCase.case, JSON.parse(testCase.tokens));
430 } else if (testCase.hasOwnProperty('failure')) {
431 testError(esprima, testCase.case, JSON.parse(testCase.failure));
432 } else if (testCase.hasOwnProperty('result')) {
433 testAPI(esprima, testCase.run, JSON.parse(testCase.result));
435 console.error('Incomplete test case:' + testCase.key + '. Generating test result...');
436 generateTestCase(esprima, testCase);
442 e.source = testCase.case || testCase.key;
448 tick = (new Date()) - tick;
450 header = total + ' tests. ' + failures.length + ' failures. ' +
452 if (failures.length) {
453 console.error(header);
454 failures.forEach(function (failure) {
456 var expectedObject = JSON.parse(failure.expected);
457 var actualObject = JSON.parse(failure.actual);
459 console.error(failure.source + ': Expected\n ' +
460 failure.expected.split('\n').join('\n ') +
461 '\nto match\n ' + failure.actual + '\nDiff:\n' +
462 diff(expectedObject, actualObject));
464 console.error(failure.source + ': Expected\n ' +
465 failure.expected.split('\n').join('\n ') +
466 '\nto match\n ' + failure.actual);
472 process.exit(failures.length === 0 ? 0 : 1);