UNPKG

25.7 kBJavaScriptView Raw
1/**
2 * @fileoverview Main Espree file that converts Acorn into Esprima output.
3 *
4 * This file contains code from the following MIT-licensed projects:
5 * 1. Acorn
6 * 2. Babylon
7 * 3. Babel-ESLint
8 *
9 * This file also contains code from Esprima, which is BSD licensed.
10 *
11 * Acorn is Copyright 2012-2015 Acorn Contributors (https://github.com/marijnh/acorn/blob/master/AUTHORS)
12 * Babylon is Copyright 2014-2015 various contributors (https://github.com/babel/babel/blob/master/packages/babylon/AUTHORS)
13 * Babel-ESLint is Copyright 2014-2015 Sebastian McKenzie <sebmck@gmail.com>
14 *
15 * Redistribution and use in source and binary forms, with or without
16 * modification, are permitted provided that the following conditions are met:
17 *
18 * * Redistributions of source code must retain the above copyright
19 * notice, this list of conditions and the following disclaimer.
20 * * Redistributions in binary form must reproduce the above copyright
21 * notice, this list of conditions and the following disclaimer in the
22 * documentation and/or other materials provided with the distribution.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
28 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
29 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
31 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
33 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 *
35 * Esprima is Copyright (c) jQuery Foundation, Inc. and Contributors, All Rights Reserved.
36 *
37 * Redistribution and use in source and binary forms, with or without
38 * modification, are permitted provided that the following conditions are met:
39 *
40 * * Redistributions of source code must retain the above copyright
41 * notice, this list of conditions and the following disclaimer.
42 * * Redistributions in binary form must reproduce the above copyright
43 * notice, this list of conditions and the following disclaimer in the
44 * documentation and/or other materials provided with the distribution.
45 *
46 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
47 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
48 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
49 * ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
50 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
51 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
52 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
53 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
54 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
55 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
56 */
57/* eslint no-undefined:0, no-use-before-define: 0 */
58
59"use strict";
60
61var astNodeTypes = require("./lib/ast-node-types"),
62 commentAttachment = require("./lib/comment-attachment"),
63 TokenTranslator = require("./lib/token-translator"),
64 acornJSX = require("acorn-jsx/inject"),
65 rawAcorn = require("acorn");
66
67
68var acorn = acornJSX(rawAcorn);
69var DEFAULT_ECMA_VERSION = 5;
70var lookahead,
71 extra,
72 lastToken;
73
74/**
75 * Object.assign polyfill for Node < 4
76 * @param {Object} target The target object
77 * @param {...Object} sources Sources for the object
78 * @returns {Object} `target` after being mutated
79 */
80var assign = Object.assign || function assign(target) {
81 for (var argIndex = 1; argIndex < arguments.length; argIndex++) {
82 if (arguments[argIndex] !== null && typeof arguments[argIndex] === "object") {
83 var keys = Object.keys(arguments[argIndex]);
84
85 for (var keyIndex = 0; keyIndex < keys.length; keyIndex++) {
86 target[keys[keyIndex]] = arguments[argIndex][keys[keyIndex]];
87 }
88 }
89 }
90
91 return target;
92};
93
94/**
95 * Resets the extra object to its default.
96 * @returns {void}
97 * @private
98 */
99function resetExtra() {
100 extra = {
101 tokens: null,
102 range: false,
103 loc: false,
104 comment: false,
105 comments: [],
106 tolerant: false,
107 errors: [],
108 strict: false,
109 ecmaFeatures: {},
110 ecmaVersion: DEFAULT_ECMA_VERSION,
111 isModule: false
112 };
113}
114
115
116
117var tt = acorn.tokTypes,
118 getLineInfo = acorn.getLineInfo;
119
120// custom type for JSX attribute values
121tt.jsxAttrValueToken = {};
122
123/**
124 * Normalize ECMAScript version from the initial config
125 * @param {number} ecmaVersion ECMAScript version from the initial config
126 * @returns {number} normalized ECMAScript version
127 */
128function normalizeEcmaVersion(ecmaVersion) {
129 if (typeof ecmaVersion === "number") {
130 var version = ecmaVersion;
131
132 // Calculate ECMAScript edition number from official year version starting with
133 // ES2015, which corresponds with ES6 (or a difference of 2009).
134 if (version >= 2015) {
135 version -= 2009;
136 }
137
138 switch (version) {
139 case 3:
140 case 5:
141 case 6:
142 case 7:
143 case 8:
144 case 9:
145 return version;
146
147 default:
148 throw new Error("Invalid ecmaVersion.");
149 }
150 } else {
151 return DEFAULT_ECMA_VERSION;
152 }
153}
154
155/**
156 * Determines if a node is valid given the set of ecmaFeatures.
157 * @param {ASTNode} node The node to check.
158 * @returns {boolean} True if the node is allowed, false if not.
159 * @private
160 */
161function isValidNode(node) {
162 var ecma = extra.ecmaFeatures;
163
164 switch (node.type) {
165 case "ExperimentalSpreadProperty":
166 case "ExperimentalRestProperty":
167 return ecma.experimentalObjectRestSpread;
168
169 case "ImportDeclaration":
170 case "ExportNamedDeclaration":
171 case "ExportDefaultDeclaration":
172 case "ExportAllDeclaration":
173 return extra.isModule;
174
175 default:
176 return true;
177 }
178}
179
180/**
181 * Performs last-minute Esprima-specific compatibility checks and fixes.
182 * @param {ASTNode} result The node to check.
183 * @returns {ASTNode} The finished node.
184 * @private
185 * @this acorn.Parser
186 */
187function esprimaFinishNode(result) {
188 // ensure that parsed node was allowed through ecmaFeatures
189 if (!isValidNode(result)) {
190 this.unexpected(result.start);
191 }
192
193 // https://github.com/marijnh/acorn/issues/323
194 if (result.type === "TryStatement") {
195 delete result.guardedHandlers;
196 } else if (result.type === "CatchClause") {
197 delete result.guard;
198 }
199
200 // Acorn doesn't count the opening and closing backticks as part of templates
201 // so we have to adjust ranges/locations appropriately.
202 if (result.type === "TemplateElement") {
203
204 // additional adjustment needed if ${ is the last token
205 var terminalDollarBraceL = this.input.slice(result.end, result.end + 2) === "${";
206
207 if (result.range) {
208 result.range[0]--;
209 result.range[1] += (terminalDollarBraceL ? 2 : 1);
210 }
211
212 if (result.loc) {
213 result.loc.start.column--;
214 result.loc.end.column += (terminalDollarBraceL ? 2 : 1);
215 }
216 }
217
218 // Acorn uses undefined instead of null, which affects serialization
219 if (result.type === "Literal" && result.value === undefined) {
220 result.value = null;
221 }
222
223 if (extra.attachComment) {
224 commentAttachment.processComment(result);
225 }
226
227 if (result.type.indexOf("Function") > -1 && !result.generator) {
228 result.generator = false;
229 }
230
231 return result;
232}
233
234/**
235 * Determines if a token is valid given the set of ecmaFeatures.
236 * @param {acorn.Parser} parser The parser to check.
237 * @returns {boolean} True if the token is allowed, false if not.
238 * @private
239 */
240function isValidToken(parser) {
241 var ecma = extra.ecmaFeatures;
242 var type = parser.type;
243
244 switch (type) {
245 case tt.jsxName:
246 case tt.jsxText:
247 case tt.jsxTagStart:
248 case tt.jsxTagEnd:
249 return ecma.jsx;
250
251 // https://github.com/ternjs/acorn/issues/363
252 case tt.regexp:
253 if (extra.ecmaVersion < 6 && parser.value.flags && parser.value.flags.indexOf("y") > -1) {
254 return false;
255 }
256
257 return true;
258
259 default:
260 return true;
261 }
262}
263
264/**
265 * Injects esprimaFinishNode into the finishNode process.
266 * @param {Function} finishNode Original finishNode function.
267 * @returns {ASTNode} The finished node.
268 * @private
269 */
270function wrapFinishNode(finishNode) {
271 return /** @this acorn.Parser */ function(node, type, pos, loc) {
272 var result = finishNode.call(this, node, type, pos, loc);
273 return esprimaFinishNode.call(this, result);
274 };
275}
276
277acorn.plugins.espree = function(instance) {
278
279 instance.extend("finishNode", wrapFinishNode);
280
281 instance.extend("finishNodeAt", wrapFinishNode);
282
283 instance.extend("next", function(next) {
284 return /** @this acorn.Parser */ function() {
285 if (!isValidToken(this)) {
286 this.unexpected();
287 }
288 return next.call(this);
289 };
290 });
291
292 // needed for experimental object rest/spread
293 instance.extend("checkLVal", function(checkLVal) {
294
295 return /** @this acorn.Parser */ function(expr, isBinding, checkClashes) {
296
297 if (extra.ecmaFeatures.experimentalObjectRestSpread && expr.type === "ObjectPattern") {
298 for (var i = 0; i < expr.properties.length; i++) {
299 if (expr.properties[i].type.indexOf("Experimental") === -1) {
300 this.checkLVal(expr.properties[i].value, isBinding, checkClashes);
301 }
302 }
303 return undefined;
304 }
305
306 return checkLVal.call(this, expr, isBinding, checkClashes);
307 };
308 });
309
310 instance.extend("parseTopLevel", function(parseTopLevel) {
311 return /** @this acorn.Parser */ function(node) {
312 if (extra.ecmaFeatures.impliedStrict && this.options.ecmaVersion >= 5) {
313 this.strict = true;
314 }
315 return parseTopLevel.call(this, node);
316 };
317 });
318
319 instance.extend("toAssignable", function(toAssignable) {
320
321 return /** @this acorn.Parser */ function(node, isBinding, refDestructuringErrors) {
322
323 if (extra.ecmaFeatures.experimentalObjectRestSpread &&
324 node.type === "ObjectExpression"
325 ) {
326 node.type = "ObjectPattern";
327
328 for (var i = 0; i < node.properties.length; i++) {
329 var prop = node.properties[i];
330
331 if (prop.type === "ExperimentalSpreadProperty") {
332 prop.type = "ExperimentalRestProperty";
333 } else if (prop.kind !== "init") {
334 this.raise(prop.key.start, "Object pattern can't contain getter or setter");
335 } else {
336 this.toAssignable(prop.value, isBinding);
337 }
338 }
339
340 return node;
341 } else {
342 return toAssignable.call(this, node, isBinding, refDestructuringErrors);
343 }
344 };
345
346 });
347
348 /**
349 * Method to parse an object rest or object spread.
350 * @returns {ASTNode} The node representing object rest or object spread.
351 * @this acorn.Parser
352 */
353 instance.parseObjectRest = function() {
354 var node = this.startNode();
355 this.next();
356 node.argument = this.parseIdent();
357
358 if (this.type === tt.comma) {
359 this.raise(this.start, "Unexpected trailing comma after rest property");
360 }
361
362 return this.finishNode(node, "ExperimentalRestProperty");
363 };
364
365 instance.extend("parseProperty", function(parseProperty) {
366 /**
367 * Override `parseProperty` method to parse rest/spread properties.
368 * @param {boolean} isPattern True if the object is a destructuring pattern.
369 * @param {Object} refDestructuringErrors ?
370 * @returns {ASTNode} The node representing a rest/spread property.
371 * @this acorn.Parser
372 */
373 return function(isPattern, refDestructuringErrors) {
374 if (extra.ecmaFeatures.experimentalObjectRestSpread && this.type === tt.ellipsis) {
375 var prop;
376
377 if (isPattern) {
378 prop = this.parseObjectRest();
379 } else {
380 prop = this.parseSpread();
381 prop.type = "ExperimentalSpreadProperty";
382 }
383
384 return prop;
385 }
386
387 return parseProperty.call(this, isPattern, refDestructuringErrors);
388 };
389 });
390
391 instance.extend("checkPropClash", function(checkPropClash) {
392 /**
393 * Override `checkPropClash` method to avoid clash on rest/spread properties.
394 * @param {ASTNode} prop A property node to check.
395 * @param {Object} propHash Names map.
396 * @param {Object} refDestructuringErrors Destructuring error information.
397 * @returns {void}
398 * @this acorn.Parser
399 */
400 return function(prop, propHash, refDestructuringErrors) {
401 if (prop.type === "ExperimentalRestProperty" || prop.type === "ExperimentalSpreadProperty") {
402 return;
403 }
404 checkPropClash.call(this, prop, propHash, refDestructuringErrors);
405 };
406 });
407
408 /**
409 * Overwrites the default raise method to throw Esprima-style errors.
410 * @param {int} pos The position of the error.
411 * @param {string} message The error message.
412 * @throws {SyntaxError} A syntax error.
413 * @returns {void}
414 */
415 instance.raise = instance.raiseRecoverable = function(pos, message) {
416 var loc = getLineInfo(this.input, pos);
417 var err = new SyntaxError(message);
418 err.index = pos;
419 err.lineNumber = loc.line;
420 err.column = loc.column + 1; // acorn uses 0-based columns
421 throw err;
422 };
423
424 /**
425 * Overwrites the default unexpected method to throw Esprima-style errors.
426 * @param {int} pos The position of the error.
427 * @throws {SyntaxError} A syntax error.
428 * @returns {void}
429 */
430 instance.unexpected = function(pos) {
431 var message = "Unexpected token";
432
433 if (pos !== null && pos !== undefined) {
434 this.pos = pos;
435
436 if (this.options.locations) {
437 while (this.pos < this.lineStart) {
438 this.lineStart = this.input.lastIndexOf("\n", this.lineStart - 2) + 1;
439 --this.curLine;
440 }
441 }
442
443 this.nextToken();
444 }
445
446 if (this.end > this.start) {
447 message += " " + this.input.slice(this.start, this.end);
448 }
449
450 this.raise(this.start, message);
451 };
452
453 /*
454 * Esprima-FB represents JSX strings as tokens called "JSXText", but Acorn-JSX
455 * uses regular tt.string without any distinction between this and regular JS
456 * strings. As such, we intercept an attempt to read a JSX string and set a flag
457 * on extra so that when tokens are converted, the next token will be switched
458 * to JSXText via onToken.
459 */
460 instance.extend("jsx_readString", function(jsxReadString) {
461 return /** @this acorn.Parser */ function(quote) {
462 var result = jsxReadString.call(this, quote);
463 if (this.type === tt.string) {
464 extra.jsxAttrValueToken = true;
465 }
466
467 return result;
468 };
469 });
470};
471
472//------------------------------------------------------------------------------
473// Tokenizer
474//------------------------------------------------------------------------------
475
476/**
477 * Tokenizes the given code.
478 * @param {string} code The code to tokenize.
479 * @param {Object} options Options defining how to tokenize.
480 * @returns {Token[]} An array of tokens.
481 * @throws {SyntaxError} If the input code is invalid.
482 * @private
483 */
484function tokenize(code, options) {
485 var toString,
486 tokens,
487 impliedStrict,
488 translator = new TokenTranslator(tt, code);
489
490 toString = String;
491 if (typeof code !== "string" && !(code instanceof String)) {
492 code = toString(code);
493 }
494
495 lookahead = null;
496
497 // Options matching.
498 options = assign({}, options);
499
500 var acornOptions = {
501 ecmaVersion: DEFAULT_ECMA_VERSION,
502 plugins: {
503 espree: true
504 }
505 };
506
507 resetExtra();
508
509 // Of course we collect tokens here.
510 options.tokens = true;
511 extra.tokens = [];
512
513 extra.range = (typeof options.range === "boolean") && options.range;
514 acornOptions.ranges = extra.range;
515
516 extra.loc = (typeof options.loc === "boolean") && options.loc;
517 acornOptions.locations = extra.loc;
518
519 extra.comment = typeof options.comment === "boolean" && options.comment;
520
521 if (extra.comment) {
522 acornOptions.onComment = function() {
523 var comment = convertAcornCommentToEsprimaComment.apply(this, arguments);
524 extra.comments.push(comment);
525 };
526 }
527
528 extra.tolerant = typeof options.tolerant === "boolean" && options.tolerant;
529
530 acornOptions.ecmaVersion = extra.ecmaVersion = normalizeEcmaVersion(options.ecmaVersion);
531
532 // apply parsing flags
533 if (options.ecmaFeatures && typeof options.ecmaFeatures === "object") {
534 extra.ecmaFeatures = assign({}, options.ecmaFeatures);
535 impliedStrict = extra.ecmaFeatures.impliedStrict;
536 extra.ecmaFeatures.impliedStrict = typeof impliedStrict === "boolean" && impliedStrict;
537 }
538
539 try {
540 var tokenizer = acorn.tokenizer(code, acornOptions);
541 while ((lookahead = tokenizer.getToken()).type !== tt.eof) {
542 translator.onToken(lookahead, extra);
543 }
544
545 // filterTokenLocation();
546 tokens = extra.tokens;
547
548 if (extra.comment) {
549 tokens.comments = extra.comments;
550 }
551 if (extra.tolerant) {
552 tokens.errors = extra.errors;
553 }
554 } catch (e) {
555 throw e;
556 }
557 return tokens;
558}
559
560//------------------------------------------------------------------------------
561// Parser
562//------------------------------------------------------------------------------
563
564
565
566/**
567 * Converts an Acorn comment to a Esprima comment.
568 * @param {boolean} block True if it's a block comment, false if not.
569 * @param {string} text The text of the comment.
570 * @param {int} start The index at which the comment starts.
571 * @param {int} end The index at which the comment ends.
572 * @param {Location} startLoc The location at which the comment starts.
573 * @param {Location} endLoc The location at which the comment ends.
574 * @returns {Object} The comment object.
575 * @private
576 */
577function convertAcornCommentToEsprimaComment(block, text, start, end, startLoc, endLoc) {
578 var comment = {
579 type: block ? "Block" : "Line",
580 value: text
581 };
582
583 if (typeof start === "number") {
584 comment.start = start;
585 comment.end = end;
586 comment.range = [start, end];
587 }
588
589 if (typeof startLoc === "object") {
590 comment.loc = {
591 start: startLoc,
592 end: endLoc
593 };
594 }
595
596 return comment;
597}
598
599/**
600 * Parses the given code.
601 * @param {string} code The code to tokenize.
602 * @param {Object} options Options defining how to tokenize.
603 * @returns {ASTNode} The "Program" AST node.
604 * @throws {SyntaxError} If the input code is invalid.
605 * @private
606 */
607function parse(code, options) {
608 var program,
609 toString = String,
610 translator,
611 impliedStrict,
612 acornOptions = {
613 ecmaVersion: DEFAULT_ECMA_VERSION,
614 plugins: {
615 espree: true
616 }
617 };
618
619 lastToken = null;
620
621 if (typeof code !== "string" && !(code instanceof String)) {
622 code = toString(code);
623 }
624
625 resetExtra();
626 commentAttachment.reset();
627
628 if (typeof options !== "undefined") {
629 extra.range = (typeof options.range === "boolean") && options.range;
630 extra.loc = (typeof options.loc === "boolean") && options.loc;
631 extra.attachComment = (typeof options.attachComment === "boolean") && options.attachComment;
632
633 if (extra.loc && options.source !== null && options.source !== undefined) {
634 extra.source = toString(options.source);
635 }
636
637 if (typeof options.tokens === "boolean" && options.tokens) {
638 extra.tokens = [];
639 translator = new TokenTranslator(tt, code);
640 }
641 if (typeof options.comment === "boolean" && options.comment) {
642 extra.comment = true;
643 extra.comments = [];
644 }
645 if (typeof options.tolerant === "boolean" && options.tolerant) {
646 extra.errors = [];
647 }
648 if (extra.attachComment) {
649 extra.range = true;
650 extra.comments = [];
651 commentAttachment.reset();
652 }
653
654 acornOptions.ecmaVersion = extra.ecmaVersion = normalizeEcmaVersion(options.ecmaVersion);
655
656 if (options.sourceType === "module") {
657 extra.isModule = true;
658
659 // modules must be in 6 at least
660 if (acornOptions.ecmaVersion < 6) {
661 acornOptions.ecmaVersion = 6;
662 extra.ecmaVersion = 6;
663 }
664
665 acornOptions.sourceType = "module";
666 }
667
668 // apply parsing flags after sourceType to allow overriding
669 if (options.ecmaFeatures && typeof options.ecmaFeatures === "object") {
670 extra.ecmaFeatures = assign({}, options.ecmaFeatures);
671 impliedStrict = extra.ecmaFeatures.impliedStrict;
672 extra.ecmaFeatures.impliedStrict = typeof impliedStrict === "boolean" && impliedStrict;
673 if (options.ecmaFeatures.globalReturn) {
674 acornOptions.allowReturnOutsideFunction = true;
675 }
676 }
677
678
679 acornOptions.onToken = function(token) {
680 if (extra.tokens) {
681 translator.onToken(token, extra);
682 }
683 if (token.type !== tt.eof) {
684 lastToken = token;
685 }
686 };
687
688 if (extra.attachComment || extra.comment) {
689 acornOptions.onComment = function() {
690 var comment = convertAcornCommentToEsprimaComment.apply(this, arguments);
691 extra.comments.push(comment);
692
693 if (extra.attachComment) {
694 commentAttachment.addComment(comment);
695 }
696 };
697 }
698
699 if (extra.range) {
700 acornOptions.ranges = true;
701 }
702
703 if (extra.loc) {
704 acornOptions.locations = true;
705 }
706
707 if (extra.ecmaFeatures.jsx) {
708 // Should process jsx plugin before espree plugin.
709 acornOptions.plugins = {
710 jsx: true,
711 espree: true
712 };
713 }
714 }
715
716 program = acorn.parse(code, acornOptions);
717 program.sourceType = extra.isModule ? "module" : "script";
718
719 if (extra.comment || extra.attachComment) {
720 program.comments = extra.comments;
721 }
722
723 if (extra.tokens) {
724 program.tokens = extra.tokens;
725 }
726
727 /*
728 * Adjust opening and closing position of program to match Esprima.
729 * Acorn always starts programs at range 0 whereas Esprima starts at the
730 * first AST node's start (the only real difference is when there's leading
731 * whitespace or leading comments). Acorn also counts trailing whitespace
732 * as part of the program whereas Esprima only counts up to the last token.
733 */
734 if (program.range) {
735 program.range[0] = program.body.length ? program.body[0].range[0] : program.range[0];
736 program.range[1] = lastToken ? lastToken.range[1] : program.range[1];
737 }
738
739 if (program.loc) {
740 program.loc.start = program.body.length ? program.body[0].loc.start : program.loc.start;
741 program.loc.end = lastToken ? lastToken.loc.end : program.loc.end;
742 }
743
744 return program;
745}
746
747//------------------------------------------------------------------------------
748// Public
749//------------------------------------------------------------------------------
750
751exports.version = require("./package.json").version;
752
753exports.tokenize = tokenize;
754
755exports.parse = parse;
756
757// Deep copy.
758/* istanbul ignore next */
759exports.Syntax = (function() {
760 var name, types = {};
761
762 if (typeof Object.create === "function") {
763 types = Object.create(null);
764 }
765
766 for (name in astNodeTypes) {
767 if (astNodeTypes.hasOwnProperty(name)) {
768 types[name] = astNodeTypes[name];
769 }
770 }
771
772 if (typeof Object.freeze === "function") {
773 Object.freeze(types);
774 }
775
776 return types;
777}());
778
779/* istanbul ignore next */
780exports.VisitorKeys = (function() {
781 var visitorKeys = require("./lib/visitor-keys");
782 var name,
783 keys = {};
784
785 if (typeof Object.create === "function") {
786 keys = Object.create(null);
787 }
788
789 for (name in visitorKeys) {
790 if (visitorKeys.hasOwnProperty(name)) {
791 keys[name] = visitorKeys[name];
792 }
793 }
794
795 if (typeof Object.freeze === "function") {
796 Object.freeze(keys);
797 }
798
799 return keys;
800}());