scripts/parser-tests/flow/index.js

Summary

Maintainability
A
3 hrs
Test Coverage
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;
});