more architectural changes

This commit is contained in:
Sebastian McKenzie 2015-07-11 20:56:26 +01:00
parent 423d8c510d
commit ff6620c8ea
18 changed files with 227 additions and 214 deletions

View File

@ -3,5 +3,13 @@
</p>
<p align="center">
Babylon is a streaming parser for <a href="https://github.com/babel/babel">Babel</a>.
Babylon is a JavaScript parser used in <a href="https://github.com/babel/babel">Babel</a>.
</p>
----
## Credits
Heavily based on [acorn](https://github.com/marijnh/acorn) and [acorn-jsx](https://github.com/RReverser/acorn-jsx).
Significant diversions expected to occur in the future such as streaming, EBNF definitions, sweet.js integration,
interspacial parsing, comment attachment etc.

View File

@ -5,8 +5,5 @@
"homepage": "https://babeljs.io/",
"license": "MIT",
"repository": "babel/babel",
"main": "lib/index.js",
"dependencies": {
"bluebird": "^2.9.33"
}
"main": "lib/index.js"
}

View File

@ -16,10 +16,9 @@
//
// [opp]: http://en.wikipedia.org/wiki/Operator-precedence_parser
import {types as tt} from "./tokentype";
import {Parser} from "./state";
import {reservedWords} from "./identifier";
import {has} from "./util";
import { types as tt } from "./tokentype";
import { Parser } from "./state";
import { reservedWords } from "./identifier";
const pp = Parser.prototype;
@ -29,14 +28,15 @@ const pp = Parser.prototype;
// strict mode, init properties are also not allowed to be repeated.
pp.checkPropClash = function (prop, propHash) {
if (this.options.ecmaVersion >= 6 && (prop.computed || prop.method || prop.shorthand))
return;
if (this.options.ecmaVersion >= 6 && (prop.computed || prop.method || prop.shorthand)) return;
let key = prop.key, name;
switch (key.type) {
case "Identifier": name = key.name; break;
case "Literal": name = String(key.value); break;
default: return;
case "Identifier": name = key.name; break;
case "Literal": name = String(key.value); break;
default: return;
}
let kind = prop.kind;
if (this.options.ecmaVersion >= 6) {
if (name === "__proto__" && kind === "init") {
@ -45,6 +45,7 @@ pp.checkPropClash = function (prop, propHash) {
}
return;
}
let other;
if (propHash[name]) {
other = propHash[name];
@ -196,10 +197,11 @@ pp.parseMaybeUnary = function (refShorthandDefaultPos) {
this.next();
node.argument = this.parseMaybeUnary();
if (refShorthandDefaultPos && refShorthandDefaultPos.start) this.unexpected(refShorthandDefaultPos.start);
if (update) this.checkLVal(node.argument);
else if (this.strict && node.operator === "delete" &&
node.argument.type === "Identifier")
if (update) {
this.checkLVal(node.argument);
} else if (this.strict && node.operator === "delete" && node.argument.type === "Identifier") {
this.raise(node.start, "Deleting local variable in strict mode");
}
return this.finishNode(node, update ? "UpdateExpression" : "UnaryExpression");
}
let startPos = this.start, startLoc = this.startLoc;
@ -666,13 +668,13 @@ pp.parseObjPropValue = function (prop, startPos, startLoc, isGenerator, isAsync,
(this.strict && (reservedWords.strictBind(prop.key.name) || reservedWords.strict(prop.key.name))) ||
(!this.options.allowReserved && this.isReservedWord(prop.key.name)))
this.raise(prop.key.start, "Binding " + prop.key.name);
prop.value = this.parseMaybeDefault(startPos, startLoc, prop.key);
prop.value = this.parseMaybeDefault(startPos, startLoc, prop.key.__clone());
} else if (this.type === tt.eq && refShorthandDefaultPos) {
if (!refShorthandDefaultPos.start)
refShorthandDefaultPos.start = this.start;
prop.value = this.parseMaybeDefault(startPos, startLoc, prop.key);
prop.value = this.parseMaybeDefault(startPos, startLoc, prop.key.__clone());
} else {
prop.value = prop.key;
prop.value = prop.key.__clone();
}
prop.shorthand = true;
} else {

View File

@ -1,26 +1,5 @@
// Acorn is a tiny, fast JavaScript parser written in JavaScript.
//
// Acorn was written by Marijn Haverbeke, Ingvar Stepanyan, and
// various contributors and released under an MIT license.
//
// Git repositories for Acorn are available at
//
// http://marijnhaverbeke.nl/git/acorn
// https://github.com/marijnh/acorn.git
//
// Please use the [github bug tracker][ghbt] to report issues.
//
// [ghbt]: https://github.com/marijnh/acorn/issues
//
// This file defines the main parser interface. The library also comes
// with a [error-tolerant parser][dammit] and an
// [abstract syntax tree walker][walk], defined in other files.
//
// [dammit]: acorn_loose.js
// [walk]: util/walk.js
import {Parser, plugins} from "./state";
import {getOptions} from "./options";
import { Parser, plugins } from "./state";
import { getOptions } from "./options";
import "./parseutil";
import "./statement";
import "./lval";
@ -29,29 +8,22 @@ import "./lookahead";
import "./tokentype";
import "./tokencontext";
export {Parser, plugins} from "./state";
export {defaultOptions} from "./options";
export {SourceLocation} from "./location";
export {getLineInfo} from "./location";
export {Node} from "./node";
export {TokenType, types as tokTypes} from "./tokentype";
export {TokContext, types as tokContexts} from "./tokencontext";
export {isIdentifierChar, isIdentifierStart} from "./identifier";
export {Token} from "./tokenize";
export {isNewLine, lineBreak, lineBreakG} from "./whitespace";
export { Parser, plugins } from "./state";
export { defaultOptions } from "./options";
export { SourceLocation } from "./location";
export { getLineInfo } from "./location";
export { Node } from "./node";
export { TokenType, types as tokTypes } from "./tokentype";
export { TokContext, types as tokContexts } from "./tokencontext";
export { isIdentifierChar, isIdentifierStart } from "./identifier";
export { Token } from "./tokenize";
export { isNewLine, lineBreak, lineBreakG } from "./whitespace";
import flowPlugin from "./plugins/flow";
import jsxPlugin from "./plugins/jsx";
plugins.flow = flowPlugin;
plugins.jsx = jsxPlugin;
// The main exported interface (under `self.acorn` when in the
// browser) is a `parse` function that takes a code string and
// returns an abstract syntax tree as specified by [Mozilla parser
// API][api].
//
// [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API
export function parse(input, options) {
return new Parser(getOptions(options), input).parse();
}

View File

@ -1,5 +1,5 @@
import {Parser} from "./state";
import {lineBreakG} from "./whitespace";
import { Parser } from "./state";
import { lineBreakG } from "./whitespace";
// These are used when `options.locations` is on, for the
// `startLoc` and `endLoc` properties.

View File

@ -1,4 +1,4 @@
import {Parser} from "./state";
import { Parser } from "./state";
const pp = Parser.prototype;

View File

@ -1,7 +1,6 @@
import {types as tt} from "./tokentype";
import {Parser} from "./state";
import {reservedWords} from "./identifier";
import {has} from "./util";
import { types as tt } from "./tokentype";
import { Parser } from "./state";
import { reservedWords } from "./identifier";
const pp = Parser.prototype;

View File

@ -1,5 +1,5 @@
import {Parser} from "./state";
import {SourceLocation} from "./location";
import { Parser } from "./state";
import { SourceLocation } from "./location";
// Start an AST node, attaching a start offset.
@ -10,12 +10,26 @@ export class Node {
this.type = "";
this.start = pos;
this.end = 0;
if (parser.options.locations)
this.loc = new SourceLocation(parser, loc);
if (parser.options.directSourceFile)
this.sourceFile = parser.options.directSourceFile;
if (parser.options.ranges)
this.range = [pos, 0];
if (parser) {
if (parser.options.locations) {
this.loc = new SourceLocation(parser, loc);
}
if (parser.options.directSourceFile) {
this.sourceFile = parser.options.directSourceFile;
}
if (parser.options.ranges) {
this.range = [pos, 0];
}
}
}
__clone() {
var node2 = new Node;
for (var key in this) node2[key] = this[key];
return node2;
}
}

View File

@ -1,5 +1,5 @@
import {has} from "./util";
import {SourceLocation} from "./location";
import { has } from "./util";
import { SourceLocation } from "./location";
// A second optional argument can be given to further configure
// the parser process. These options are recognized:

View File

@ -1,6 +1,6 @@
import {types as tt} from "./tokentype";
import {Parser} from "./state";
import {lineBreak} from "./whitespace";
import { types as tt } from "./tokentype";
import { Parser } from "./state";
import { lineBreak } from "./whitespace";
const pp = Parser.prototype;

View File

@ -820,4 +820,4 @@ export default function (instance) {
}
};
});
};
}

