Skip to content

Instantly share code, notes, and snippets.

@timostamm
Created October 27, 2022 13:41
Show Gist options
  • Save timostamm/9bafd4133e886978cf6647b4fa0b26c1 to your computer and use it in GitHub Desktop.
Save timostamm/9bafd4133e886978cf6647b4fa0b26c1 to your computer and use it in GitHub Desktop.
Using exports and imports in @bufbuild/protoplugin
import {localName, GeneratedFile, ImportSymbol, Schema} from "@bufbuild/protoplugin/ecmascript";
import type {DescMessage} from "@bufbuild/protobuf";
export function generateTs(schema: Schema) {
const map = new Map<DescMessage, ImportSymbol>();
function getFunctionName(f: GeneratedFile, message: DescMessage): ImportSymbol {
let s = map.get(message);
if (!s) {
s = f.export(`validate${localName(message)}`);
map.set(message, s);
}
return s;
}
function generateMessage(f: GeneratedFile, message: DescMessage) {
f.print("export function ", getFunctionName(f, message), "() {");
for (const field of message.fields) {
if (field.fieldKind === "message") {
f.print(" ", getFunctionName(f, field.message), "();");
}
}
f.print("}");
for (const nestedMessage of message.nestedMessages) {
generateMessage(f, nestedMessage);
}
}
for (const file of schema.files) {
const f = schema.generateFile(file.name + "_validate.ts");
for (const message of file.messages) {
generateMessage(f, message);
}
}
}
@timostamm
Copy link
Author

Ah, I see how the original thing above breaks!
(using buf generate https://github.com/bufbuild/protoc-gen-validate.git#ref=65fff7348be4c0b042657182ae2610290aadc21d)

The flaw is that it was using schema.files (only the proto files that the plugin was asked to generate) instead of schema.allFiles (all compiled proto files).

Even though the buf generate command above does generates all files, it does so by splitting the input files by directory and making separate plugin invocations in parallel.

This fixes it:

function generateTs(schema: Schema) {
  const map = new Map<DescMessage, ImportSymbol>();

  function getFunctionName(f: GeneratedFile, message: DescMessage): ImportSymbol {
    let s = map.get(message);
    if (!s) {
      s = f.export(`validate${localName(message)}`);
      map.set(message, s);
    }
    return s;
  }

  function generateMessage(f: GeneratedFile, message: DescMessage) {
    const name = getFunctionName(f, message);
    // note we only actually generate for files we were asked to
    if (schema.files.includes(message.file)) {
      f.print("export function ", name, "() {");
      for (const field of message.fields) {
        if (field.fieldKind === "message") {
          f.print("  ", getFunctionName(f, field.message), "();");
        }
      }
      f.print("}");
    }
    for (const nestedMessage of message.nestedMessages) {
      generateMessage(f, nestedMessage);
    }
  }

  for (const file of schema.allFiles) { // note we are walking through *all* files now
    const f = schema.generateFile(file.name + "_validate.ts");
    for (const message of file.messages) {
      generateMessage(f, message);
    }
  }
}

@timostamm
Copy link
Author

But quite frankly, this is pretty awkward 🙈
Your f.import(name, ... above seems much more straight forward!

function generateTs(schema: Schema) {
  function getFunctionName(f: GeneratedFile, message: DescMessage): ImportSymbol {
    const name = `validate${localName(message)}`;
    return f.import(name, `./${message.file.name}_validate.js`);
  }

  function generateMessage(f: GeneratedFile, message: DescMessage) {
    f.print("export function ", getFunctionName(f, message), "() {");
    for (const field of message.fields) {
      if (field.fieldKind === "message") {
        f.print("  ", getFunctionName(f, field.message), "();");
      }
    }
    f.print("}");
    for (const nestedMessage of message.nestedMessages) {
      generateMessage(f, nestedMessage);
    }
  }

  for (const file of schema.files) {
    const f = schema.generateFile(file.name + "_validate.ts");
    for (const message of file.messages) {
      generateMessage(f, message);
    }
  }
}

Thanks for shaking this out ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment