scripts/parser-tests/flow/index.js
const fs = require("fs").promises;
const path = require("path");
const merge = require("mergeiterator");
const TestRunner = require("../utils/parser-test-runner");
const flowOptionsMapping = {
esproposal_class_instance_fields: "classProperties",
esproposal_class_static_fields: "classProperties",
esproposal_export_star_as: "exportNamespaceFrom",
esproposal_decorators: "decorators-legacy",
esproposal_nullish_coalescing: "nullishCoalescingOperator",
esproposal_optional_chaining: "optionalChaining",
types: "flowComments",
intern_comments: false,
};
function getPlugins(test) {
const flowOptions = { all: true };
const plugins = [
"dynamicImport",
["flow", flowOptions],
"flowComments",
"jsx",
"classProperties",
"classPrivateProperties",
"classPrivateMethods",
"bigInt",
"numericSeparator",
];
if (!test.options) return plugins;
for (const [option, enabled] of Object.entries(test.options)) {
if (!enabled) {
const idx = plugins.indexOf(flowOptionsMapping[option]);
if (idx !== -1) plugins.splice(idx, 1);
} else if (option === "enums") {
flowOptions.enums = true;
} else if (!(option in flowOptionsMapping)) {
throw new Error("Parser options not mapped " + option);
} else if (flowOptionsMapping[option]) {
plugins.push(flowOptionsMapping[option]);
}
}
return plugins;
}
async function* readdirRecursive(root, dir = ".") {
const names = await fs.readdir(path.join(root, dir));
const dirs = [];
for (const name of names) {
const file = path.join(dir, name);
const stats = await fs.stat(path.join(root, file));
if (!stats.isDirectory()) {
if (!file) continue;
yield file;
} else {
dirs.push(readdirRecursive(root, file));
}
}
yield* merge(dirs);
}
async function* loadTests(root) {
for await (const file of readdirRecursive(root)) {
if (file.slice(-3) === ".js") {
const noExt = path.join(root, file).slice(0, -3);
const [contents, tree, options] = await Promise.all([
fs.readFile(noExt + ".js", "utf8"),
fs.readFile(noExt + ".tree.json", "utf8").catch(() => null),
fs.readFile(noExt + ".options.json", "utf8").catch(() => null),
]);
yield {
file,
contents,
tree: JSON.parse(tree),
options: JSON.parse(options),
};
}
}
}
const runner = new TestRunner({
testDir: path.join(__dirname, "../../../build/flow/src/parser/test/flow"),
allowlist: path.join(__dirname, "allowlist.txt"),
shouldUpdate: process.argv.includes("--update-allowlist"),
async *getTests() {
for await (const test of loadTests(this.testDir)) {
const shouldSuccess =
test.tree && (!test.tree.errors || !test.tree.errors.length);
yield {
contents: test.contents,
fileName: test.file,
id: test.file,
expectedError: !shouldSuccess,
plugins: getPlugins(test),
};
}
},
parse(test, parser) {
try {
parser(test.contents, {
sourceType: "module",
plugins: test.plugins,
});
} catch (e) {
// lets retry in script mode
if (!test.expectedError) {
try {
parser(test.contents, {
sourceType: "script",
plugins: test.plugins,
});
return;
} catch {}
}
throw e;
}
},
});
runner.run().catch(err => {
console.error(err);
process.exitCode = 1;
});