View File

@ -3,7 +3,7 @@ import { TokenType, types as tt } from "../../tokentype";
import { TokContext, types as tc } from "../../tokencontext";
import { Parser } from "../../state";
import { isIdentifierChar, isIdentifierStart } from "../../identifier";
import { isNewLine, lineBreak, lineBreakG } from "../../whitespace";
import { isNewLine } from "../../whitespace";
const HEX_NUMBER = /^[\da-fA-F]+$/;
const DECIMAL_NUMBER = /^\d+$/;
@ -40,37 +40,39 @@ var pp = Parser.prototype;
pp.jsxReadToken = function() {
var out = "", chunkStart = this.pos;
for (;;) {
if (this.pos >= this.input.length)
if (this.pos >= this.input.length) {
this.raise(this.start, "Unterminated JSX contents");
}
var ch = this.input.charCodeAt(this.pos);
switch (ch) {
case 60: // "<"
case 123: // "{"
if (this.pos === this.start) {
if (ch === 60 && this.exprAllowed) {
++this.pos;
return this.finishToken(tt.jsxTagStart);
case 60: // "<"
case 123: // "{"
if (this.pos === this.start) {
if (ch === 60 && this.exprAllowed) {
++this.pos;
return this.finishToken(tt.jsxTagStart);
}
return this.getTokenFromCode(ch);
}
return this.getTokenFromCode(ch);
}
out += this.input.slice(chunkStart, this.pos);
return this.finishToken(tt.jsxText, out);
case 38: // "&"
out += this.input.slice(chunkStart, this.pos);
out += this.jsxReadEntity();
chunkStart = this.pos;
break;
default:
if (isNewLine(ch)) {
out += this.input.slice(chunkStart, this.pos);
out += this.jsxReadNewLine(true);
return this.finishToken(tt.jsxText, out);
case 38: // "&"
out += this.input.slice(chunkStart, this.pos);
out += this.jsxReadEntity();
chunkStart = this.pos;
} else {
++this.pos;
}
break;
default:
if (isNewLine(ch)) {
out += this.input.slice(chunkStart, this.pos);
out += this.jsxReadNewLine(true);
chunkStart = this.pos;
} else {
++this.pos;
}
}
}
};
@ -96,8 +98,10 @@ pp.jsxReadNewLine = function(normalizeCRLF) {
pp.jsxReadString = function(quote) {
var out = "", chunkStart = ++this.pos;
for (;;) {
if (this.pos >= this.input.length)
if (this.pos >= this.input.length) {
this.raise(this.start, "Unterminated string constant");
}
var ch = this.input.charCodeAt(this.pos);
if (ch === quote) break;
if (ch === 38) { // "&"
@ -119,8 +123,8 @@ pp.jsxReadString = function(quote) {
pp.jsxReadEntity = function() {
var str = "", count = 0, entity;
var ch = this.input[this.pos];
if (ch !== "&")
this.raise(this.pos, "Entity must start with an ampersand");
if (ch !== "&") this.raise(this.pos, "Entity must start with an ampersand");
var startPos = ++this.pos;
while (this.pos < this.input.length && count++ < 10) {
ch = this.input[this.pos++];
@ -168,27 +172,30 @@ pp.jsxReadWord = function() {
// Transforms JSX element name to string.
function getQualifiedJSXName(object) {
if (object.type === "JSXIdentifier")
if (object.type === "JSXIdentifier") {
return object.name;
}
if (object.type === "JSXNamespacedName")
if (object.type === "JSXNamespacedName") {
return object.namespace.name + ":" + object.name.name;
}
if (object.type === "JSXMemberExpression")
return getQualifiedJSXName(object.object) + "." +
getQualifiedJSXName(object.property);
if (object.type === "JSXMemberExpression") {
return getQualifiedJSXName(object.object) + "." + getQualifiedJSXName(object.property);
}
}
// Parse next token as JSX identifier
pp.jsxParseIdentifier = function() {
var node = this.startNode();
if (this.type === tt.jsxName)
if (this.type === tt.jsxName) {
node.name = this.value;
else if (this.type.keyword)
} else if (this.type.keyword) {
node.name = this.type.keyword;
else
} else {
this.unexpected();
}
this.next();
return this.finishNode(node, "JSXIdentifier");
};
@ -199,6 +206,7 @@ pp.jsxParseNamespacedName = function() {
var startPos = this.start, startLoc = this.startLoc;
var name = this.jsxParseIdentifier();
if (!this.eat(tt.colon)) return name;
var node = this.startNodeAt(startPos, startLoc);
node.namespace = name;
node.name = this.jsxParseIdentifier();
@ -224,18 +232,20 @@ pp.jsxParseElementName = function() {
pp.jsxParseAttributeValue = function() {
switch (this.type) {
case tt.braceL:
var node = this.jsxParseExpressionContainer();
if (node.expression.type === "JSXEmptyExpression")
this.raise(node.start, "JSX attributes must only be assigned a non-empty expression");
return node;
case tt.braceL:
var node = this.jsxParseExpressionContainer();
if (node.expression.type === "JSXEmptyExpression") {
this.raise(node.start, "JSX attributes must only be assigned a non-empty expression");
} else {
return node;
}
case tt.jsxTagStart:
case tt.string:
return this.parseExprAtom();
case tt.jsxTagStart:
case tt.string:
return this.parseExprAtom();
default:
this.raise(this.start, "JSX value should be either an expression or a quoted JSX text");
default:
this.raise(this.start, "JSX value should be either an expression or a quoted JSX text");
}
};
@ -261,9 +271,11 @@ pp.jsxParseEmptyExpression = function() {
pp.jsxParseExpressionContainer = function() {
var node = this.startNode();
this.next();
node.expression = this.type === tt.braceR
? this.jsxParseEmptyExpression()
: this.parseExpression();
if (this.type === tt.braceR) {
node.expression = this.jsxParseEmptyExpression();
} else {
node.expression = this.parseExpression();
}
this.expect(tt.braceR);
return this.finishNode(node, "JSXExpressionContainer");
};
@ -289,8 +301,9 @@ pp.jsxParseOpeningElementAt = function(startPos, startLoc) {
var node = this.startNodeAt(startPos, startLoc);
node.attributes = [];
node.name = this.jsxParseElementName();
while (this.type !== tt.slash && this.type !== tt.jsxTagEnd)
while (this.type !== tt.slash && this.type !== tt.jsxTagEnd) {
node.attributes.push(this.jsxParseAttribute());
}
node.selfClosing = this.eat(tt.slash);
this.expect(tt.jsxTagEnd);
return this.finishNode(node, "JSXOpeningElement");
@ -317,32 +330,33 @@ pp.jsxParseElementAt = function(startPos, startLoc) {
if (!openingElement.selfClosing) {
contents: for (;;) {
switch (this.type) {
case tt.jsxTagStart:
startPos = this.start; startLoc = this.startLoc;
this.next();
if (this.eat(tt.slash)) {
closingElement = this.jsxParseClosingElementAt(startPos, startLoc);
break contents;
}
children.push(this.jsxParseElementAt(startPos, startLoc));
break;
case tt.jsxTagStart:
startPos = this.start; startLoc = this.startLoc;
this.next();
if (this.eat(tt.slash)) {
closingElement = this.jsxParseClosingElementAt(startPos, startLoc);
break contents;
}
children.push(this.jsxParseElementAt(startPos, startLoc));
break;
case tt.jsxText:
children.push(this.parseExprAtom());
break;
case tt.jsxText:
children.push(this.parseExprAtom());
break;
case tt.braceL:
children.push(this.jsxParseExpressionContainer());
break;
case tt.braceL:
children.push(this.jsxParseExpressionContainer());
break;
default:
this.unexpected();
default:
this.unexpected();
}
}
if (getQualifiedJSXName(closingElement.name) !== getQualifiedJSXName(openingElement.name)) {
this.raise(
closingElement.start,
"Expected corresponding JSX closing tag for <" + getQualifiedJSXName(openingElement.name) + ">");
"Expected corresponding JSX closing tag for <" + getQualifiedJSXName(openingElement.name) + ">"
);
}
}
@ -418,4 +432,4 @@ export default function(instance) {
}
};
});
};
}

View File

@ -1,6 +1,6 @@
import {reservedWords, keywords} from "./identifier";
import {types as tt} from "./tokentype";
import {lineBreak} from "./whitespace";
import { reservedWords, keywords } from "./identifier";
import { types as tt } from "./tokentype";
import { lineBreak } from "./whitespace";
export function Parser(options, input, startPos) {
this.options = options;
@ -80,9 +80,7 @@ Parser.prototype.loadPlugins = function (plugins) {
};
Parser.prototype.parse = function () {
return new Promise((resolve) => {
let node = this.options.program || this.startNode();
this.nextToken();
resolve(this.parseTopLevel(node));
});
let node = this.options.program || this.startNode();
this.nextToken();
return this.parseTopLevel(node);
};

View File

@ -1,6 +1,6 @@
import {types as tt} from "./tokentype";
import {Parser} from "./state";
import {lineBreak} from "./whitespace";
import { types as tt } from "./tokentype";
import { Parser } from "./state";
import { lineBreak } from "./whitespace";
const pp = Parser.prototype;
@ -734,7 +734,7 @@ pp.parseExportSpecifiers = function () {
let node = this.startNode();
node.local = this.parseIdent(this.type === tt._default);
node.exported = this.eatContextual("as") ? this.parseIdent(true) : node.local;
node.exported = this.eatContextual("as") ? this.parseIdent(true) : node.local.__clone();
nodes.push(this.finishNode(node, "ExportSpecifier"));
}
@ -792,7 +792,7 @@ pp.parseImportSpecifiers = function (node) {
let specifier = this.startNode();
specifier.imported = this.parseIdent(true);
specifier.local = this.eatContextual("as") ? this.parseIdent() : specifier.imported;
specifier.local = this.eatContextual("as") ? this.parseIdent() : specifier.imported.__clone();
this.checkLVal(specifier.local, true);
node.specifiers.push(this.finishNode(specifier, "ImportSpecifier"));
}

View File

@ -2,9 +2,9 @@
// given point in the program is loosely based on sweet.js' approach.
// See https://github.com/mozilla/sweet.js/wiki/design
import {Parser} from "./state";
import {types as tt} from "./tokentype";
import {lineBreak} from "./whitespace";
import { Parser } from "./state";
import { types as tt } from "./tokentype";
import { lineBreak } from "./whitespace";
export class TokContext {
constructor(token, isExpr, preserveSpace, override) {

View File

@ -1,8 +1,8 @@
import {isIdentifierStart, isIdentifierChar} from "./identifier";
import {types as tt, keywords as keywordTypes} from "./tokentype";
import {Parser} from "./state";
import {SourceLocation} from "./location";
import {lineBreak, lineBreakG, isNewLine, nonASCIIwhitespace} from "./whitespace";
import { isIdentifierStart, isIdentifierChar } from "./identifier";
import { types as tt, keywords as keywordTypes } from "./tokentype";
import { Parser } from "./state";
import { SourceLocation } from "./location";
import { lineBreak, lineBreakG, isNewLine, nonASCIIwhitespace } from "./whitespace";
// Object type used to represent tokens. Note that normally, tokens
// simply exist as properties on the parser object. This is only
@ -123,14 +123,15 @@ pp.skipBlockComment = function () {
pp.skipLineComment = function (startSkip) {
let start = this.pos;
let startLoc = this.options.onComment && this.curPosition();
let ch = this.input.charCodeAt(this.pos+=startSkip);
let ch = this.input.charCodeAt(this.pos += startSkip);
while (this.pos < this.input.length && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) {
++this.pos;
ch = this.input.charCodeAt(this.pos);
}
if (this.options.onComment)
if (this.options.onComment) {
this.options.onComment(false, this.input.slice(start + startSkip, this.pos), start, this.pos,
startLoc, this.curPosition());
}
};
// Called at the start of the parse and after every token. Skips
@ -143,10 +144,12 @@ pp.skipSpace = function() {
case 32: case 160: // ' '
++this.pos;
break;
case 13:
if (this.input.charCodeAt(this.pos + 1) === 10) {
++this.pos;
}
case 10: case 8232: case 8233:
++this.pos;
if (this.options.locations) {
@ -154,18 +157,22 @@ pp.skipSpace = function() {
this.lineStart = this.pos;
}
break;
case 47: // '/'
switch (this.input.charCodeAt(this.pos + 1)) {
case 42: // '*'
this.skipBlockComment();
break;
case 47:
this.skipLineComment(2);
break;
default:
break loop;
}
break;
default:
if (ch > 8 && ch < 14 || ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) {
++this.pos;
@ -402,28 +409,35 @@ pp.finishOp = function (type, size) {
return this.finishToken(type, str);
};
var regexpUnicodeSupport = false;
try {
new RegExp("\uffff", "u");
regexpUnicodeSupport = true;
} catch(e) {}
// Parse a regular expression. Some context-awareness is necessary,
// since a '/' inside a '[]' set does not end the expression.
pp.readRegexp = function () {
function tryCreateRegexp(src, flags, throwErrorStart) {
try {
return new RegExp(src, flags);
} catch (e) {
if (throwErrorStart !== undefined) {
if (e instanceof SyntaxError) this.raise(throwErrorStart, "Error parsing regular expression: " + e.message);
this.raise(e);
}
}
}
var regexpUnicodeSupport = !!tryCreateRegexp("\uffff", "u");
pp.readRegexp = function() {
let escaped, inClass, start = this.pos;
for (;;) {
if (this.pos >= this.input.length) this.raise(start, "Unterminated regular expression");
let ch = this.input.charAt(this.pos);
if (lineBreak.test(ch)) this.raise(start, "Unterminated regular expression");
if (!escaped) {
if (escaped) {
escaped = false;
} else {
if (ch === "[") inClass = true;
else if (ch === "]" && inClass) inClass = false;
else if (ch === "/" && !inClass) break;
escaped = ch === "\\";
} else {
escaped = false;
}
++this.pos;
}
@ -456,23 +470,14 @@ pp.readRegexp = function () {
}
// Detect invalid regular expressions.
let value = null;
// Rhino's regular expression parser is flaky and throws uncatchable exceptions,
// so don't do detection if we are running under Rhino
if (!isRhino) {
try {
new RegExp(tmp);
} catch (e) {
if (e instanceof SyntaxError) this.raise(start, `Error parsing regular expression: ${e.message}`);
this.raise(e);
}
tryCreateRegexp(tmp, undefined, start);
// Get a regular expression object for this pattern-flag pair, or `null` in
// case the current environment doesn't support the flags it uses.
try {
value = new RegExp(content, mods);
} catch (err) {}
value = tryCreateRegexp(content, mods);
}
return this.finishToken(tt.regexp, {pattern: content, flags: mods, value: value});
};

View File

@ -27,24 +27,9 @@ function runTest(test) {
if (expected.onToken = testOpts.onToken)
testOpts.onToken = [];
return parse(test.code, testOpts).then(function (ast) {
if (test.error) {
throw new Error("Expected error message: " + test.error + ". But parsing succeeded.");
} else if (test.assert) {
var error = test.assert(ast);
if (error) throw new Error("Assertion failed: " + error);
} else {
var mis = misMatch(test.ast, ast);
for (var name in expected) {
if (mis) break;
if (expected[name]) {
mis = misMatch(expected[name], testOpts[name]);
testOpts[name] = expected[name];
}
}
if (mis) throw new Error(mis);
}
}, function (err) {
try {
var ast = parse(test.code, testOpts);
} catch (err) {
if (test.error) {
if (err.message === test.error) {
return;
@ -54,7 +39,24 @@ function runTest(test) {
}
throw err;
});
}
if (test.error) {
throw new Error("Expected error message: " + test.error + ". But parsing succeeded.");
} else if (test.assert) {
var error = test.assert(ast);
if (error) throw new Error("Assertion failed: " + error);
} else {
var mis = misMatch(test.ast, ast);
for (var name in expected) {
if (mis) break;
if (expected[name]) {
mis = misMatch(expected[name], testOpts[name]);
testOpts[name] = expected[name];
}
}
if (mis) throw new Error(mis);
}
};
function ppJSON(v) { return v instanceof RegExp ? v.toString() : JSON.stringify(v, null, 2); }

View File

@ -3636,7 +3636,9 @@ if (typeof exports !== "undefined") {
var testFail = require("./driver.js").testFail;
}
testFail("var x = <div>one</div><div>two</div>;", "Adjacent JSX elements must be wrapped in an enclosing tag (1:22)");
testFail("var x = <div>one</div><div>two</div>;", "Adjacent JSX elements must be wrapped in an enclosing tag (1:22)", {
plugins: { jsx: true }
});
for (var ns in fbTestFixture) {
ns = fbTestFixture[ns];