-
-
Save timostamm/9bafd4133e886978cf6647b4fa0b26c1 to your computer and use it in GitHub Desktop.
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); | |
} | |
} | |
} |
... Or even simpler, this works interestingly (just not when using it with f.export and the registry approach:
const name = `${localName(desc)}Schema`;
return f.import(name, `./${desc.file.name}_validate.js`);
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);
}
}
}
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 ❤️
That doesn't seem to work across package boundaries. Hacked my way around it with something like this for now: