babel/packages/babel-parser/test/helpers/runFixtureTests.js
Nicolò Ribaudo 5156d3ea06
Restore @babel/parser tests (#10854)
* Make parser tests work again

* Update failing fixtures

* Fix serialization of BigInt

* Fix
2019-12-11 11:09:13 +01:00

297 lines
7.9 KiB
JavaScript

/* global BigInt */
import { multiple as getFixtures } from "@babel/helper-fixtures";
import { codeFrameColumns } from "@babel/code-frame";
import fs from "fs";
import path from "path";
const rootPath = path.join(__dirname, "../../../..");
const serialized = "$$ babel internal serialized type";
class FixtureError extends Error {
constructor(previousError, fixturePath, code) {
super(previousError.message);
const messageLines = (previousError.message.match(/\n/g) || []).length + 1;
let fixtureStackFrame = "";
if (previousError.loc) {
fixtureStackFrame =
codeFrameColumns(
code,
{
start: {
line: previousError.loc.line,
column: previousError.loc.column + 1,
},
},
{ highlightCode: true },
) +
"\n" +
`at fixture (${fixturePath}:${previousError.loc.line}:${previousError
.loc.column + 1})\n`;
}
this.stack =
previousError.constructor.name +
": " +
previousError.message +
"\n" +
fixtureStackFrame +
previousError.stack
.split("\n")
.slice(messageLines)
.join("\n");
}
}
export function runFixtureTests(fixturesPath, parseFunction) {
const fixtures = getFixtures(fixturesPath);
Object.keys(fixtures).forEach(function(name) {
fixtures[name].forEach(function(testSuite) {
testSuite.tests.forEach(function(task) {
const testFn = task.disabled ? it.skip : it;
testFn(name + "/" + testSuite.title + "/" + task.title, function() {
try {
runTest(task, parseFunction);
} catch (err) {
if (!task.expect.code && !process.env.CI) {
const fn = path.dirname(task.expect.loc) + "/options.json";
if (!fs.existsSync(fn)) {
task.options = task.options || {};
task.options.throws = err.message.replace(
/^.*Got error message: /,
"",
);
fs.writeFileSync(fn, JSON.stringify(task.options, null, 2));
}
}
const fixturePath = `${path.relative(
rootPath,
fixturesPath,
)}/${name}/${task.actual.filename}`;
throw new FixtureError(err, fixturePath, task.actual.code);
}
});
});
});
});
}
export function runThrowTestsWithEstree(fixturesPath, parseFunction) {
const fixtures = getFixtures(fixturesPath);
Object.keys(fixtures).forEach(function(name) {
fixtures[name].forEach(function(testSuite) {
testSuite.tests.forEach(function(task) {
if (!task.options.throws) return;
task.options.plugins = task.options.plugins || [];
task.options.plugins.push("estree");
const testFn = task.disabled ? it.skip : it;
testFn(name + "/" + testSuite.title + "/" + task.title, function() {
try {
runTest(task, parseFunction);
} catch (err) {
const fixturePath = `${path.relative(
rootPath,
fixturesPath,
)}/${name}/${task.actual.filename}`;
throw new FixtureError(err, fixturePath, task.actual.code);
}
});
});
});
});
}
function save(test, ast) {
overrideToJSON(() =>
fs.writeFileSync(test.expect.loc, JSON.stringify(ast, null, 2)),
);
}
// Ensure that RegExp, BigInt, and Errors are serialized as strings
function overrideToJSON(cb) {
const originalToJSONMap = new Map();
const notJSONparseableObj = [RegExp, Error];
if (typeof BigInt !== "undefined") {
notJSONparseableObj.push(BigInt);
}
for (const obj of notJSONparseableObj) {
const { toJSON } = obj.prototype;
originalToJSONMap.set(obj, toJSON);
obj.prototype.toJSON = function() {
if (typeof this === "bigint") {
return { [serialized]: "BigInt", value: serialize(this) };
}
return this.toString();
};
}
const result = cb();
for (const obj of notJSONparseableObj) {
obj.prototype.toJSON = originalToJSONMap.get(obj);
}
return result;
}
function runTest(test, parseFunction) {
const opts = test.options;
if (opts.throws && test.expect.code) {
throw new Error(
"File expected.json exists although options specify throws. Remove expected.json.",
);
}
let ast;
try {
ast = parseFunction(test.actual.code, { errorRecovery: true, ...opts });
} catch (err) {
if (opts.throws) {
if (err.message === opts.throws) {
return;
} else {
if (process.env.OVERWRITE) {
const fn = path.dirname(test.expect.loc) + "/options.json";
test.options = test.options || {};
test.options.throws = err.message;
fs.writeFileSync(fn, JSON.stringify(test.options, null, 2));
return;
}
err.message =
"Expected error message: " +
opts.throws +
". Got error message: " +
err.message;
throw err;
}
}
throw err;
}
if (ast.comments && !ast.comments.length) delete ast.comments;
if (ast.errors && !ast.errors.length) delete ast.errors;
if (!test.expect.code && !opts.throws && !process.env.CI) {
test.expect.loc += "on";
return save(test, ast);
}
if (opts.throws) {
if (process.env.OVERWRITE) {
const fn = path.dirname(test.expect.loc) + "/options.json";
test.options = test.options || {};
delete test.options.throws;
const contents = JSON.stringify(test.options, null, 2);
if (contents === "{}") {
fs.unlinkSync(fn);
} else {
fs.writeFileSync(fn, JSON.stringify(test.options, null, 2));
}
test.expect.loc += "on";
return save(test, ast);
}
throw new Error(
"Expected error message: " + opts.throws + ". But parsing succeeded.",
);
} else {
const mis = misMatch(JSON.parse(test.expect.code), ast);
if (mis) {
if (process.env.OVERWRITE) {
return save(test, ast);
}
throw new Error(mis);
}
}
}
function serialize(value) {
if (typeof value === "bigint") {
return value.toString() + "n";
}
return JSON.stringify(value, null, 2);
}
function ppJSON(v) {
if (v && typeof v === "object" && v[serialized]) {
switch (v[serialized]) {
case "BigInt":
return typeof BigInt === "undefined" ? "null" : v.value;
}
}
return serialize(v);
}
function addPath(str, pt) {
if (str.charAt(str.length - 1) === ")") {
return str.slice(0, str.length - 1) + "/" + pt + ")";
} else {
return str + " (" + pt + ")";
}
}
function misMatch(exp, act) {
return overrideToJSON(() => {
if (
act instanceof RegExp ||
act instanceof Error ||
typeof act === "bigint" ||
(exp && typeof exp === "object" && exp[serialized])
) {
const left = ppJSON(exp);
const right = ppJSON(act);
if (left !== right) return left + " !== " + right;
} else if (Array.isArray(exp)) {
if (!Array.isArray(act)) return ppJSON(exp) + " != " + ppJSON(act);
if (act.length != exp.length) {
return "array length mismatch " + exp.length + " != " + act.length;
}
for (let i = 0; i < act.length; ++i) {
const mis = misMatch(exp[i], act[i]);
if (mis) return addPath(mis, i);
}
} else if (
!exp ||
!act ||
typeof exp != "object" ||
typeof act != "object"
) {
if (exp !== act && typeof exp != "function") {
return ppJSON(exp) + " !== " + ppJSON(act);
}
} else {
for (const prop of Object.keys(exp)) {
const mis = misMatch(exp[prop], act[prop]);
if (mis) return addPath(mis, prop);
}
for (const prop of Object.keys(act)) {
if (typeof act[prop] === "function") {
continue;
}
if (!(prop in exp) && act[prop] !== undefined) {
return `Did not expect a property '${prop}'`;
}
}
}
});
}