validate-wasm-grammar-prs.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. /*
  2. * This script is called via `validate-wasm-grammar-prs.yml`
  3. * It's purpose is to ensure that everytime a `.wasm` file is changed in a PR
  4. * That the `parserSource` key of the grammar that uses that specific `.wasm`
  5. * file is also updated.
  6. * This way we can ensure that the `parserSource` is always accurate, and is
  7. * never forgotten about.
  8. */
  9. const cp = require("node:child_process");
  10. const path = require("node:path");
  11. const fs = require("node:fs");
  12. const CSON = require("season");
  13. // Change this if you want more logs
  14. let verbose = true;
  15. // Lets first find our common ancestor commit
  16. // This lets us determine the commit where the branch or fork departed from
  17. const commonAncestorCmd = cp.spawnSync("git", [ "merge-base", "origin/master", "HEAD^" ]);
  18. if (commonAncestorCmd.status !== 0 || commonAncestorCmd.stderr.toString().length > 0) {
  19. console.error("Git Command has failed!");
  20. console.error("'git merge-base origin/master HEAD^'");
  21. console.error(commonAncestorCmd.stderr.toString());
  22. process.exit(1);
  23. }
  24. const commit = commonAncestorCmd.stdout.toString().trim();
  25. if (verbose) {
  26. console.log(`Common Ancestor Commit: '${commit}'`);
  27. }
  28. const cmd = cp.spawnSync("git", [ "diff", "--name-only", "-r", "HEAD", commit])
  29. if (cmd.status !== 0 || cmd.stderr.toString().length > 0) {
  30. console.error("Git Command has failed!");
  31. console.error(`'git diff --name-only -r HEAD ${commit}'`);
  32. console.error(cmd.stderr.toString());
  33. process.exit(1);
  34. }
  35. const changedFiles = cmd.stdout.toString().split("\n");
  36. // This gives us an array of the name and path of every single changed file from the last two commits
  37. // Now to check if there's any changes we care about.
  38. if (verbose) {
  39. console.log("Array of changed files between commits:");
  40. console.log(changedFiles);
  41. }
  42. const wasmFilesChanged = changedFiles.filter(element => element.endsWith(".wasm"));
  43. if (wasmFilesChanged.length === 0) {
  44. // No WASM files have been modified. Return success
  45. console.log("No WASM files have been changed.");
  46. process.exit(0);
  47. }
  48. // Now for every single wasm file that's been changed, we must validate those changes
  49. // are also accompanied by a change in the `parserSource` key
  50. for (const wasmFile of wasmFilesChanged) {
  51. // Ignore files that have been deleted or moved.
  52. if (!fs.existsSync(wasmFile)) {
  53. console.log(`Skipping file that no longer exists: ${wasmFile}`);
  54. continue;
  55. }
  56. const wasmPath = path.dirname(wasmFile);
  57. // Don't check the base `tree-sitter.wasm` file.
  58. if (wasmFile.includes('vendor/web-tree-sitter')) continue;
  59. const files = fs.readdirSync(path.join(wasmPath, ".."));
  60. console.log(`Detected changes to: ${wasmFile}`);
  61. if (verbose) {
  62. console.log("Verbose file check details:");
  63. console.log(wasmFile);
  64. console.log(wasmPath);
  65. console.log(files);
  66. console.log("\n");
  67. }
  68. for (const file of files) {
  69. // Only check `cson` files.
  70. if (!file.endsWith('.cson')) continue;
  71. const filePath = path.join(wasmPath, "..", file);
  72. console.log(`Checking: ${filePath}`);
  73. if (fs.lstatSync(filePath).isFile()) {
  74. const contents = CSON.readFileSync(filePath);
  75. // We now have the contents of one of the grammar files for this specific grammar.
  76. // Since each grammar may contain multiple grammar files, we need to ensure
  77. // that this particular one is using the tree-sitter wasm file that was
  78. // actually changed.
  79. const grammarFile = contents.treeSitter?.grammar ?? "";
  80. if (path.basename(grammarFile) === path.basename(wasmFile)) {
  81. // This grammar uses the WASM file that's changed. So we must ensure our key has also changed
  82. // Sidenote we use `basename` here, since the `wasmFile` will be
  83. // a path relative from the root of the repo, meanwhile `grammarFile`
  84. // will be relative from the file itself
  85. // In order to check the previous state of what the key is, we first must retreive the file prior to this PR
  86. const getPrevFile = cp.spawnSync("git", [ "show", `${commit}:./${filePath}` ]);
  87. if (getPrevFile.status !== 0 || getPrevFile.stderr.toString().length > 0) {
  88. // This can fail for two major reasons
  89. // 1. The `git show` command has returned an error code other than `0`, failing.
  90. // 2. This is a new file, and it failed to find an earlier copy (which didn't exist)
  91. // So that we don't fail brand new TreeSitter grammars, we manually check for number 2
  92. if (getPrevFile.stderr.toString().includes("exists on disk, but not in")) {
  93. // Looks like this file is new. Skip this check
  94. if (verbose) {
  95. console.log("Looks like this file is new. Skipping...");
  96. }
  97. continue;
  98. }
  99. console.error("Git command failed!");
  100. console.error(`'git show ${commit}:./${filePath}'`);
  101. console.error(getPrevFile.stderr.toString());
  102. process.exit(1);
  103. }
  104. fs.writeFileSync(path.join(wasmPath, "..", `OLD-${file}`), getPrevFile.stdout.toString());
  105. const oldContents = CSON.readFileSync(path.join(wasmPath, "..", `OLD-${file}`));
  106. const oldParserSource = oldContents.treeSitter?.parserSource ?? "";
  107. const newParserSource = contents.treeSitter?.parserSource ?? "";
  108. if (newParserSource.length === 0) {
  109. console.error(`Failed to find the new \`parserSource\` within: '${filePath}'`);
  110. console.error(contents.treeSitter);
  111. process.exit(1);
  112. }
  113. if (oldParserSource == newParserSource) {
  114. // The repo and commit is identical! This means it hasn't been updated
  115. console.error(`The \`parserSource\` key of '${filePath}' has not been updated!`);
  116. console.error(`Current key: ${newParserSource} - Old key: ${oldParserSource}`);
  117. process.exit(1);
  118. }
  119. // Else it looks like it has been updated properly
  120. console.log(`Validated \`parserSource\` has been updated within '${filePath}' properly.`);
  121. } else {
  122. if (verbose) {
  123. console.log("This grammar file doesn't use a WASM file that's changed (On the current iteration)");
  124. }
  125. }
  126. }
  127. }
  128. }