diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..c14d5c67b4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true diff --git a/acorn.js b/acorn.js index 2c7f61d8c3..cdcb2f8be7 100644 --- a/acorn.js +++ b/acorn.js @@ -43,8 +43,9 @@ input = String(inpt); inputLen = input.length; setOptions(opts); initTokenState(); + var startPos = options.locations ? [tokPos, new Position] : tokPos; initParserState(); - return parseTopLevel(options.program); + return parseTopLevel(options.program || startNodeAt(startPos)); }; // A second optional argument can be given to further configure @@ -135,9 +136,9 @@ }; function setOptions(opts) { - options = opts || {}; - for (var opt in defaultOptions) if (!has(options, opt)) - options[opt] = defaultOptions[opt]; + options = {}; + for (var opt in defaultOptions) + options[opt] = opts && has(opts, opt) ? opts[opt] : defaultOptions[opt]; sourceFile = options.sourceFile || null; if (isArray(options.onToken)) { var tokens = options.onToken; @@ -223,6 +224,7 @@ input = String(inpt); inputLen = input.length; setOptions(opts); initTokenState(); + skipSpace(); function getToken(forceRegexp) { lastEnd = tokEnd; @@ -243,6 +245,10 @@ tokRegexpAllowed = reAllowed; skipSpace(); }; + getToken.noRegexp = function() { + tokRegexpAllowed = false; + }; + getToken.options = options; return getToken; }; @@ -317,6 +323,7 @@ if (options.locations) lastEndLoc = new Position; inFunction = inGenerator = inAsync = strict = false; labels = []; + skipSpace(); readToken(); } @@ -417,9 +424,10 @@ var _bracketL = {type: "[", beforeExpr: true}, _bracketR = {type: "]"}, _braceL = {type: "{", beforeExpr: true}; var _braceR = {type: "}"}, _parenL = {type: "(", beforeExpr: true}, _parenR = {type: ")"}; var _comma = {type: ",", beforeExpr: true}, _semi = {type: ";", beforeExpr: true}; - var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _ellipsis = {type: "..."}, _question = {type: "?", beforeExpr: true}; + var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _question = {type: "?", beforeExpr: true}; var _arrow = {type: "=>", beforeExpr: true}, _bquote = {type: "`"}, _dollarBraceL = {type: "${", beforeExpr: true}; var _ltSlash = {type: "= 7 && tokType === _for) { @@ -2530,9 +2547,6 @@ case _new: return parseNew(); - case _ellipsis: - return parseSpread(); - case _bquote: return parseTemplate(); @@ -2558,15 +2572,6 @@ return finishNode(node, "NewExpression"); } - // Parse spread element '...expr' - - function parseSpread() { - var node = startNode(); - next(); - node.argument = parseExpression(true); - return finishNode(node, "SpreadElement"); - } - // Parse template expression. function parseTemplate() { @@ -2953,7 +2958,7 @@ } else first = false; var node = startNode(); - node.id = parseIdent(); + node.id = parseIdent(tokType === _default); if (tokType === _name && tokVal === "as") { next(); node.name = parseIdent(true); @@ -3256,7 +3261,7 @@ inXJSTag = false; next(); - var node = parseSpread(); + var node = parseMaybeUnary(); inXJSTag = origInXJSTag; diff --git a/acorn_loose.js b/acorn_loose.js index 09fbf210fe..649aec6241 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -40,14 +40,14 @@ var options, input, fetchToken, context; + acorn.defaultOptions.tabSize = 4; + exports.parse_dammit = function(inpt, opts) { if (!opts) opts = {}; input = String(inpt); if (/^#!.*/.test(input)) input = "//" + input.slice(2); - - options = opts; - if (!opts.tabSize) opts.tabSize = 4; fetchToken = acorn.tokenize(input, opts); + options = fetchToken.options; sourceFile = options.sourceFile || null; context = []; nextLineStart = 0; @@ -59,15 +59,14 @@ var lastEnd, token = {start: 0, end: 0}, ahead = []; var curLineStart, nextLineStart, curIndent, lastEndLoc, sourceFile; - function next() { + function next(forceRegexp) { lastEnd = token.end; if (options.locations) lastEndLoc = token.endLoc; + if (forceRegexp) + ahead.length = 0; - if (ahead.length) - token = ahead.shift(); - else - token = readToken(); + token = ahead.shift() || readToken(forceRegexp); if (token.start >= nextLineStart) { while (token.start >= nextLineStart) { @@ -78,10 +77,16 @@ } } - function readToken() { + function readToken(forceRegexp) { for (;;) { try { - return fetchToken(); + var tok = fetchToken(forceRegexp); + if (tok.type === tt.dot && input.substr(tok.end, 1) === '.') { + tok = fetchToken(); + tok.start--; + tok.type = tt.ellipsis; + } + return tok; } catch(e) { if (!(e instanceof SyntaxError)) throw e; @@ -256,6 +261,8 @@ if (token.type === type) { next(); return true; + } else { + return false; } } @@ -263,7 +270,7 @@ return (token.type === tt.eof || token.type === tt.braceR || newline.test(input.slice(lastEnd, token.start))); } function semicolon() { - eat(tt.semi); + return eat(tt.semi); } function expect(type) { @@ -279,26 +286,45 @@ } function checkLVal(expr) { - if (expr.type === "Identifier" || expr.type === "MemberExpression") return expr; - return dummyIdent(); + if (!expr) return expr; + switch (expr.type) { + case "Identifier": + case "MemberExpression": + case "ObjectPattern": + case "ArrayPattern": + case "SpreadElement": + return expr; + + default: + return dummyIdent(); + } } function parseTopLevel() { - var node = startNode(); + var node = startNodeAt(options.locations ? [0, acorn.getLineInfo(input, 0)] : 0); node.body = []; while (token.type !== tt.eof) node.body.push(parseStatement()); + lastEnd = token.end; + lastEndLoc = token.endLoc; return finishNode(node, "Program"); } function parseStatement() { + if (token.type === tt.slash || token.type === tt.assign && token.value === "/=") + next(true); + var starttype = token.type, node = startNode(); switch (starttype) { case tt._break: case tt._continue: next(); var isBreak = starttype === tt._break; - node.label = token.type === tt.name ? parseIdent() : null; - semicolon(); + if (semicolon() || canInsertSemicolon()) { + node.label = null; + } else { + node.label = token.type === tt.name ? parseIdent() : null; + semicolon(); + } return finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement"); case tt._debugger: @@ -318,16 +344,17 @@ pushCx(); expect(tt.parenL); if (token.type === tt.semi) return parseFor(node, null); - if (token.type === tt._var) { - var init = startNode(); - next(); - parseVar(init, true); - if (init.declarations.length === 1 && eat(tt._in)) + if (token.type === tt._var || token.type === tt._let) { + var init = parseVar(true); + if (init.declarations.length === 1 && (token.type === tt._in || token.type === tt.name && token.value === "of")) { return parseForIn(node, init); + } return parseFor(node, init); } var init = parseExpression(false, true); - if (eat(tt._in)) {return parseForIn(node, checkLVal(init));} + if (token.type === tt._in || token.type === tt.name && token.value === "of") { + return parseForIn(node, checkLVal(init)); + } return parseFor(node, init); case tt._function: @@ -404,9 +431,9 @@ return finishNode(node, "TryStatement"); case tt._var: - next(); - node = parseVar(node); - return node; + case tt._let: + case tt._const: + return parseVar(); case tt._while: next(); @@ -427,6 +454,15 @@ next(); return finishNode(node, "EmptyStatement"); + case tt._class: + return parseObj(true, true); + + case tt._import: + return parseImport(); + + case tt._export: + return parseExport(); + default: var expr = parseExpression(); if (isDummy(expr)) { @@ -470,24 +506,27 @@ } function parseForIn(node, init) { + var type = token.type === tt._in ? "ForInStatement" : "ForOfStatement"; + next(); node.left = init; node.right = parseExpression(); popCx(); expect(tt.parenR); node.body = parseStatement(); - return finishNode(node, "ForInStatement"); + return finishNode(node, type); } - function parseVar(node, noIn) { + function parseVar(noIn) { + var node = startNode(); + node.kind = token.type.keyword; + next(); node.declarations = []; - node.kind = "var"; - while (token.type === tt.name) { + do { var decl = startNode(); - decl.id = parseIdent(); + decl.id = options.ecmaVersion >= 6 ? toAssignable(parseExprAtom()) : parseIdent(); decl.init = eat(tt.eq) ? parseExpression(true, noIn) : null; node.declarations.push(finishNode(decl, "VariableDeclarator")); - if (!eat(tt.comma)) break; - } + } while (eat(tt.comma)); if (!node.declarations.length) { var decl = startNode(); decl.id = dummyIdent(); @@ -524,7 +563,7 @@ if (token.type.isAssign) { var node = startNodeAt(start); node.operator = token.value; - node.left = checkLVal(left); + node.left = token.type === tt.eq ? toAssignable(left) : checkLVal(left); next(); node.right = parseMaybeAssign(noIn); return finishNode(node, "AssignmentExpression"); @@ -575,13 +614,20 @@ function parseMaybeUnary(noIn) { if (token.type.prefix) { - var node = startNode(), update = token.type.isUpdate; + var node = startNode(), update = token.type.isUpdate, nodeType; + if (token.type === tt.ellipsis) { + nodeType = "SpreadElement"; + } else { + nodeType = update ? "UpdateExpression" : "UnaryExpression"; + node.operator = token.value; + node.prefix = true; + } node.operator = token.value; node.prefix = true; next(); node.argument = parseMaybeUnary(noIn); if (update) node.argument = checkLVal(node.argument); - return finishNode(node, update ? "UpdateExpression" : "UnaryExpression"); + return finishNode(node, nodeType); } var start = storeCurrentPos(); var expr = parseExprSubscripts(); @@ -647,9 +693,22 @@ var node = startNode(); next(); return finishNode(node, "ThisExpression"); + case tt.name: - return parseIdent(); - case tt.num: case tt.string: case tt.regexp: + var start = storeCurrentPos(); + var id = parseIdent(); + return eat(tt.arrow) ? parseArrowExpression(startNodeAt(start), [id]) : id; + + case tt.regexp: + var node = startNode(); + var val = token.value; + node.regex = {pattern: val.pattern, flags: val.flags}; + node.value = val.value; + node.raw = input.slice(token.start, token.end); + next(); + return finishNode(node, "Literal"); + + case tt.num: case tt.string: var node = startNode(); node.value = token.value; node.raw = input.slice(token.start, token.end); @@ -664,20 +723,32 @@ return finishNode(node, "Literal"); case tt.parenL: + var start = storeCurrentPos(); next(); var val = parseExpression(); expect(tt.parenR); + if (eat(tt.arrow)) { + return parseArrowExpression(startNodeAt(start), val.expressions || (isDummy(val) ? [] : [val])); + } + if (options.preserveParens) { + var par = startNodeAt(start); + par.expression = val; + val = finishNode(par, "ParenthesizedExpression"); + } return val; case tt.bracketL: var node = startNode(); pushCx(); - node.elements = parseExprList(tt.bracketR); + node.elements = parseExprList(tt.bracketR, true); return finishNode(node, "ArrayExpression"); case tt.braceL: return parseObj(); + case tt._class: + return parseObj(true); + case tt._function: var node = startNode(); next(); @@ -686,6 +757,18 @@ case tt._new: return parseNew(); + case tt._yield: + var node = startNode(); + next(); + if (semicolon() || canInsertSemicolon()) { + node.delegate = false; + node.argument = null; + } else { + node.delegate = eat(tt.star); + node.argument = parseExpression(true); + } + return finishNode(node, "YieldExpression"); + default: return dummyIdent(); } @@ -705,41 +788,92 @@ return finishNode(node, "NewExpression"); } - function parseObj() { + function parseObj(isClass, isStatement) { var node = startNode(); - node.properties = []; + if (isClass) { + next(); + if (token.type === tt.name) node.id = parseIdent(); + else if (isStatement) node.id = dummyIdent(); + node.superClass = eat(tt._extends) ? parseExpression() : null; + node.body = startNode(); + node.body.body = []; + } else { + node.properties = []; + } pushCx(); var indent = curIndent + 1, line = curLineStart; - next(); + eat(tt.braceL); if (curIndent + 1 < indent) { indent = curIndent; line = curLineStart; } while (!closes(tt.braceR, indent, line)) { - var name = parsePropertyName(); - if (!name) { if (isDummy(parseExpression(true))) next(); eat(tt.comma); continue; } - var prop = startNode(); - prop.key = name; - if (eat(tt.colon)) { - prop.value = parseExpression(true); + var prop = startNode(), isGenerator; + if (options.ecmaVersion >= 6) { + if (isClass) { + if (prop['static'] = (token.type === tt.name && token.value === "static")) next(); + } else { + prop.method = false; + prop.shorthand = false; + } + isGenerator = eat(tt.star); + } + parsePropertyName(prop); + if (isDummy(prop.key)) { if (isDummy(parseExpression(true))) next(); eat(tt.comma); continue; } + if (!isClass && eat(tt.colon)) { prop.kind = "init"; + prop.value = parseExpression(true); + } else if (options.ecmaVersion >= 6 && (token.type === tt.parenL || token.type === tt.braceL)) { + if (isClass) { + prop.kind = ""; + } else { + prop.kind = "init"; + prop.method = true; + } + prop.value = parseMethod(isGenerator); } else if (options.ecmaVersion >= 5 && prop.key.type === "Identifier" && (prop.key.name === "get" || prop.key.name === "set")) { prop.kind = prop.key.name; - prop.key = parsePropertyName() || dummyIdent(); - prop.value = parseFunction(startNode(), false); + parsePropertyName(prop); + prop.value = parseMethod(false); + } else if (isClass) { + prop.kind = ""; + prop.value = parseMethod(isGenerator); } else { - prop.value = dummyIdent(); + prop.kind = "init"; + prop.value = options.ecmaVersion >= 6 ? prop.key : dummyIdent(); + prop.shorthand = true; } - node.properties.push(finishNode(prop, "Property")); - eat(tt.comma); + if (isClass) { + node.body.body.push(finishNode(prop, "MethodDefinition")); + semicolon(); + } else { + node.properties.push(finishNode(prop, "Property")); + eat(tt.comma); + } } popCx(); eat(tt.braceR); - return finishNode(node, "ObjectExpression"); + if (isClass) { + semicolon(); + finishNode(node.body, "ClassBody"); + return finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression"); + } else { + return finishNode(node, "ObjectExpression"); + } } - function parsePropertyName() { - if (token.type === tt.num || token.type === tt.string) return parseExprAtom(); - if (token.type === tt.name || token.type.keyword) return parseIdent(); + function parsePropertyName(prop) { + if (options.ecmaVersion >= 6) { + if (eat(tt.bracketL)) { + prop.computed = true; + prop.key = parseExpression(); + expect(tt.bracketR); + return; + } else { + prop.computed = false; + } + } + var key = (token.type === tt.num || token.type === tt.string) ? parseExprAtom() : parseIdent(); + prop.key = key || dummyIdent(); } function parsePropertyAccessor() { @@ -749,32 +883,212 @@ function parseIdent() { var node = startNode(); node.name = token.type === tt.name ? token.value : token.type.keyword; + fetchToken.noRegexp(); next(); return finishNode(node, "Identifier"); } + function initFunction(node) { + node.id = null; + node.params = []; + if (options.ecmaVersion >= 6) { + node.defaults = []; + node.rest = null; + node.generator = false; + node.expression = false; + } + } + + // Convert existing expression atom to assignable pattern + // if possible. + + function toAssignable(node) { + if (options.ecmaVersion >= 6 && node) { + switch (node.type) { + case "ObjectExpression": + node.type = "ObjectPattern"; + var props = node.properties; + for (var i = 0; i < props.length; i++) { + props[i].value = toAssignable(props[i].value); + } + break; + + case "ArrayExpression": + node.type = "ArrayPattern"; + var elms = node.elements; + for (var i = 0; i < elms.length; i++) { + elms[i] = toAssignable(elms[i]); + } + break; + + case "SpreadElement": + node.argument = toAssignable(node.argument); + break; + } + } + return checkLVal(node); + } + + function parseFunctionParams(node, params) { + var defaults = [], hasDefaults = false; + + if (!params) { + pushCx(); + params = parseExprList(tt.parenR); + } + for (var i = 0; i < params.length; i++) { + var param = params[i], defValue = null; + if (param.type === "AssignmentExpression") { + defValue = param.right; + param = param.left; + } + param = toAssignable(param); + if (param.type === "SpreadElement") { + param = param.argument; + if (i === params.length - 1) { + node.rest = param; + continue; + } + } + node.params.push(param); + defaults.push(defValue); + if (defValue) hasDefaults = true; + } + + if (hasDefaults) node.defaults = defaults; + } + function parseFunction(node, isStatement) { + initFunction(node); + if (options.ecmaVersion >= 6) { + node.generator = eat(tt.star); + } if (token.type === tt.name) node.id = parseIdent(); else if (isStatement) node.id = dummyIdent(); - else node.id = null; - node.params = []; - pushCx(); - expect(tt.parenL); - while (token.type == tt.name) { - node.params.push(parseIdent()); - eat(tt.comma); - } - popCx(); - eat(tt.parenR); + parseFunctionParams(node); node.body = parseBlock(); return finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression"); } - function parseExprList(close) { + function parseMethod(isGenerator) { + var node = startNode(); + initFunction(node); + parseFunctionParams(node); + node.generator = isGenerator || false; + node.expression = options.ecmaVersion >= 6 && token.type !== tt.braceL; + node.body = node.expression ? parseExpression(true) : parseBlock(); + return finishNode(node, "FunctionExpression"); + } + + function parseArrowExpression(node, params) { + initFunction(node); + parseFunctionParams(node, params); + node.expression = token.type !== tt.braceL; + node.body = node.expression ? parseExpression(true) : parseBlock(); + return finishNode(node, "ArrowFunctionExpression"); + } + + function parseExport() { + var node = startNode(); + next(); + node['default'] = eat(tt._default); + node.specifiers = node.source = null; + if (node['default']) { + node.declaration = parseExpression(); + semicolon(); + } else if (token.type.keyword) { + node.declaration = parseStatement(); + } else { + node.declaration = null; + parseSpecifierList(node, "Export"); + } + semicolon(); + return finishNode(node, "ExportDeclaration"); + } + + function parseImport() { + var node = startNode(); + next(); + if (token.type === tt.string) { + node.specifiers = []; + node.source = parseExprAtom(); + node.kind = ''; + } else { + if (token.type === tt.name && token.value !== "from") { + var elt = startNode(); + elt.id = parseIdent(); + elt.name = null; + elt['default'] = true; + finishNode(elt, "ImportSpecifier"); + eat(tt.comma); + } + parseSpecifierList(node, "Import"); + var specs = node.specifiers; + for (var i = 0; i < specs.length; i++) specs[i]['default'] = false; + if (elt) node.specifiers.unshift(elt); + } + semicolon(); + return finishNode(node, "ImportDeclaration"); + } + + function parseSpecifierList(node, prefix) { + var elts = node.specifiers = []; + if (token.type === tt.star) { + var elt = startNode(); + next(); + if (token.type === tt.name && token.value === "as") { + next(); + elt.name = parseIdent(); + } + elts.push(finishNode(elt, prefix + "BatchSpecifier")); + } else { + var indent = curIndent, line = curLineStart, continuedLine = nextLineStart; + pushCx(); + eat(tt.braceL); + if (curLineStart > continuedLine) continuedLine = curLineStart; + while (!closes(tt.braceR, indent + (curLineStart <= continuedLine ? 1 : 0), line)) { + var elt = startNode(); + if (token.type === tt.star) { + next(); + if (token.type === tt.name && token.value === "as") { + next(); + elt.name = parseIdent(); + } + finishNode(elt, prefix + "BatchSpecifier"); + } else { + if (token.type === tt.name && token.value === "from") break; + elt.id = parseIdent(); + if (token.type === tt.name && token.value === "as") { + next(); + elt.name = parseIdent(); + } else { + elt.name = null; + } + finishNode(elt, prefix + "Specifier"); + } + elts.push(elt); + eat(tt.comma); + } + eat(tt.braceR); + popCx(); + } + if (token.type === tt.name && token.value === "from") { + next(); + node.source = parseExprAtom(); + } else { + node.source = null; + } + } + + function parseExprList(close, allowEmpty) { var indent = curIndent, line = curLineStart, elts = [], continuedLine = nextLineStart; next(); // Opening bracket if (curLineStart > continuedLine) continuedLine = curLineStart; while (!closes(close, indent + (curLineStart <= continuedLine ? 1 : 0), line)) { + if (eat(tt.comma)) { + elts.push(allowEmpty ? null : dummyIdent()); + continue; + } var elt = parseExpression(true); if (isDummy(elt)) { if (closes(close, indent, line)) break; @@ -782,7 +1096,7 @@ } else { elts.push(elt); } - while (eat(tt.comma)) {} + eat(tt.comma); } popCx(); eat(close); diff --git a/test/driver.js b/test/driver.js index a08bf6e837..c76b8f91a4 100644 --- a/test/driver.js +++ b/test/driver.js @@ -1,9 +1,8 @@ (function(exports) { var tests = []; - var acorn = typeof require == "undefined" ? window.acorn : require("../acorn.js"); - exports.test = function(code, ast, options, comments) { - tests.push({code: code, ast: ast, options: options, comments: comments}); + exports.test = function(code, ast, options) { + tests.push({code: code, ast: ast, options: options}); }; exports.testFail = function(code, message, options) { tests.push({code: code, error: message, options: options}); @@ -12,30 +11,29 @@ tests.push({code: code, assert: assert, options: options}); }; - exports.runTests = function(callback) { - var comments; - - function onComment(block, text, start, end, startLoc, endLoc) { - comments.push({ - block: block, - text: text, - start: start, - end: end, - startLoc: { line: startLoc.line, column: startLoc.column }, - endLoc: { line: endLoc.line, column: endLoc.column } - }); - } - - var opts = {locations: true, onComment: onComment}; + exports.runTests = function(config, callback) { + var parse = config.parse; for (var i = 0; i < tests.length; ++i) { var test = tests[i]; + if (config.filter && !config.filter(test)) continue; try { - comments = []; - if (test.options && !test.options.onComment) test.options.onComment = onComment; - var ast = acorn.parse(test.code, test.options || opts); - if (test.error) callback("fail", test.code, - "Expected error message: " + test.error + "\nBut parsing succeeded."); + var testOpts = test.options || {locations: true}; + var expected = {}; + if (expected.onComment = testOpts.onComment) { + testOpts.onComment = [] + } + if (expected.onToken = testOpts.onToken) { + testOpts.onToken = []; + } + var ast = parse(test.code, testOpts); + if (test.error) { + if (config.loose) { + callback("ok", test.code); + } else { + callback("fail", test.code, "Expected error message: " + test.error + "\nBut parsing succeeded."); + } + } else if (test.assert) { var error = test.assert(ast); if (error) callback("fail", test.code, @@ -43,12 +41,21 @@ else callback("ok", test.code); } else { var mis = misMatch(test.ast, ast); - if (!mis && test.comments) mis = misMatch(test.comments, comments); + for (var name in expected) { + if (mis) break; + if (expected[name]) { + mis = misMatch(expected[name], testOpts[name]); + testOpts[name] = expected[name]; + } + } if (mis) callback("fail", test.code, mis); else callback("ok", test.code); } } catch(e) { - if (test.error && e instanceof SyntaxError) { + if (!(e instanceof SyntaxError)) { + throw e; + } + if (test.error) { if (e.message == test.error) callback("ok", test.code); else callback("fail", test.code, "Expected error message: " + test.error + "\nGot error message: " + e.message); diff --git a/test/index.html b/test/index.html index a5b299b460..bb4e45513e 100644 --- a/test/index.html +++ b/test/index.html @@ -3,24 +3,13 @@ Acorn test suite + - - + + + + diff --git a/test/run.js b/test/run.js index e1a2753f9e..a717e9800b 100644 --- a/test/run.js +++ b/test/run.js @@ -1,23 +1,111 @@ -var driver = require("./driver.js"); -require("./tests.js"); -require("./tests-harmony.js"); -require("./tests-jsx.js"); +(function() { + var driver; -var testsRun = 0, failed = 0; -function report(state, code, message) { - if (state != "ok") {++failed; console.log(code, message);} - ++testsRun; -} + if (typeof require !== "undefined") { + driver = require("./driver.js"); + require("./tests.js"); + require("./tests-harmony.js"); + require("./tests-jsx.js"); + } else { + driver = window; + } -var t0 = +new Date; -driver.runTests(report); -console.log(testsRun + " tests run in " + (+new Date - t0) + "ms"); + var htmlLog = typeof document === "object" && document.getElementById('log'); + var htmlGroup = htmlLog; -if (failed) { - console.log(failed + " failures."); - process.stdout.write("", function() { - process.exit(1); - }); -} else { - console.log("All passed."); -} \ No newline at end of file + function group(name) { + if (htmlGroup) { + var parentGroup = htmlGroup; + htmlGroup = document.createElement("ul"); + var item = document.createElement("li"); + item.textContent = name; + item.appendChild(htmlGroup); + parentGroup.appendChild(item); + } + if (typeof console === "object" && console.group) { + console.group(name); + } + } + + function groupEnd() { + if (htmlGroup) { + htmlGroup = htmlGroup.parentElement.parentElement; + } + if (typeof console === "object" && console.groupEnd) { + console.groupEnd(name); + } + } + + function log(title, message) { + if (htmlGroup) { + var elem = document.createElement("li"); + elem.innerHTML = "" + title + " " + message; + htmlGroup.appendChild(elem); + } + if (typeof console === "object") console.log(title, message); + } + + var stats, modes = { + Normal: { + config: { + parse: (typeof require === "undefined" ? window.acorn : require("../acorn.js")).parse + } + }, + Loose: { + config: { + parse: (typeof require === "undefined" ? window.acorn : require("../acorn_loose")).parse_dammit, + loose: true, + filter: function (test) { + if (/`/.test(test.code)) return false; // FIXME remove this when the loose parse supports template strings + var opts = test.options || {}; + if (opts.loose === false) return false; + return (opts.ecmaVersion || 5) <= 6; + } + } + } + }; + + function report(state, code, message) { + if (state != "ok") {++stats.failed; log(code, message);} + ++stats.testsRun; + } + + group("Errors"); + + for (var name in modes) { + group(name); + var mode = modes[name]; + stats = mode.stats = {testsRun: 0, failed: 0}; + var t0 = +new Date; + driver.runTests(mode.config, report); + mode.stats.duration = +new Date - t0; + groupEnd(); + } + + groupEnd(); + + function outputStats(name, stats) { + log(name + ":", stats.testsRun + " tests run in " + stats.duration + "ms; " + + (stats.failed ? stats.failed + " failures." : "all passed.")); + } + + var total = {testsRun: 0, failed: 0, duration: 0}; + + group("Stats"); + + for (var name in modes) { + var stats = modes[name].stats; + outputStats(name + " parser", stats); + for (var key in stats) total[key] += stats[key]; + } + + outputStats("Total", total); + + groupEnd(); + + if (total.failed && typeof process === "object") { + process.stdout.write("", function() { + process.exit(1); + }); + } +})(); diff --git a/test/tests-harmony.js b/test/tests-harmony.js index 0600d736c0..958f6e63e5 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -5064,6 +5064,59 @@ test("export { encrypt, decrypt as dec }", { locations: true }); +test("export { default } from \"other\"", { + type: "Program", + body: [{ + type: "ExportDeclaration", + declaration: null, + specifiers: [ + { + type: "ExportSpecifier", + id: { + type: "Identifier", + name: "default", + loc: { + start: {line: 1, column: 9}, + end: {line: 1, column: 16} + } + }, + name: null, + loc: { + start: {line: 1, column: 9}, + end: {line: 1, column: 16} + } + } + ], + source: { + type: "Literal", + loc: { + start: { + line: 1, + column: 24 + }, + end: { + line: 1, + column: 31 + } + }, + value: "other", + raw: "\"other\"" + }, + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 31} + } + }], + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 31} + } +}, { + ecmaVersion: 6, + ranges: true, + locations: true +}); + test("import \"jquery\"", { type: "Program", body: [{ @@ -5113,7 +5166,6 @@ test("import $ from \"jquery\"", { end: {line: 1, column: 8} } }], - kind: "default", source: { type: "Literal", value: "jquery", @@ -5176,7 +5228,6 @@ test("import { encrypt, decrypt } from \"crypto\"", { } } ], - kind: "named", source: { type: "Literal", value: "crypto", @@ -5228,7 +5279,6 @@ test("import { encrypt as enc } from \"crypto\"", { end: {line: 1, column: 23} } }], - kind: "named", source: { type: "Literal", value: "crypto", @@ -5333,8 +5383,7 @@ test("import crypto, { decrypt, encrypt as enc } from \"crypto\"", { }, value: "crypto", raw: "\"crypto\"" - }, - kind: "default" + } }] }, { ecmaVersion: 6, @@ -5371,7 +5420,6 @@ test("import { null as nil } from \"bar\"", { end: {line: 1, column: 20} } }], - kind: "named", source: { type: "Literal", value: "bar", @@ -5431,8 +5479,7 @@ test("import * as crypto from \"crypto\"", { }, value: "crypto", raw: "\"crypto\"" - }, - kind: "named" + } }] }, { ecmaVersion: 6, @@ -14483,6 +14530,7 @@ test("yield* 10", { } }, { ecmaVersion: 6, + loose: false, ranges: true, locations: true }); @@ -14547,6 +14595,7 @@ test("e => yield* 10", { } }, { ecmaVersion: 6, + loose: false, ranges: true, locations: true }); @@ -14620,6 +14669,7 @@ test("(function () { yield* 10 })", { } }, { ecmaVersion: 6, + loose: false, ranges: true, locations: true }); diff --git a/test/tests.js b/test/tests.js index 1571cb5150..72b9340307 100644 --- a/test/tests.js +++ b/test/tests.js @@ -43,8 +43,8 @@ test("this\n", { column: 0 }, end: { - line: 1, - column: 4 + line: 2, + column: 0 } } }); @@ -86,8 +86,8 @@ test("null\n", { column: 0 }, end: { - line: 1, - column: 4 + line: 2, + column: 0 } } }); @@ -125,12 +125,12 @@ test("\n 42\n\n", { ], loc: { start: { - line: 2, - column: 4 + line: 1, + column: 0 }, end: { - line: 2, - column: 6 + line: 4, + column: 0 } } }); @@ -4916,7 +4916,7 @@ test("/* block comment */ 42", { loc: { start: { line: 1, - column: 20 + column: 0 }, end: { line: 1, @@ -4963,7 +4963,7 @@ test("42 /*The*/ /*Answer*/", { }, end: { line: 1, - column: 2 + column: 21 } } }); @@ -5006,7 +5006,7 @@ test("42 /*the*/ /*answer*/", { }, end: { line: 1, - column: 2 + column: 21 } } }); @@ -5044,8 +5044,8 @@ test("/* multiline\ncomment\nshould\nbe\nignored */ 42", { ], loc: { start: { - line: 5, - column: 11 + line: 1, + column: 0 }, end: { line: 5, @@ -5087,8 +5087,8 @@ test("/*a\r\nb*/ 42", { ], loc: { start: { - line: 2, - column: 4 + line: 1, + column: 0 }, end: { line: 2, @@ -5130,8 +5130,8 @@ test("/*a\rb*/ 42", { ], loc: { start: { - line: 2, - column: 4 + line: 1, + column: 0 }, end: { line: 2, @@ -5173,8 +5173,8 @@ test("/*a\nb*/ 42", { ], loc: { start: { - line: 2, - column: 4 + line: 1, + column: 0 }, end: { line: 2, @@ -5216,8 +5216,8 @@ test("/*a\nc*/ 42", { ], loc: { start: { - line: 2, - column: 4 + line: 1, + column: 0 }, end: { line: 2, @@ -5259,7 +5259,7 @@ test("// line comment\n42", { ], loc: { start: { - line: 2, + line: 1, column: 0 }, end: { @@ -5307,7 +5307,7 @@ test("42 // line comment", { }, end: { line: 1, - column: 2 + column: 18 } } }); @@ -5345,7 +5345,7 @@ test("// Hello, world!\n42", { ], loc: { start: { - line: 2, + line: 1, column: 0 }, end: { @@ -5360,7 +5360,7 @@ test("// Hello, world!\n", { body: [], loc: { start: { - line: 2, + line: 1, column: 0 }, end: { @@ -5375,7 +5375,7 @@ test("// Hallo, world!\n", { body: [], loc: { start: { - line: 2, + line: 1, column: 0 }, end: { @@ -5418,7 +5418,7 @@ test("//\n42", { ], loc: { start: { - line: 2, + line: 1, column: 0 }, end: { @@ -5434,7 +5434,7 @@ test("//", { loc: { start: { line: 1, - column: 2 + column: 0 }, end: { line: 1, @@ -5449,7 +5449,7 @@ test("// ", { loc: { start: { line: 1, - column: 3 + column: 0 }, end: { line: 1, @@ -5492,7 +5492,7 @@ test("/**/42", { loc: { start: { line: 1, - column: 4 + column: 0 }, end: { line: 1, @@ -5534,7 +5534,7 @@ test("// Hello, world!\n\n// Another hello\n42", { ], loc: { start: { - line: 4, + line: 1, column: 0 }, end: { @@ -28666,191 +28666,171 @@ testFail("for(x of a);", "Unexpected token (1:6)"); testFail("for(var x of a);", "Unexpected token (1:10)"); // Assertion Tests -(function() { - var actualComments = [], - expectedComments = [ - " Bear class", - " Whatever", - [" 1", - " 2", - " 3" - ].join('\n'), - "stuff" - ]; - testAssert( - function TestComments() { - // Bear class - function Bear(x,y,z) { - this.position = [x||0,y||0,z||0] - } - - Bear.prototype.roar = function(message) { - return 'RAWWW: ' + message; // Whatever - }; - - function Cat() { - /* 1 - 2 - 3*/ - } - - Cat.prototype.roar = function(message) { - return 'MEOOWW: ' + /*stuff*/ message; - }; - }.toString().replace(/\r\n/g, '\n'), - function assert(ast) { - if (actualComments.length !== expectedComments.length) { - return JSON.stringify(actualComments) + " !== " + JSON.stringify(expectedComments); - } else { - for (var i=0, n=actualComments.length; i < n; i++) { - if (actualComments[i] !== expectedComments[i]) - return JSON.stringify(actualComments[i]) + ' !== ' + JSON.stringify(expectedComments[i]); - } - } - }, - { - onComment: function(isMultiline, text) { - actualComments.push(text); - } +test(function TestComments() { + // Bear class + function Bear(x,y,z) { + this.position = [x||0,y||0,z||0] } - ); -})(); + + Bear.prototype.roar = function(message) { + return 'RAWWW: ' + message; // Whatever + }; + + function Cat() { + /* 1 + 2 + 3*/ + } + + Cat.prototype.roar = function(message) { + return 'MEOOWW: ' + /*stuff*/ message; + }; +}.toString().replace(/\r\n/g, '\n'), {}, { + onComment: [ + {type: "Line", value: " Bear class"}, + {type: "Line", value: " Whatever"}, + {type: "Block", value: [ + " 1", + " 2", + " 3" + ].join('\n')}, + {type: "Block", value: "stuff"} + ] +}); test(" HTML comment", {}, { + locations: true, + onComment: [{ + type: "Line", + value: " HTML comment", + loc: { + start: { line: 2, column: 0 }, + end: { line: 2, column: 16 } + } + }] +}); - test(";\n--> HTML comment", {}, {locations: true}, - [{ - block: false, - text: " HTML comment", - startLoc: { line: 2, column: 0 }, - endLoc: { line: 2, column: 16 } - }]); -})(); +var tokTypes = acorn.tokTypes; -(function() { - var tokTypes = acorn.tokTypes; - - var actualTokens = [], - expectedTokens = [ - { - type: tokTypes._var, - value: "var", - loc: { - start: {line: 1, column: 0}, - end: {line: 1, column: 3} - } - }, - { - type: tokTypes.name, - value: "x", - loc: { - start: {line: 1, column: 4}, - end: {line: 1, column: 5} - } - }, - { - type: tokTypes.eq, - value: "=", - loc: { - start: {line: 1, column: 6}, - end: {line: 1, column: 7} - } - }, - { - type: tokTypes.parenL, - value: undefined, - loc: { - start: {line: 1, column: 8}, - end: {line: 1, column: 9} - } - }, - { - type: tokTypes.num, - value: 1, - loc: { - start: {line: 1, column: 9}, - end: {line: 1, column: 10} - } - }, - { - type: {binop: 9, prefix: true, beforeExpr: true}, - value: "+", - loc: { - start: {line: 1, column: 11}, - end: {line: 1, column: 12} - } - }, - { - type: tokTypes.num, - value: 2, - loc: { - start: {line: 1, column: 13}, - end: {line: 1, column: 14} - } - }, - { - type: tokTypes.parenR, - value: undefined, - loc: { - start: {line: 1, column: 14}, - end: {line: 1, column: 15} - } - }, - { - type: tokTypes.eof, - value: undefined, - loc: { - start: {line: 1, column: 15}, - end: {line: 1, column: 15} - } - } - ]; - testAssert('var x = (1 + 2)', function assert(ast) { - if (actualTokens.length !== expectedTokens.length) { - return "Bad token stream length: expected " + expectedTokens.length + ", got " + actualTokens.length; - } else { - for (var i=0, n=actualTokens.length; i < n; i++) { - var mis = misMatch(expectedTokens[i], actualTokens[i]); - if (mis) return mis; +test('var x = (1 + 2)', {}, { + locations: true, + onToken: [ + { + type: tokTypes._var, + value: "var", + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 3} + } + }, + { + type: tokTypes.name, + value: "x", + loc: { + start: {line: 1, column: 4}, + end: {line: 1, column: 5} + } + }, + { + type: tokTypes.eq, + value: "=", + loc: { + start: {line: 1, column: 6}, + end: {line: 1, column: 7} + } + }, + { + type: tokTypes.parenL, + value: undefined, + loc: { + start: {line: 1, column: 8}, + end: {line: 1, column: 9} + } + }, + { + type: tokTypes.num, + value: 1, + loc: { + start: {line: 1, column: 9}, + end: {line: 1, column: 10} + } + }, + { + type: {binop: 9, prefix: true, beforeExpr: true}, + value: "+", + loc: { + start: {line: 1, column: 11}, + end: {line: 1, column: 12} + } + }, + { + type: tokTypes.num, + value: 2, + loc: { + start: {line: 1, column: 13}, + end: {line: 1, column: 14} + } + }, + { + type: tokTypes.parenR, + value: undefined, + loc: { + start: {line: 1, column: 14}, + end: {line: 1, column: 15} + } + }, + { + type: tokTypes.eof, + value: undefined, + loc: { + start: {line: 1, column: 15}, + end: {line: 1, column: 15} } } - }, { - locations: true, - onToken: actualTokens - }); -})(); + ] +}); test("function f(f) { 'use strict'; }", {});