UNPKG

27.2 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 lineBreak = acorn.lineBreak;
120
121// custom type for JSX attribute values
122tt.jsxAttrValueToken = {};
123
124/**
125 * Normalize ECMAScript version from the initial config
126 * @param {number} ecmaVersion ECMAScript version from the initial config
127 * @returns {number} normalized ECMAScript version
128 */
129function normalizeEcmaVersion(ecmaVersion) {
130 if (typeof ecmaVersion === "number") {
131 var version = ecmaVersion;
132
133 // Calculate ECMAScript edition number from official year version starting with
134 // ES2015, which corresponds with ES6 (or a difference of 2009).
135 if (version >= 2015) {
136 version -= 2009;
137 }
138
139 switch (version) {
140 case 3:
141 case 5:
142 case 6:
143 case 7:
144 case 8:
145 case 9:
146 return version;
147
148 default:
149 throw new Error("Invalid ecmaVersion.");
150 }
151 } else {
152 return DEFAULT_ECMA_VERSION;
153 }
154}
155
156/**
157 * Determines if a node is valid given the set of ecmaFeatures.
158 * @param {ASTNode} node The node to check.
159 * @returns {boolean} True if the node is allowed, false if not.
160 * @private
161 */
162function isValidNode(node) {
163 var ecma = extra.ecmaFeatures;
164
165 switch (node.type) {
166 case "ExperimentalSpreadProperty":
167 case "ExperimentalRestProperty":
168 return ecma.experimentalObjectRestSpread;
169
170 case "ImportDeclaration":
171 case "ExportNamedDeclaration":
172 case "ExportDefaultDeclaration":
173 case "ExportAllDeclaration":
174 return extra.isModule;
175
176 default:
177 return true;
178 }
179}
180
181/**
182 * Performs last-minute Esprima-specific compatibility checks and fixes.
183 * @param {ASTNode} result The node to check.
184 * @returns {ASTNode} The finished node.
185 * @private
186 * @this acorn.Parser
187 */
188function esprimaFinishNode(result) {
189 // ensure that parsed node was allowed through ecmaFeatures
190 if (!isValidNode(result)) {
191 this.unexpected(result.start);
192 }
193
194 // https://github.com/marijnh/acorn/issues/323
195 if (result.type === "TryStatement") {
196 delete result.guardedHandlers;
197 } else if (result.type === "CatchClause") {
198 delete result.guard;
199 }
200
201 // Acorn doesn't count the opening and closing backticks as part of templates
202 // so we have to adjust ranges/locations appropriately.
203 if (result.type === "TemplateElement") {
204
205 // additional adjustment needed if ${ is the last token
206 var terminalDollarBraceL = this.input.slice(result.end, result.end + 2) === "${";
207
208 if (result.range) {
209 result.range[0]--;
210 result.range[1] += (terminalDollarBraceL ? 2 : 1);
211 }
212
213 if (result.loc) {
214 result.loc.start.column--;
215 result.loc.end.column += (terminalDollarBraceL ? 2 : 1);
216 }
217 }
218
219 // Acorn uses undefined instead of null, which affects serialization
220 if (result.type === "Literal" && result.value === undefined) {
221 result.value = null;
222 }
223
224 if (extra.attachComment) {
225 commentAttachment.processComment(result);
226 }
227
228 if (result.type.indexOf("Function") > -1 && !result.generator) {
229 result.generator = false;
230 }
231
232 return result;
233}
234
235/**
236 * Determines if a token is valid given the set of ecmaFeatures.
237 * @param {acorn.Parser} parser The parser to check.
238 * @returns {boolean} True if the token is allowed, false if not.
239 * @private
240 */
241function isValidToken(parser) {
242 var ecma = extra.ecmaFeatures;
243 var type = parser.type;
244
245 switch (type) {
246 case tt.jsxName:
247 case tt.jsxText:
248 case tt.jsxTagStart:
249 case tt.jsxTagEnd:
250 return ecma.jsx;
251
252 // https://github.com/ternjs/acorn/issues/363
253 case tt.regexp:
254 if (extra.ecmaVersion < 6 && parser.value.flags && parser.value.flags.indexOf("y") > -1) {
255 return false;
256 }
257
258 return true;
259
260 default:
261 return true;
262 }
263}
264
265/**
266 * Injects esprimaFinishNode into the finishNode process.
267 * @param {Function} finishNode Original finishNode function.
268 * @returns {ASTNode} The finished node.
269 * @private
270 */
271function wrapFinishNode(finishNode) {
272 return /** @this acorn.Parser */ function(node, type, pos, loc) {
273 var result = finishNode.call(this, node, type, pos, loc);
274 return esprimaFinishNode.call(this, result);
275 };
276}
277
278acorn.plugins.espree = function(instance) {
279
280 instance.extend("finishNode", wrapFinishNode);
281
282 instance.extend("finishNodeAt", wrapFinishNode);
283
284 instance.extend("next", function(next) {
285 return /** @this acorn.Parser */ function() {
286 if (!isValidToken(this)) {
287 this.unexpected();
288 }
289 return next.call(this);
290 };
291 });
292
293 // needed for experimental object rest/spread
294 instance.extend("checkLVal", function(checkLVal) {
295
296 return /** @this acorn.Parser */ function(expr, isBinding, checkClashes) {
297
298 if (extra.ecmaFeatures.experimentalObjectRestSpread && expr.type === "ObjectPattern") {
299 for (var i = 0; i < expr.properties.length; i++) {
300 if (expr.properties[i].type.indexOf("Experimental") === -1) {
301 this.checkLVal(expr.properties[i].value, isBinding, checkClashes);
302 }
303 }
304 return undefined;
305 }
306
307 return checkLVal.call(this, expr, isBinding, checkClashes);
308 };
309 });
310
311 instance.extend("parseTopLevel", function(parseTopLevel) {
312 return /** @this acorn.Parser */ function(node) {
313 if (extra.ecmaFeatures.impliedStrict && this.options.ecmaVersion >= 5) {
314 this.strict = true;
315 }
316 return parseTopLevel.call(this, node);
317 };
318 });
319
320 instance.extend("toAssignable", function(toAssignable) {
321
322 return /** @this acorn.Parser */ function(node, isBinding) {
323
324 if (extra.ecmaFeatures.experimentalObjectRestSpread &&
325 node.type === "ObjectExpression"
326 ) {
327 node.type = "ObjectPattern";
328
329 for (var i = 0; i < node.properties.length; i++) {
330 var prop = node.properties[i];
331
332 if (prop.type === "ExperimentalSpreadProperty") {
333 prop.type = "ExperimentalRestProperty";
334 } else if (prop.kind !== "init") {
335 this.raise(prop.key.start, "Object pattern can't contain getter or setter");
336 } else {
337 this.toAssignable(prop.value, isBinding);
338 }
339 }
340
341 return node;
342 } else {
343 return toAssignable.call(this, node, isBinding);
344 }
345 };
346
347 });
348
349 /**
350 * Method to parse an object rest or object spread.
351 * @returns {ASTNode} The node representing object rest or object spread.
352 * @this acorn.Parser
353 */
354 instance.parseObjectRest = function() {
355 var node = this.startNode();
356 this.next();
357 node.argument = this.parseIdent();
358 return this.finishNode(node, "ExperimentalRestProperty");
359 };
360
361 /**
362 * Method to parse an object with object rest or object spread.
363 * @param {boolean} isPattern True if the object is a destructuring pattern.
364 * @param {Object} refShorthandDefaultPos ?
365 * @returns {ASTNode} The node representing object rest or object spread.
366 * @this acorn.Parser
367 */
368 instance.parseObj = function(isPattern, refShorthandDefaultPos) {
369 var node = this.startNode(),
370 first = true,
371 hasRestProperty = false,
372 propHash = {};
373 node.properties = [];
374 this.next();
375 while (!this.eat(tt.braceR)) {
376
377 if (!first) {
378 this.expect(tt.comma);
379
380 if (this.afterTrailingComma(tt.braceR)) {
381 if (hasRestProperty) {
382 this.raise(node.properties[node.properties.length - 1].end, "Unexpected trailing comma after rest property");
383 }
384 break;
385 }
386
387 } else {
388 first = false;
389 }
390
391 var prop = this.startNode(),
392 isGenerator,
393 isAsync,
394 startPos,
395 startLoc;
396
397 if (extra.ecmaFeatures.experimentalObjectRestSpread && this.type === tt.ellipsis) {
398 if (isPattern) {
399 prop = this.parseObjectRest();
400 hasRestProperty = true;
401 } else {
402 prop = this.parseSpread();
403 prop.type = "ExperimentalSpreadProperty";
404 }
405
406 node.properties.push(prop);
407 continue;
408 }
409
410 if (this.options.ecmaVersion >= 6) {
411 prop.method = false;
412 prop.shorthand = false;
413
414 if (isPattern || refShorthandDefaultPos) {
415 startPos = this.start;
416 startLoc = this.startLoc;
417 }
418
419 if (!isPattern) {
420 isGenerator = this.eat(tt.star);
421 }
422 }
423
424 // grab the property name or "async"
425 this.parsePropertyName(prop, refShorthandDefaultPos);
426 if (this.options.ecmaVersion >= 8 &&
427 !isPattern &&
428 !isGenerator &&
429 !prop.computed &&
430 prop.key.type === "Identifier" &&
431 prop.key.name === "async" &&
432 (
433 this.type === tt.name ||
434 this.type === tt.num ||
435 this.type === tt.string ||
436 this.type === tt.bracketL ||
437 this.type.keyword
438 ) &&
439 !lineBreak.test(this.input.slice(this.lastTokEnd, this.start))
440 ) {
441 this.parsePropertyName(prop, refShorthandDefaultPos);
442 isAsync = true;
443 } else {
444 isAsync = false;
445 }
446
447 this.parsePropertyValue(prop, isPattern, isGenerator, isAsync, startPos, startLoc, refShorthandDefaultPos);
448 this.checkPropClash(prop, propHash);
449 node.properties.push(this.finishNode(prop, "Property"));
450 }
451
452 return this.finishNode(node, isPattern ? "ObjectPattern" : "ObjectExpression");
453 };
454
455 /**
456 * Overwrites the default raise method to throw Esprima-style errors.
457 * @param {int} pos The position of the error.
458 * @param {string} message The error message.
459 * @throws {SyntaxError} A syntax error.
460 * @returns {void}
461 */
462 instance.raise = instance.raiseRecoverable = function(pos, message) {
463 var loc = getLineInfo(this.input, pos);
464 var err = new SyntaxError(message);
465 err.index = pos;
466 err.lineNumber = loc.line;
467 err.column = loc.column + 1; // acorn uses 0-based columns
468 throw err;
469 };
470
471 /**
472 * Overwrites the default unexpected method to throw Esprima-style errors.
473 * @param {int} pos The position of the error.
474 * @throws {SyntaxError} A syntax error.
475 * @returns {void}
476 */
477 instance.unexpected = function(pos) {
478 var message = "Unexpected token";
479
480 if (pos !== null && pos !== undefined) {
481 this.pos = pos;
482
483 if (this.options.locations) {
484 while (this.pos < this.lineStart) {
485 this.lineStart = this.input.lastIndexOf("\n", this.lineStart - 2) + 1;
486 --this.curLine;
487 }
488 }
489
490 this.nextToken();
491 }
492
493 if (this.end > this.start) {
494 message += " " + this.input.slice(this.start, this.end);
495 }
496
497 this.raise(this.start, message);
498 };
499
500 /*
501 * Esprima-FB represents JSX strings as tokens called "JSXText", but Acorn-JSX
502 * uses regular tt.string without any distinction between this and regular JS
503 * strings. As such, we intercept an attempt to read a JSX string and set a flag
504 * on extra so that when tokens are converted, the next token will be switched
505 * to JSXText via onToken.
506 */
507 instance.extend("jsx_readString", function(jsxReadString) {
508 return /** @this acorn.Parser */ function(quote) {
509 var result = jsxReadString.call(this, quote);
510 if (this.type === tt.string) {
511 extra.jsxAttrValueToken = true;
512 }
513
514 return result;
515 };
516 });
517};
518
519//------------------------------------------------------------------------------
520// Tokenizer
521//------------------------------------------------------------------------------
522
523/**
524 * Tokenizes the given code.
525 * @param {string} code The code to tokenize.
526 * @param {Object} options Options defining how to tokenize.
527 * @returns {Token[]} An array of tokens.
528 * @throws {SyntaxError} If the input code is invalid.
529 * @private
530 */
531function tokenize(code, options) {
532 var toString,
533 tokens,
534 impliedStrict,
535 translator = new TokenTranslator(tt, code);
536
537 toString = String;
538 if (typeof code !== "string" && !(code instanceof String)) {
539 code = toString(code);
540 }
541
542 lookahead = null;
543
544 // Options matching.
545 options = assign({}, options);
546
547 var acornOptions = {
548 ecmaVersion: DEFAULT_ECMA_VERSION,
549 plugins: {
550 espree: true
551 }
552 };
553
554 resetExtra();
555
556 // Of course we collect tokens here.
557 options.tokens = true;
558 extra.tokens = [];
559
560 extra.range = (typeof options.range === "boolean") && options.range;
561 acornOptions.ranges = extra.range;
562
563 extra.loc = (typeof options.loc === "boolean") && options.loc;
564 acornOptions.locations = extra.loc;
565
566 extra.comment = typeof options.comment === "boolean" && options.comment;
567
568 if (extra.comment) {
569 acornOptions.onComment = function() {
570 var comment = convertAcornCommentToEsprimaComment.apply(this, arguments);
571 extra.comments.push(comment);
572 };
573 }
574
575 extra.tolerant = typeof options.tolerant === "boolean" && options.tolerant;
576
577 acornOptions.ecmaVersion = extra.ecmaVersion = normalizeEcmaVersion(options.ecmaVersion);
578
579 // apply parsing flags
580 if (options.ecmaFeatures && typeof options.ecmaFeatures === "object") {
581 extra.ecmaFeatures = assign({}, options.ecmaFeatures);
582 impliedStrict = extra.ecmaFeatures.impliedStrict;
583 extra.ecmaFeatures.impliedStrict = typeof impliedStrict === "boolean" && impliedStrict;
584 }
585
586 try {
587 var tokenizer = acorn.tokenizer(code, acornOptions);
588 while ((lookahead = tokenizer.getToken()).type !== tt.eof) {
589 translator.onToken(lookahead, extra);
590 }
591
592 // filterTokenLocation();
593 tokens = extra.tokens;
594
595 if (extra.comment) {
596 tokens.comments = extra.comments;
597 }
598 if (extra.tolerant) {
599 tokens.errors = extra.errors;
600 }
601 } catch (e) {
602 throw e;
603 }
604 return tokens;
605}
606
607//------------------------------------------------------------------------------
608// Parser
609//------------------------------------------------------------------------------
610
611
612
613/**
614 * Converts an Acorn comment to a Esprima comment.
615 * @param {boolean} block True if it's a block comment, false if not.
616 * @param {string} text The text of the comment.
617 * @param {int} start The index at which the comment starts.
618 * @param {int} end The index at which the comment ends.
619 * @param {Location} startLoc The location at which the comment starts.
620 * @param {Location} endLoc The location at which the comment ends.
621 * @returns {Object} The comment object.
622 * @private
623 */
624function convertAcornCommentToEsprimaComment(block, text, start, end, startLoc, endLoc) {
625 var comment = {
626 type: block ? "Block" : "Line",
627 value: text
628 };
629
630 if (typeof start === "number") {
631 comment.start = start;
632 comment.end = end;
633 comment.range = [start, end];
634 }
635
636 if (typeof startLoc === "object") {
637 comment.loc = {
638 start: startLoc,
639 end: endLoc
640 };
641 }
642
643 return comment;
644}
645
646/**
647 * Parses the given code.
648 * @param {string} code The code to tokenize.
649 * @param {Object} options Options defining how to tokenize.
650 * @returns {ASTNode} The "Program" AST node.
651 * @throws {SyntaxError} If the input code is invalid.
652 * @private
653 */
654function parse(code, options) {
655 var program,
656 toString = String,
657 translator,
658 impliedStrict,
659 acornOptions = {
660 ecmaVersion: DEFAULT_ECMA_VERSION,
661 plugins: {
662 espree: true
663 }
664 };
665
666 lastToken = null;
667
668 if (typeof code !== "string" && !(code instanceof String)) {
669 code = toString(code);
670 }
671
672 resetExtra();
673 commentAttachment.reset();
674
675 if (typeof options !== "undefined") {
676 extra.range = (typeof options.range === "boolean") && options.range;
677 extra.loc = (typeof options.loc === "boolean") && options.loc;
678 extra.attachComment = (typeof options.attachComment === "boolean") && options.attachComment;
679
680 if (extra.loc && options.source !== null && options.source !== undefined) {
681 extra.source = toString(options.source);
682 }
683
684 if (typeof options.tokens === "boolean" && options.tokens) {
685 extra.tokens = [];
686 translator = new TokenTranslator(tt, code);
687 }
688 if (typeof options.comment === "boolean" && options.comment) {
689 extra.comment = true;
690 extra.comments = [];
691 }
692 if (typeof options.tolerant === "boolean" && options.tolerant) {
693 extra.errors = [];
694 }
695 if (extra.attachComment) {
696 extra.range = true;
697 extra.comments = [];
698 commentAttachment.reset();
699 }
700
701 acornOptions.ecmaVersion = extra.ecmaVersion = normalizeEcmaVersion(options.ecmaVersion);
702
703 if (options.sourceType === "module") {
704 extra.isModule = true;
705
706 // modules must be in 6 at least
707 if (acornOptions.ecmaVersion < 6) {
708 acornOptions.ecmaVersion = 6;
709 extra.ecmaVersion = 6;
710 }
711
712 acornOptions.sourceType = "module";
713 }
714
715 // apply parsing flags after sourceType to allow overriding
716 if (options.ecmaFeatures && typeof options.ecmaFeatures === "object") {
717 extra.ecmaFeatures = assign({}, options.ecmaFeatures);
718 impliedStrict = extra.ecmaFeatures.impliedStrict;
719 extra.ecmaFeatures.impliedStrict = typeof impliedStrict === "boolean" && impliedStrict;
720 if (options.ecmaFeatures.globalReturn) {
721 acornOptions.allowReturnOutsideFunction = true;
722 }
723 }
724
725
726 acornOptions.onToken = function(token) {
727 if (extra.tokens) {
728 translator.onToken(token, extra);
729 }
730 if (token.type !== tt.eof) {
731 lastToken = token;
732 }
733 };
734
735 if (extra.attachComment || extra.comment) {
736 acornOptions.onComment = function() {
737 var comment = convertAcornCommentToEsprimaComment.apply(this, arguments);
738 extra.comments.push(comment);
739
740 if (extra.attachComment) {
741 commentAttachment.addComment(comment);
742 }
743 };
744 }
745
746 if (extra.range) {
747 acornOptions.ranges = true;
748 }
749
750 if (extra.loc) {
751 acornOptions.locations = true;
752 }
753
754 if (extra.ecmaFeatures.jsx) {
755 // Should process jsx plugin before espree plugin.
756 acornOptions.plugins = {
757 jsx: true,
758 espree: true
759 };
760 }
761 }
762
763 program = acorn.parse(code, acornOptions);
764 program.sourceType = extra.isModule ? "module" : "script";
765
766 if (extra.comment || extra.attachComment) {
767 program.comments = extra.comments;
768 }
769
770 if (extra.tokens) {
771 program.tokens = extra.tokens;
772 }
773
774 /*
775 * Adjust opening and closing position of program to match Esprima.
776 * Acorn always starts programs at range 0 whereas Esprima starts at the
777 * first AST node's start (the only real difference is when there's leading
778 * whitespace or leading comments). Acorn also counts trailing whitespace
779 * as part of the program whereas Esprima only counts up to the last token.
780 */
781 if (program.range) {
782 program.range[0] = program.body.length ? program.body[0].range[0] : program.range[0];
783 program.range[1] = lastToken ? lastToken.range[1] : program.range[1];
784 }
785
786 if (program.loc) {
787 program.loc.start = program.body.length ? program.body[0].loc.start : program.loc.start;
788 program.loc.end = lastToken ? lastToken.loc.end : program.loc.end;
789 }
790
791 return program;
792}
793
794//------------------------------------------------------------------------------
795// Public
796//------------------------------------------------------------------------------
797
798exports.version = require("./package.json").version;
799
800exports.tokenize = tokenize;
801
802exports.parse = parse;
803
804// Deep copy.
805/* istanbul ignore next */
806exports.Syntax = (function() {
807 var name, types = {};
808
809 if (typeof Object.create === "function") {
810 types = Object.create(null);
811 }
812
813 for (name in astNodeTypes) {
814 if (astNodeTypes.hasOwnProperty(name)) {
815 types[name] = astNodeTypes[name];
816 }
817 }
818
819 if (typeof Object.freeze === "function") {
820 Object.freeze(types);
821 }
822
823 return types;
824}());
825
826/* istanbul ignore next */
827exports.VisitorKeys = (function() {
828 var visitorKeys = require("./lib/visitor-keys");
829 var name,
830 keys = {};
831
832 if (typeof Object.create === "function") {
833 keys = Object.create(null);
834 }
835
836 for (name in visitorKeys) {
837 if (visitorKeys.hasOwnProperty(name)) {
838 keys[name] = visitorKeys[name];
839 }
840 }
841
842 if (typeof Object.freeze === "function") {
843 Object.freeze(keys);
844 }
845
846 return keys;
847}());