more architectural changes

This commit is contained in:
Sebastian McKenzie 2015-07-11 20:56:26 +01:00
parent afe5eb118c
commit a226641631
42 changed files with 393 additions and 463 deletions

View File

@ -19,7 +19,8 @@
"no-loop-func": 0,
"no-unreachable": 0,
"no-labels": 0,
"no-process-exit": 0
"no-process-exit": 0,
"camelcase": 0
},
"env": {
"node": true

2
.gitignore vendored
View File

@ -1,6 +1,6 @@
.DS_Store
node_modules
test/core/tmp
test/tmp
*.log
*.cache
/templates.json

View File

@ -6,7 +6,6 @@ cache:
node_js:
- iojs
- "0.8"
- "0.10"
- "0.12"

View File

@ -10,6 +10,4 @@ commander.option("-t, --output-type [type]", "Type of output (global|umd|var)",
commander.usage("[options]");
commander.parse(process.argv);
util.ensureTemplates().then(function () {
console.log(runtime(commander.whitelist, commander.outputType));
});
console.log(runtime(commander.whitelist, commander.outputType));

View File

@ -35,7 +35,7 @@ exports.transform = function (filename, code, opts) {
opts.ignore = null;
opts.only = null;
var result = babel.__plsDontUseThis(code, opts);
var result = babel.transform(code, opts);
result.filename = filename;
result.actual = code;
return result;

View File

@ -0,0 +1,2 @@
var bar = () => console.log("bar");
bar();

View File

@ -0,0 +1,5 @@
import "./bar2";
import "./not_node_modules";
var foo = () => console.log("foo");
foo();

View File

@ -0,0 +1,10 @@
/*
The purpose of this file is to test that the node_modules check in the require
hook doesn't mistakenly exclude something like "not_node_modules". To pass, this
file merely needs to be transpiled. The transpiled code won't, and doesn't need
to, execute without error. It won't execute because React will be undefined.
*/
try {
<Some jsx="element" />;
}
catch (e) {}

View File

@ -4,11 +4,6 @@ import * as babylon from "babylon";
import * as util from "../util";
import fs from "fs";
var deasync;
try {
deasync = require("deasync");
} catch (err) {}
export { util, babylon as acorn, transform };
export { pipeline } from "../transformation";
export { canCompile } from "../util";
@ -42,32 +37,24 @@ export function transformFile(filename: string, opts?: Object, callback: Functio
opts.filename = filename;
return transform(fs.createReadStream(filename), opts);
fs.readFile(filename, function (err, code) {
if (err) return callback(err);
var result;
try {
result = transform(code, opts);
} catch (err) {
return callback(err);
}
callback(null, result);
});
}
export function __plsDontUseThis(code, opts) {
if (!deasync) {
throw new Error("Sorry, this API isn't available in the current environment.");
}
var done = false;
var result, err;
transform(code, opts).then(function (_result) {
result = _result;
done = true;
}, function (_err) {
err = _err;
done = true;
});
deasync.loopWhile(() => !done);
if (err) {
throw err;
} else {
return result;
}
export function transformFileSync(filename: string, opts?: Object = {}) {
opts.filename = filename;
return transform(fs.readFileSync(filename, "utf8"), opts);
}
export function parse(code, opts = {}) {

View File

@ -57,7 +57,7 @@ var compile = function (filename, opts = {}) {
optsManager.mergeOptions(transformOpts);
opts = optsManager.init(opts);
var cacheKey = `${filename}:${JSON.stringify(opts)}:${babel.version}`;
var cacheKey = `${JSON.stringify(opts)}:${babel.version}`;
var env = process.env.BABEL_ENV || process.env.NODE_ENV;
if (env) cacheKey += `:${env}`;
@ -70,8 +70,7 @@ var compile = function (filename, opts = {}) {
}
if (!result) {
var file = fs.readFileSync(filename, "utf8");
result = babel.__plsDontUseThis(file, extend(opts, {
result = babel.transformFileSync(filename, extend(opts, {
sourceMap: "both",
ast: false
}));

View File

@ -45,9 +45,8 @@ export default function (code, opts = {}) {
parseOpts.plugins.flow = true;
}
return babylon.parse(code, parseOpts).then(function (ast) {
var ast = babylon.parse(code, parseOpts);
estraverse.attachComments(ast, comments, tokens);
ast = normalizeAst(ast, comments, commentsAndTokens);
return ast;
});
}

View File

@ -12,7 +12,6 @@ import defaults from "lodash/object/defaults";
import includes from "lodash/collection/includes";
import traverse from "../../traversal";
import Logger from "./logger";
import { Promise } from "bluebird";
import Plugin from "../plugin";
import parse from "../../helpers/parse";
import Hub from "../../traversal/hub";
@ -429,10 +428,9 @@ export default class File {
parseOpts.sourceType = "module";
this.log.debug("Parse start");
return parse(code, parseOpts).then((ast) => {
var ast = parse(code, parseOpts);
this.log.debug("Parse stop");
return ast;
});
}
_addAst(ast) {
@ -467,11 +465,7 @@ export default class File {
}
this.call("post");
return new Promise((resolve, reject) => {
util.ensureTemplates().then(() => {
resolve(this.generate());
}, reject);
});
return this.generate();
}
wrap(code, callback) {
@ -479,7 +473,7 @@ export default class File {
try {
if (this.shouldIgnore()) {
return Promise.resolve(this.makeResult({ code, ignored: true }));
return this.makeResult({ code, ignored: true });
} else {
return callback();
}
@ -519,9 +513,8 @@ export default class File {
parseCode() {
this.parseShebang();
return this.parse(this.code).then((ast) => {
var ast = this.parse(this.code);
this.addAst(ast);
});
}
shouldIgnore() {
@ -567,6 +560,10 @@ export default class File {
map: map
};
result.then = function (callback) {
callback(result);
};
if (this.opts.code) {
result.code = code;
}

View File

@ -1,6 +1,5 @@
import PluginManager from "./file/plugin-manager";
import normalizeAst from "../helpers/normalize-ast";
import { Promise } from "bluebird";
import Plugin from "./plugin";
import assign from "lodash/object/assign";
import object from "../helpers/object";
@ -91,20 +90,19 @@ export default class Pipeline {
var file = new File(opts, this);
return file.wrap(code, function () {
file.addCode(code);
return file.parseCode(code).then(() => file.transform());
file.parseCode(code);
return file.transform();
});
}
transformFromAst(ast, code, opts) {
return new Promise((resolve) => {
ast = normalizeAst(ast);
var file = new File(opts, this);
resolve(file.wrap(code, function () {
return file.wrap(code, function () {
file.addCode(code);
file.addAst(ast);
return file.transform();
}));
});
}

View File

@ -5,7 +5,6 @@ export default {
//- builtin-pre
strict: require("./other/strict"),
eval: require("babel-plugin-eval"),
_explode: require("./internal/explode"),
_validation: require("./internal/validation"),
_hoistDirectives: require("./internal/hoist-directives"),
"minification.removeDebugger": require("babel-plugin-remove-debugger"),

View File

@ -1,34 +0,0 @@
import clone from "lodash/lang/clone";
import * as t from "../../../types";
export var metadata = {
group: "builtin-pre"
};
function buildClone(bindingKey, refKey, check?) {
return function (node) {
if (node[bindingKey] === node[refKey] || (check && check(node))) {
node[refKey] = t.removeComments(clone(node[refKey]));
}
};
}
function buildListClone(listKey, bindingKey, refKey) {
var clone = buildClone(bindingKey, refKey);
return function (node) {
if (!node[listKey]) return;
for (var subNode of (node[listKey]: Array)) {
clone(subNode);
}
};
}
export var visitor = {
Property: buildClone("value", "key", function (node) {
return t.isAssignmentPattern(node.value) && node.value.left === node.key;
}),
ExportDeclaration: buildListClone("specifiers", "local", "exported"),
ImportDeclaration: buildListClone("specifiers", "local", "imported")
};

View File

@ -10,7 +10,6 @@ import contains from "lodash/collection/contains";
import traverse from "./traversal";
import isString from "lodash/lang/isString";
import isRegExp from "lodash/lang/isRegExp";
import { Promise } from "bluebird";
import Module from "module";
import isEmpty from "lodash/lang/isEmpty";
import parse from "./helpers/parse";
@ -157,8 +156,6 @@ var templateVisitor = {
//
export function template(name: string, nodes?: Array<Object>, keepExpression?: boolean): Object {
if (!exports.templates) throw new ReferenceError(`haven't initialised templates yet`);
var ast = exports.templates[name];
if (!ast) throw new ReferenceError(`unknown template ${name}`);
@ -183,11 +180,10 @@ export function template(name: string, nodes?: Array<Object>, keepExpression?: b
return node;
}
}
export function parseTemplate(loc: string, code: string): Object {
return parse(code, { filename: loc, looseModules: true }).then(function (ast) {
return traverse.removeProperties(ast.program);
});
var ast = parse(code, { filename: loc, looseModules: true }).program;
ast = traverse.removeProperties(ast);
return ast;
}
function loadTemplates() {
@ -198,37 +194,22 @@ function loadTemplates() {
throw new ReferenceError(messages.get("missingTemplatesDirectory"));
}
var promises = [];
for (var name of (fs.readdirSync(templatesLoc): Array)) {
if (name[0] === ".") return;
for (let name of (fs.readdirSync(templatesLoc): Array)) {
if (name[0] === ".") continue;
var key = path.basename(name, path.extname(name));
var loc = path.join(templatesLoc, name);
var code = fs.readFileSync(loc, "utf8");
let key = path.basename(name, path.extname(name));
let loc = path.join(templatesLoc, name);
let code = fs.readFileSync(loc, "utf8");
promises.push(parseTemplate(loc, code).then(function (template) {
templates[key] = template;
}));
templates[key] = parseTemplate(loc, code);
}
return Promise.all(promises).then(function () {
return exports.templates = templates;
});
}
export function ensureTemplates() {
return new Promise((resolve) => {
if (exports.templates) {
resolve(exports.templates);
} else {
resolve(loadTemplates());
}
});
return templates;
}
try {
exports.templates = require("../templates.json");
exports.templates = require("../../templates.json");
} catch (err) {
if (err.code !== "MODULE_NOT_FOUND") throw err;
exports.templates = loadTemplates();
}

View File

@ -12,12 +12,7 @@ var _ = require("lodash");
require("../lib/polyfill");
var doneBeforeEval = false;
function beforeEval() {
if (doneBeforeEval) return;
doneBeforeEval = true;
eval(buildExernalHelpers());
}
eval(buildExernalHelpers());
global.assertNoOwnProperties = function (obj) {
assert.equal(Object.getOwnPropertyNames(obj).length, 0);
@ -49,7 +44,6 @@ chai.assert.throw = function (fn, msg) {
return chai.assert._throw(fn, msg);
};
var run = function (task, done) {
var actual = task.actual;
var expect = task.expect;
@ -74,10 +68,8 @@ var run = function (task, done) {
helper.esvalid(result.ast.program, result.code, opts.loc);
};
var promises = [];
if (execCode) {
promises.push(transform(execCode, getOpts(exec)).then(function (result) {
result = transform(execCode, getOpts(exec));
execCode = result.code;
try {
@ -91,9 +83,8 @@ var run = function (task, done) {
}
};
var fn = new Function("require", "exports", execCode);
beforeEval();
fn.call(global, fakeRequire, {});
var fn = new Function("require", "done", "exports", execCode);
fn.call(global, fakeRequire, chai.assert, {}, done);
} catch (err) {
err.message = exec.loc + ": " + err.message;
err.message += codeFrame(execCode);
@ -101,13 +92,12 @@ var run = function (task, done) {
}
checkAst(result, exec);
}));
}
var actualCode = actual.code;
var expectCode = expect.code;
if (!execCode || actualCode) {
promises.push(transform(actualCode, getOpts(actual)).then(function (result) {
result = transform(actualCode, getOpts(actual));
actualCode = result.code.trim();
try {
@ -118,6 +108,7 @@ var run = function (task, done) {
}
checkAst(result, actual);
}
if (task.sourceMap) {
chai.expect(result.map).to.deep.equal(task.sourceMap);
@ -133,12 +124,6 @@ var run = function (task, done) {
chai.expect({ line: expect.line, column: expect.column }).to.deep.equal(actual);
});
}
}));
}
Promise.all(promises).then(function () {
done()
}, done);
};
module.exports = function (suiteOpts, taskOpts, dynamicOpts) {
@ -155,7 +140,11 @@ module.exports = function (suiteOpts, taskOpts, dynamicOpts) {
_.each(testSuite.tests, function (task) {
if (_.contains(suiteOpts.ignoreTasks, task.title) || _.contains(suiteOpts.ignoreTasks, testSuite.title + "/" + task.title)) return;
test(task.title, !task.disabled && function (done) {
var runTest = function (done) {
var runTask = function () {
run(task, done);
};
_.extend(task.options, taskOpts);
if (dynamicOpts) dynamicOpts(task.options, task);
@ -165,26 +154,24 @@ module.exports = function (suiteOpts, taskOpts, dynamicOpts) {
// the options object with useless options
delete task.options.throws;
return run(task, function (err) {
if (err) {
// we just wanted any error message
if (throwMsg === true) return done();
assert.throws(runTask, function (err) {
return throwMsg === true || err.message.indexOf(throwMsg) >= 0;
});
} else {
runTask();
}
};
if (err.message.indexOf(throwMsg) >= 0) {
// success!
done();
var callback;
if (task.options.asyncExec) {
callback = runTest;
} else {
// didn't include it :(
done(new Error("Expected an error message of " + JSON.stringify(throwMsg) + " but got " + JSON.stringify(err.message)));
callback = function () {
return runTest();
};
}
} else {
done(new Error("Expected an error message but got none"));
}
});
} else {
return run(task, done);
}
});
test(task.title, !task.disabled && callback);
});
});
});

View File

@ -2,5 +2,5 @@
var A = "a";
var o = {
A: A // comment
};
A // comment
: A };

View File

@ -0,0 +1,3 @@
{
"noCheckAst": true
}

View File

@ -3,9 +3,7 @@
Object.defineProperty(exports, "__esModule", {
value: true
});
function f(a) {
exports.f = function f(a) {
return !a;
}
exports.f = f;
};

View File

@ -34,9 +34,7 @@ function* x() {
}
}
function y() {
exports.y = function y() {
return [].concat(babelHelpers.toConsumableArray(x()));
}
exports.y = y;
};

View File

@ -22,11 +22,11 @@ suite("generation", function () {
_.each(helper.get("generation"), function (testSuite) {
suite("generation/" + testSuite.title, function () {
_.each(testSuite.tests, function (task) {
test(task.title, !task.disabled && function (done) {
test(task.title, !task.disabled && function () {
var expect = task.expect;
var actual = task.actual;
parse(actual.code, {
var actualAst = parse(actual.code, {
filename: actual.loc,
nonStandard: true,
strictMode: false,
@ -38,11 +38,10 @@ _.each(helper.get("generation"), function (testSuite) {
"es7.exportExtensions": true,
"es7.functionBind": true
}
}).then(function (actualAst) {
});
var actualCode = generate(actualAst, task.options, actual.code).code;
chai.expect(actualCode).to.equal(expect.code, actual.loc + " !== " + expect.loc);
done();
}, done);
});
});
});

View File

@ -12,16 +12,10 @@ suite("util", function () {
test("templates do not recurse", function () {
var key = __filename;
var KEY = parse("replacedKey").program.body[0].expression;
var VALUE = parse("+KEY").program.body[0].expression;
return parse("replacedKey").then(function (result) {
var KEY = result.program.body[0].expression;
return parse("+KEY").then(function (result) {
var VALUE = result.program.body[0].expression;
return util.parseTemplate(key, "KEY = VALUE;").then(function (template) {
util.templates[key] = template;
util.templates[key] = util.parseTemplate(key, "KEY = VALUE;");
var result = util.template(key, { KEY: KEY, VALUE: VALUE });
delete util.templates[key];
@ -31,9 +25,6 @@ suite("util", function () {
"template should not recurse into replaced nodes, replacing KEY inside VALUE"
);
});
});
});
});
test("canCompile", function () {
assert.ok(util.canCompile("test.js"));

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;
}
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,13 +10,27 @@ export class Node {
this.type = "";
this.start = pos;
this.end = 0;
if (parser.options.locations)
if (parser) {
if (parser.options.locations) {
this.loc = new SourceLocation(parser, loc);
if (parser.options.directSourceFile)
}
if (parser.options.directSourceFile) {
this.sourceFile = parser.options.directSourceFile;
if (parser.options.ranges)
}
if (parser.options.ranges) {
this.range = [pos, 0];
}
}
}
__clone() {
var node2 = new Node;
for (var key in this) node2[key] = this[key];
return node2;
}
}
pp.startNode = function () {

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,8 +40,10 @@ 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) {
@ -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();
@ -226,9 +234,11 @@ pp.jsxParseAttributeValue = function() {
switch (this.type) {
case tt.braceL:
var node = this.jsxParseExpressionContainer();
if (node.expression.type === "JSXEmptyExpression")
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:
@ -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");
@ -342,7 +355,8 @@ pp.jsxParseElementAt = function(startPos, startLoc) {
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));
});
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,7 +27,20 @@ function runTest(test) {
if (expected.onToken = testOpts.onToken)
testOpts.onToken = [];
return parse(test.code, testOpts).then(function (ast) {
try {
var ast = parse(test.code, testOpts);
} catch (err) {
if (test.error) {
if (err.message === test.error) {
return;
} else {
throw new Error("Expected error message: " + test.error + ". Got error message: " + err.message);
}
}
throw err;
}
if (test.error) {
throw new Error("Expected error message: " + test.error + ". But parsing succeeded.");
} else if (test.assert) {
@ -44,17 +57,6 @@ function runTest(test) {
}
if (mis) throw new Error(mis);
}
}, function (err) {
if (test.error) {
if (err.message === test.error) {
return;
} else {
throw new Error("Expected error message: " + test.error + ". Got error message: " + err.message);
}
}
throw err;
});
};
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];