Skip to content

Commit

Permalink
Merge pull request #740 from pulsar-edit/ensure-wasm-changes
Browse files Browse the repository at this point in the history
[meta] Create Workflow to validate WASM Grammar Changes
  • Loading branch information
confused-Techie authored Oct 26, 2023
2 parents 27088af + 26707b1 commit d42bae7
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 0 deletions.
30 changes: 30 additions & 0 deletions .github/workflows/validate-wasm-grammar-prs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Validate WASM Grammar PR Changes
# Since we now want to enforce the rule that any changes to a WASM grammar binary
# file, is accompanied by a change to the `parserSource` key within the
# `grammar.cson` file. This GHA will preform this check for us.

on:
pull_request:
paths:
- '**.wasm'

jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout the Latest Code
uses: actions/checkout@v3
with:
fetch-depth: 0
# Make sure we get all commits, so that we can compare to early commits

- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16

- name: Install dependencies
run: yarn install

- name: Run Validation Script
run: node ./script/validate-wasm-grammar-prs.js
148 changes: 148 additions & 0 deletions script/validate-wasm-grammar-prs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
* This script is called via `validate-wasm-grammar-prs.yml`
* It's purpose is to ensure that everytime a `.wasm` file is changed in a PR
* That the `parserSource` key of the grammar that uses that specific `.wasm`
* file is also updated.
* This way we can ensure that the `parserSource` is always accurate, and is
* never forgotten about.
*/

const cp = require("node:child_process");
const path = require("node:path");
const fs = require("node:fs");
const CSON = require("season");

// Change this if you want more logs
let verbose = true;

// Lets first find our common ancestor commit
// This lets us determine the commit where the branch or fork departed from
const commonAncestorCmd = cp.spawnSync("git", [ "merge-base", "origin/master", "HEAD^" ]);

if (commonAncestorCmd.status !== 0 || commonAncestorCmd.stderr.toString().length > 0) {
console.error("Git Command has failed!");
console.error("'git merge-base origin/master HEAD^'");
console.error(commonAncestorCmd.stderr.toString());
process.exit(1);
}

const commit = commonAncestorCmd.stdout.toString().trim();

if (verbose) {
console.log(`Common Ancestor Commit: '${commit}'`);
}

const cmd = cp.spawnSync("git", [ "diff", "--name-only", "-r", "HEAD", commit])

if (cmd.status !== 0 || cmd.stderr.toString().length > 0) {
console.error("Git Command has failed!");
console.error(`'git diff --name-only -r HEAD ${commit}'`);
console.error(cmd.stderr.toString());
process.exit(1);
}

const changedFiles = cmd.stdout.toString().split("\n");
// This gives us an array of the name and path of every single changed file from the last two commits
// Now to check if there's any changes we care about.

if (verbose) {
console.log("Array of changed files between commits:");
console.log(changedFiles);
}

const wasmFilesChanged = changedFiles.filter(element => element.endsWith(".wasm"));

if (wasmFilesChanged.length === 0) {
// No WASM files have been modified. Return success
console.log("No WASM files have been changed.");
process.exit(0);
}

// Now for every single wasm file that's been changed, we must validate those changes
// are also accompanied by a change in the `parserSource` key

for (const wasmFile of wasmFilesChanged) {
const wasmPath = path.dirname(wasmFile);

const files = fs.readdirSync(path.join(wasmPath, ".."));
console.log(`Detected changes to: ${wasmFile}`);

if (verbose) {
console.log("Verbose file check details:");
console.log(wasmFile);
console.log(wasmPath);
console.log(files);
console.log("\n");
}

for (const file of files) {
const filePath = path.join(wasmPath, "..", file);
console.log(`Checking: ${filePath}`);

if (fs.lstatSync(filePath).isFile()) {
const contents = CSON.readFileSync(filePath);

// We now have the contents of one of the grammar files for this specific grammar.
// Since each grammar may contain multiple grammar files, we need to ensure
// that this particular one is using the tree-sitter wasm file that was
// actually changed.
const grammarFile = contents.treeSitter?.grammar ?? "";

if (path.basename(grammarFile) === path.basename(wasmFile)) {
// This grammar uses the WASM file that's changed. So we must ensure our key has also changed
// Sidenote we use `basename` here, since the `wasmFile` will be
// a path relative from the root of the repo, meanwhile `grammarFile`
// will be relative from the file itself

// In order to check the previous state of what the key is, we first must retreive the file prior to this PR
const getPrevFile = cp.spawnSync("git", [ "show", `${commit}:./${filePath}` ]);

if (getPrevFile.status !== 0 || getPrevFile.stderr.toString().length > 0) {
// This can fail for two major reasons
// 1. The `git show` command has returned an error code other than `0`, failing.
// 2. This is a new file, and it failed to find an earlier copy (which didn't exist)
// So that we don't fail brand new TreeSitter grammars, we manually check for number 2

if (getPrevFile.stderr.toString().includes("exists on disk, but not in")) {
// Looks like this file is new. Skip this check
if (verbose) {
console.log("Looks like this file is new. Skipping...");
}
continue;
}

console.error("Git command failed!");
console.error(`'git show ${commit}:./${filePath}'`);
console.error(getPrevFile.stderr.toString());
process.exit(1);
}

fs.writeFileSync(path.join(wasmPath, "..", `OLD-${file}`), getPrevFile.stdout.toString());

const oldContents = CSON.readFileSync(path.join(wasmPath, "..", `OLD-${file}`));
const oldParserSource = oldContents.treeSitter?.parserSource ?? "";
const newParserSource = contents.treeSitter?.parserSource ?? "";

if (newParserSource.length === 0) {
console.error(`Failed to find the new \`parserSource\` within: '${filePath}'`);
console.error(contents.treeSitter);
process.exit(1);
}

if (oldParserSource == newParserSource) {
// The repo and commit is identical! This means it hasn't been updated
console.error(`The \`parserSource\` key of '${filePath}' has not been updated!`);
console.error(`Current key: ${newParserSource} - Old key: ${oldParserSource}`);
process.exit(1);
}

// Else it looks like it has been updated properly
console.log(`Validated \`parserSource\` has been updated within '${filePath}' properly.`);
} else {
if (verbose) {
console.log("This grammar file doesn't use a WASM file that's changed (On the current iteration)");
}
}
}
}
}

0 comments on commit d42bae7

Please sign in to comment.