2017-03-25 19:32:05 -04:00

501 lines
11 KiB
JavaScript

var assert = require("assert");
var babelEslint = require("..");
var espree = require("espree");
var util = require("util");
var unpad = require("dedent");
// Checks if the source ast implements the target ast. Ignores extra keys on source ast
function assertImplementsAST(target, source, path) {
if (!path) {
path = [];
}
function error(text) {
var err = new Error(`At ${path.join(".")}: ${text}:`);
err.depth = path.length + 1;
throw err;
}
var typeA = target === null ? "null" : typeof target;
var typeB = source === null ? "null" : typeof source;
if (typeA !== typeB) {
error(`have different types (${typeA} !== ${typeB}) (${target} !== ${source})`);
} else if (typeA === "object") {
var keysTarget = Object.keys(target);
for (var i in keysTarget) {
var key = keysTarget[i];
path.push(key);
assertImplementsAST(target[key], source[key], path);
path.pop();
}
} else if (target !== source) {
error(`are different (${JSON.stringify(target)} !== ${JSON.stringify(source)})`);
}
}
function lookup(obj, keypath, backwardsDepth) {
if (!keypath) { return obj; }
return keypath.split(".").slice(0, -1 * backwardsDepth)
.reduce((base, segment) => { return base && base[segment], obj; });
}
function parseAndAssertSame(code) {
var esAST = espree.parse(code, {
ecmaFeatures: {
// enable JSX parsing
jsx: true,
// enable return in global scope
globalReturn: true,
// enable implied strict mode (if ecmaVersion >= 5)
impliedStrict: true,
// allow experimental object rest/spread
experimentalObjectRestSpread: true
},
tokens: true,
loc: true,
range: true,
comment: true,
attachComment: true,
ecmaVersion: 8,
sourceType: "module"
});
var babylonAST = babelEslint.parse(code);
try {
assertImplementsAST(esAST, babylonAST);
} catch (err) {
var traversal = err.message.slice(3, err.message.indexOf(":"));
if (esAST.tokens) {
delete esAST.tokens;
}
if (babylonAST.tokens) {
delete babylonAST.tokens;
}
err.message += unpad(`
espree:
${util.inspect(lookup(esAST, traversal, 2), { depth: err.depth, colors: true })}
babel-eslint:
${util.inspect(lookup(babylonAST, traversal, 2), { depth: err.depth, colors: true })}
`);
throw err;
}
// assert.equal(esAST, babylonAST);
}
describe("babylon-to-esprima", () => {
describe("templates", () => {
it("empty template string", () => {
parseAndAssertSame("``");
});
it("template string", () => {
parseAndAssertSame("`test`");
});
it("template string using $", () => {
parseAndAssertSame("`$`");
});
it("template string with expression", () => {
parseAndAssertSame("`${a}`");
});
it("template string with multiple expressions", () => {
parseAndAssertSame("`${a}${b}${c}`");
});
it("template string with expression and strings", () => {
parseAndAssertSame("`a${a}a`");
});
it("template string with binary expression", () => {
parseAndAssertSame("`a${a + b}a`");
});
it("tagged template", () => {
parseAndAssertSame("jsx`<Button>Click</Button>`");
});
it("tagged template with expression", () => {
parseAndAssertSame("jsx`<Button>Hi ${name}</Button>`");
});
it("tagged template with new operator", () => {
parseAndAssertSame("new raw`42`");
});
it("template with nested function/object", () => {
parseAndAssertSame("`outer${{x: {y: 10}}}bar${`nested${function(){return 1;}}endnest`}end`");
});
it("template with braces inside and outside of template string #96", () => {
parseAndAssertSame("if (a) { var target = `{}a:${webpackPort}{}}}}`; } else { app.use(); }");
});
it("template also with braces #96", () => {
parseAndAssertSame(
unpad(`
export default function f1() {
function f2(foo) {
const bar = 3;
return \`\${foo} \${bar}\`;
}
return f2;
}
`)
);
});
it("template with destructuring #31", () => {
parseAndAssertSame(
unpad(`
module.exports = {
render() {
var {name} = this.props;
return Math.max(null, \`Name: \${name}, Name: \${name}\`);
}
};
`)
);
});
});
it("simple expression", () => {
parseAndAssertSame("a = 1");
});
it("class declaration", () => {
parseAndAssertSame("class Foo {}");
});
it("class expression", () => {
parseAndAssertSame("var a = class Foo {}");
});
it("jsx expression", () => {
parseAndAssertSame("<App />");
});
it("jsx expression with 'this' as identifier", () => {
parseAndAssertSame("<this />");
});
it("jsx expression with a dynamic attribute", () => {
parseAndAssertSame("<App foo={bar} />");
});
it("jsx expression with a member expression as identifier", () => {
parseAndAssertSame("<foo.bar />");
});
it("jsx expression with spread", () => {
parseAndAssertSame("var myDivElement = <div {...this.props} />;");
});
it("empty jsx text", () => {
parseAndAssertSame("<a></a>");
});
it("jsx text with content", () => {
parseAndAssertSame("<a>Hello, world!</a>");
});
it("nested jsx", () => {
parseAndAssertSame("<div>\n<h1>Wat</h1>\n</div>");
});
it("default import", () => {
parseAndAssertSame("import foo from \"foo\";");
});
it("import specifier", () => {
parseAndAssertSame("import { foo } from \"foo\";");
});
it("import specifier with name", () => {
parseAndAssertSame("import { foo as bar } from \"foo\";");
});
it("import bare", () => {
parseAndAssertSame("import \"foo\";");
});
it("export default class declaration", () => {
parseAndAssertSame("export default class Foo {}");
});
it("export default class expression", () => {
parseAndAssertSame("export default class {}");
});
it("export default function declaration", () => {
parseAndAssertSame("export default function Foo() {}");
});
it("export default function expression", () => {
parseAndAssertSame("export default function () {}");
});
it("export all", () => {
parseAndAssertSame("export * from \"foo\";");
});
it("export named", () => {
parseAndAssertSame("export { foo };");
});
it("export named alias", () => {
parseAndAssertSame("export { foo as bar };");
});
it.skip("empty program with line comment", () => {
parseAndAssertSame("// single comment");
});
it.skip("empty program with block comment", () => {
parseAndAssertSame(" /* multiline\n * comment\n*/");
});
it("line comments", () => {
parseAndAssertSame(
unpad(`
// single comment
var foo = 15; // comment next to statement
// second comment after statement
`)
);
});
it("block comments", () => {
parseAndAssertSame(
unpad(`
/* single comment */
var foo = 15; /* comment next to statement */
/*
* multiline
* comment
*/
`)
);
});
it("block comments #124", () => {
parseAndAssertSame(
unpad(`
React.createClass({
render() {
// return (
// <div />
// ); // <-- this is the line that is reported
}
});
`)
);
});
it("null", () => {
parseAndAssertSame("null");
});
it("boolean", () => {
parseAndAssertSame("if (true) {} else if (false) {}");
});
it("regexp", () => {
parseAndAssertSame("/affix-top|affix-bottom|affix|[a-z]/");
});
it("regexp in a template string", () => {
parseAndAssertSame("`${/\\d/.exec(\"1\")[0]}`");
});
it("first line is empty", () => {
parseAndAssertSame("\nimport Immutable from \"immutable\";");
});
it("empty", () => {
parseAndAssertSame("");
});
it("jsdoc", () => {
parseAndAssertSame(
unpad(`
/**
* @param {object} options
* @return {number}
*/
const test = function({ a, b, c }) {
return a + b + c;
};
module.exports = test;
`)
);
});
it("empty block with comment", () => {
parseAndAssertSame(
unpad(`
function a () {
try {
b();
} catch (e) {
// asdf
}
}
`)
);
});
describe("babel 6 tests", () => {
it("MethodDefinition", () => {
parseAndAssertSame(
unpad(`
export default class A {
a() {}
}
`)
);
});
it("MethodDefinition 2", () => {
parseAndAssertSame("export default class Bar { get bar() { return 42; }}");
});
it("ClassMethod", () => {
parseAndAssertSame(
unpad(`
class A {
constructor() {
}
}
`)
);
});
it("ClassMethod multiple params", () => {
parseAndAssertSame(
unpad(`
class A {
constructor(a, b, c) {
}
}
`)
);
});
it("ClassMethod multiline", () => {
parseAndAssertSame(
unpad(`
class A {
constructor (
a,
b,
c
)
{
}
}
`)
);
});
it("ClassMethod oneline", () => {
parseAndAssertSame("class A { constructor(a, b, c) {} }");
});
it("ObjectMethod", () => {
parseAndAssertSame(
unpad(`
var a = {
b(c) {
}
}
`)
);
});
it("do not allow import export everywhere", () => {
assert.throws(() => {
parseAndAssertSame("function F() { import a from \"a\"; }");
}, /SyntaxError: 'import' and 'export' may only appear at the top level/);
});
it("return outside function", () => {
parseAndAssertSame("return;");
});
it("super outside method", () => {
parseAndAssertSame("function F() { super(); }");
});
it("StringLiteral", () => {
parseAndAssertSame("");
parseAndAssertSame("");
parseAndAssertSame("a");
});
it("getters and setters", () => {
parseAndAssertSame("class A { get x ( ) { ; } }");
parseAndAssertSame(
unpad(`
class A {
get x(
)
{
;
}
}
`)
);
parseAndAssertSame("class A { set x (a) { ; } }");
parseAndAssertSame(
unpad(`
class A {
set x(a
)
{
;
}
}
`)
);
parseAndAssertSame(
unpad(`
var B = {
get x () {
return this.ecks;
},
set x (ecks) {
this.ecks = ecks;
}
};
`)
);
});
it("RestOperator", () => {
parseAndAssertSame("var { a, ...b } = c");
parseAndAssertSame("var [ a, ...b ] = c");
parseAndAssertSame("var a = function (...b) {}");
});
it("SpreadOperator", () => {
parseAndAssertSame("var a = { b, ...c }");
parseAndAssertSame("var a = [ a, ...b ]");
parseAndAssertSame("var a = sum(...b)");
});
it("Async/Await", () => {
parseAndAssertSame(
unpad(`
async function a() {
await 1;
}
`)
);
});
});
});