var assert = require("assert");
var babelEslint = require("..");
var espree = require("espree");
var escope = require("eslint-scope");
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" &&
["RegExp"].indexOf(target.constructor.name) !== -1 &&
target.constructor.name !== source.constructor.name
) {
error(
`object have different constructors (${target.constructor
.name} !== ${source.constructor.name}`
);
} 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) {
code = unpad(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("compatibility", () => {
it("should allow ast.analyze to be called without options", function() {
var esAST = babelEslint.parse("`test`");
assert.doesNotThrow(
() => {
escope.analyze(esAST);
},
TypeError,
"Should allow no options argument."
);
});
});
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``");
});
it("tagged template with expression", () => {
parseAndAssertSame("jsx``");
});
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(`
export default function f1() {
function f2(foo) {
const bar = 3;
return \`\${foo} \${bar}\`;
}
return f2;
}
`);
});
it("template with destructuring #31", () => {
parseAndAssertSame(`
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("