run_babel_parser_flow_tests.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. "use strict";
  2. const path = require("path");
  3. const fs = require("fs");
  4. const chalk = require("chalk");
  5. const parse = require("../../../packages/babel-parser").parse;
  6. const TESTS_FOLDER = path.join(
  7. __dirname,
  8. "../../../build/flow/src/parser/test/flow"
  9. );
  10. const WHITELIST_PATH = path.join(__dirname, "./flow_tests_whitelist.txt");
  11. const shouldUpdateWhitelist = process.argv.indexOf("--update-whitelist") > 0;
  12. function map_get_default(map, key, defaultConstructor) {
  13. if (map.has(key)) {
  14. return map.get(key);
  15. }
  16. const value = new defaultConstructor();
  17. map.set(key, value);
  18. return value;
  19. }
  20. function get_whitelist(filename) {
  21. return fs
  22. .readFileSync(filename, "utf8")
  23. .split("\n")
  24. .map(line => line.replace(/#.*$/, "").trim())
  25. .filter(Boolean);
  26. }
  27. function list_files(root, dir) {
  28. const files = fs.readdirSync(dir ? path.join(root, dir) : root);
  29. let result = [];
  30. for (let i = 0; i < files.length; i++) {
  31. const file = dir ? path.join(dir, files[i]) : files[i];
  32. const stats = fs.statSync(path.join(root, file));
  33. if (stats.isDirectory()) {
  34. result = result.concat(list_files(root, file));
  35. } else {
  36. result.push(file);
  37. }
  38. }
  39. return result.sort();
  40. }
  41. function get_tests(root_dir) {
  42. const files = list_files(root_dir);
  43. const tests = new Map();
  44. for (let i = 0; i < files.length; i++) {
  45. const file = files[i];
  46. const test_name = path.dirname(file);
  47. const case_parts = path.basename(file).split(".");
  48. const case_name = case_parts[0];
  49. // Hack to ignore hidden files.
  50. if (case_name === "") {
  51. continue;
  52. }
  53. const cases = map_get_default(tests, test_name, Map);
  54. const case_ = map_get_default(cases, case_name, Object);
  55. const content = fs.readFileSync(path.join(root_dir, file), "utf8");
  56. const ext = case_parts[case_parts.length - 1];
  57. const kind =
  58. case_parts.length > 2 ? case_parts[case_parts.length - 2] : null;
  59. if (ext === "js") {
  60. case_.file = file;
  61. case_.content = content;
  62. } else if (ext === "json" && kind === "tree") {
  63. case_.expected_ast = JSON.parse(content);
  64. } else if (ext === "json" && kind === "options") {
  65. case_.options = JSON.parse(content);
  66. }
  67. }
  68. return tests;
  69. }
  70. function update_whitelist(summary) {
  71. const contains = (tests, file) =>
  72. tests.some(({ test }) => test.file === file);
  73. const disallowed = summary.disallowed.success.concat(
  74. summary.disallowed.failure
  75. );
  76. const oldLines = fs
  77. .readFileSync(WHITELIST_PATH, "utf8")
  78. .trim()
  79. .split("\n")
  80. .filter(line => {
  81. const file = line.replace(/#.*$/, "").trim();
  82. return (
  83. !contains(disallowed, file) && summary.unrecognized.indexOf(file) === -1
  84. );
  85. });
  86. const newLines = disallowed
  87. .map(({ test }) => test.file)
  88. .filter(test => oldLines.indexOf(test) === -1);
  89. const result = oldLines.concat(newLines).join("\n") + "\n";
  90. fs.writeFileSync(WHITELIST_PATH, result);
  91. }
  92. const options = {
  93. plugins: [
  94. "asyncGenerators",
  95. "dynamicImport",
  96. ["flow", { all: true }],
  97. "flowComments",
  98. "jsx",
  99. "objectRestSpread",
  100. ],
  101. sourceType: "module",
  102. ranges: true,
  103. };
  104. const flowOptionsMapping = {
  105. esproposal_class_instance_fields: "classProperties",
  106. esproposal_class_static_fields: "classProperties",
  107. esproposal_export_star_as: "exportNamespaceFrom",
  108. esproposal_decorators: "decorators-legacy",
  109. esproposal_nullish_coalescing: "nullishCoalescingOperator",
  110. esproposal_optional_chaining: "optionalChaining",
  111. types: "flowComments",
  112. };
  113. const summary = {
  114. passed: true,
  115. allowed: {
  116. success: [],
  117. failure: [],
  118. },
  119. disallowed: {
  120. success: [],
  121. failure: [],
  122. },
  123. unrecognized: [],
  124. };
  125. const tests = get_tests(TESTS_FOLDER);
  126. const whitelist = get_whitelist(WHITELIST_PATH);
  127. const unrecognized = new Set(whitelist);
  128. tests.forEach(section => {
  129. section.forEach(test => {
  130. const shouldSuccess =
  131. test.expected_ast &&
  132. (!Array.isArray(test.expected_ast.errors) ||
  133. test.expected_ast.errors.length === 0);
  134. const inWhitelist = whitelist.indexOf(test.file) > -1;
  135. const babelParserOptions = Object.assign({}, options);
  136. babelParserOptions.plugins = babelParserOptions.plugins.slice();
  137. if (test.options) {
  138. Object.keys(test.options).forEach(option => {
  139. if (!test.options[option]) {
  140. const idx = babelParserOptions.plugins.indexOf(
  141. flowOptionsMapping[option]
  142. );
  143. if (idx) {
  144. babelParserOptions.plugins.splice(idx, 1);
  145. }
  146. return;
  147. }
  148. if (!flowOptionsMapping[option]) {
  149. throw new Error("Parser options not mapped " + option);
  150. }
  151. babelParserOptions.plugins.push(flowOptionsMapping[option]);
  152. });
  153. }
  154. let failed = false;
  155. let exception = null;
  156. try {
  157. parse(test.content, babelParserOptions);
  158. } catch (e) {
  159. exception = e;
  160. failed = true;
  161. // lets retry in script mode
  162. if (shouldSuccess) {
  163. try {
  164. parse(
  165. test.content,
  166. Object.assign({}, babelParserOptions, { sourceType: "script" })
  167. );
  168. exception = null;
  169. failed = false;
  170. } catch (e) {}
  171. }
  172. }
  173. const isSuccess = shouldSuccess !== failed;
  174. const isAllowed = isSuccess !== inWhitelist;
  175. summary[isAllowed ? "allowed" : "disallowed"][
  176. isSuccess ? "success" : "failure"
  177. ].push({ test, exception, shouldSuccess, babelParserOptions });
  178. summary.passed &= isAllowed;
  179. unrecognized.delete(test.file);
  180. process.stdout.write(chalk.gray("."));
  181. });
  182. });
  183. summary.unrecognized = Array.from(unrecognized);
  184. summary.passed &= summary.unrecognized.length === 0;
  185. // This is needed because, after the dots written using
  186. // `process.stdout.write(".")` there is no final newline
  187. console.log();
  188. if (summary.disallowed.failure.length || summary.disallowed.success.length) {
  189. console.log("\n-- FAILED TESTS --");
  190. summary.disallowed.failure.forEach(
  191. ({ test, shouldSuccess, exception, babelParserOptions }) => {
  192. console.log(chalk.red(`✘ ${test.file}`));
  193. if (shouldSuccess) {
  194. console.log(chalk.yellow(" Should parse successfully, but did not"));
  195. console.log(chalk.yellow(` Failed with: \`${exception.message}\``));
  196. } else {
  197. console.log(chalk.yellow(" Should fail parsing, but did not"));
  198. }
  199. console.log(
  200. chalk.yellow(
  201. ` Active plugins: ${JSON.stringify(babelParserOptions.plugins)}`
  202. )
  203. );
  204. }
  205. );
  206. summary.disallowed.success.forEach(
  207. ({ test, shouldSuccess, babelParserOptions }) => {
  208. console.log(chalk.red(`✘ ${test.file}`));
  209. if (shouldSuccess) {
  210. console.log(
  211. chalk.yellow(
  212. " Correctly parsed successfully, but" +
  213. " was disallowed by the whitelist"
  214. )
  215. );
  216. } else {
  217. console.log(
  218. chalk.yellow(
  219. " Correctly failed parsing, but" +
  220. " was disallowed by the whitelist"
  221. )
  222. );
  223. }
  224. console.log(
  225. chalk.yellow(
  226. ` Active plugins: ${JSON.stringify(babelParserOptions.plugins)}`
  227. )
  228. );
  229. }
  230. );
  231. }
  232. console.log("-- SUMMARY --");
  233. console.log(
  234. chalk.green("✔ " + summary.allowed.success.length + " tests passed")
  235. );
  236. console.log(
  237. chalk.green(
  238. "✔ " +
  239. summary.allowed.failure.length +
  240. " tests failed but were allowed in the whitelist"
  241. )
  242. );
  243. console.log(
  244. chalk.red("✘ " + summary.disallowed.failure.length + " tests failed")
  245. );
  246. console.log(
  247. chalk.red(
  248. "✘ " +
  249. summary.disallowed.success.length +
  250. " tests passed but were disallowed in the whitelist"
  251. )
  252. );
  253. console.log(
  254. chalk.red(
  255. "✘ " +
  256. summary.unrecognized.length +
  257. " tests specified in the whitelist were not found"
  258. )
  259. );
  260. // Some padding to separate the output from the message `make`
  261. // adds at the end of failing scripts
  262. console.log();
  263. if (shouldUpdateWhitelist) {
  264. update_whitelist(summary);
  265. console.log("\nWhitelist updated");
  266. } else {
  267. process.exit(summary.passed ? 0 : 1);
  268. }