mirror of
https://github.com/github/codeql-action.git
synced 2025-12-27 01:30:10 +08:00
Co-authored-by: Andrew Eisenberg <aeisenberg@github.com> Co-authored-by: Henry Mercer <henrymercer@github.com>
204 lines
7.9 KiB
JavaScript
204 lines
7.9 KiB
JavaScript
"use strict";
|
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
});
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.PluginBase = void 0;
|
|
const plugin_1 = require("./google/protobuf/compiler/plugin");
|
|
const util_1 = require("util");
|
|
const runtime_1 = require("@protobuf-ts/runtime");
|
|
/**
|
|
* Base class for a protobuf plugin.
|
|
*
|
|
* Implement the abstract `generate()` method to create a plugin.
|
|
* The method takes a `CodeGeneratorRequest` and returns an
|
|
* array of `GeneratedFile` or a promise thereof.
|
|
*
|
|
*
|
|
* Usage:
|
|
*
|
|
* #!/usr/bin/env node
|
|
* const {MyPlugin} = require( ... );
|
|
* new MyPlugin.run().catch(_ => {
|
|
* process.stderr.write('failed to run plugin');
|
|
* process.exit(1);
|
|
* });
|
|
*
|
|
* Reads a `CodeGeneratorRequest` created by `protoc` from stdin,
|
|
* passes it to the plugin-function and writes a
|
|
* `CodeGeneratorResponse` to stdout.
|
|
*
|
|
*
|
|
* Options:
|
|
*
|
|
* Use the `parseOptions()` method the parse the parameter
|
|
* of a `CodeGeneratorRequest` to a map of flags. Options are
|
|
* validated and usage is generated on error.
|
|
*
|
|
*
|
|
* Error handling:
|
|
*
|
|
* `generate()` may raise an error, reject it's promise or
|
|
* return an `GeneratedFile` with an attached error.
|
|
*
|
|
* Throwing `new Error("hello")` will result in the output:
|
|
*
|
|
* $ protoc --xx_out=/tmp -I protos protos/*
|
|
* --xx_out: Error: hello
|
|
* at /path/to/your-plugin.js:69
|
|
* ...
|
|
*
|
|
*
|
|
*/
|
|
class PluginBase {
|
|
run() {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
try {
|
|
let response, bytes = yield this.readBytes(process.stdin), request = plugin_1.CodeGeneratorRequest.fromBinary(bytes);
|
|
try {
|
|
const files = yield this.generate(request);
|
|
response = this.createResponse(files);
|
|
}
|
|
catch (error) {
|
|
response = plugin_1.CodeGeneratorResponse.create({
|
|
error: this.errorToString(error)
|
|
});
|
|
}
|
|
this.setBlockingStdout();
|
|
process.stdout.write(plugin_1.CodeGeneratorResponse.toBinary(response));
|
|
}
|
|
catch (error) {
|
|
process.stderr.write('Plugin failed to read CodeGeneratorRequest from stdin or write CodeGeneratorResponse to stdout.\n');
|
|
process.stderr.write(this.errorToString(error));
|
|
process.stderr.write('\n');
|
|
process.exit(1);
|
|
}
|
|
});
|
|
}
|
|
getSupportedFeatures() {
|
|
return [];
|
|
}
|
|
parseOptions(spec, parameter) {
|
|
var _a, _b, _c, _d;
|
|
this.validateOptionsSpec(spec);
|
|
let given = parameter ? parameter.split(',') : [];
|
|
let known = Object.keys(spec);
|
|
let excess = given.filter(i => !known.includes(i));
|
|
if (excess.length > 0) {
|
|
this.throwOptionError(spec, `Option "${excess.join('", "')}" not recognized.`);
|
|
}
|
|
for (let [key, val] of Object.entries(spec)) {
|
|
if (given.includes(key)) {
|
|
let missing = (_b = (_a = val.requires) === null || _a === void 0 ? void 0 : _a.filter(i => !given.includes(i))) !== null && _b !== void 0 ? _b : [];
|
|
if (missing.length > 0) {
|
|
this.throwOptionError(spec, `Option "${key}" requires option "${missing.join('", "')}" to be set.`);
|
|
}
|
|
let excess = (_d = (_c = val.excludes) === null || _c === void 0 ? void 0 : _c.filter(i => given.includes(i))) !== null && _d !== void 0 ? _d : [];
|
|
if (excess.length > 0) {
|
|
this.throwOptionError(spec, `If option "${key}" is set, option "${excess.join('", "')}" cannot be set.`);
|
|
}
|
|
}
|
|
}
|
|
let resolved = {};
|
|
for (let key of Object.keys(spec)) {
|
|
resolved[key] = given.includes(key);
|
|
}
|
|
return resolved;
|
|
}
|
|
throwOptionError(spec, error) {
|
|
let text = '';
|
|
text += error + '\n';
|
|
text += `\n`;
|
|
text += `Available options:\n`;
|
|
text += `\n`;
|
|
for (let [key, val] of Object.entries(spec)) {
|
|
text += `- "${key}"\n`;
|
|
for (let l of val.description.split('\n')) {
|
|
text += ` ${l}\n`;
|
|
}
|
|
text += `\n`;
|
|
}
|
|
let err = new Error(text);
|
|
err.name = `ParameterError`;
|
|
throw err;
|
|
}
|
|
validateOptionsSpec(spec) {
|
|
var _a, _b;
|
|
let known = Object.keys(spec);
|
|
for (let [key, { excludes, requires }] of Object.entries(spec)) {
|
|
let r = (_a = requires === null || requires === void 0 ? void 0 : requires.filter(i => !known.includes(i))) !== null && _a !== void 0 ? _a : [];
|
|
if (r.length > 0) {
|
|
throw new Error(`Invalid parameter spec for parameter "${key}". "requires" points to unknown parameters: ${r.join(', ')}`);
|
|
}
|
|
let e = (_b = excludes === null || excludes === void 0 ? void 0 : excludes.filter(i => !known.includes(i))) !== null && _b !== void 0 ? _b : [];
|
|
if (e.length > 0) {
|
|
throw new Error(`Invalid parameter spec for parameter "${key}". "excludes" points to unknown parameters: ${e.join(', ')}`);
|
|
}
|
|
}
|
|
}
|
|
readBytes(stream) {
|
|
return new Promise(resolve => {
|
|
const chunks = [];
|
|
stream.on('data', chunk => chunks.push(chunk));
|
|
stream.on('end', () => {
|
|
resolve(Buffer.concat(chunks));
|
|
});
|
|
});
|
|
}
|
|
createResponse(files) {
|
|
// we have to respond with an xor of all of our supported features.
|
|
// we should be working on a ulong here, but we cannot rely on bigint support.
|
|
let feat = 0;
|
|
for (let f of this.getSupportedFeatures()) {
|
|
feat = feat ^ f;
|
|
}
|
|
return plugin_1.CodeGeneratorResponse.create({
|
|
file: files
|
|
.map(f => plugin_1.CodeGeneratorResponse_File.create({
|
|
name: f.getFilename(),
|
|
content: f.getContent()
|
|
}))
|
|
.filter(f => f.content && f.content.length > 0),
|
|
supportedFeatures: runtime_1.PbULong.from(feat).toString()
|
|
});
|
|
}
|
|
errorToString(error) {
|
|
var _a;
|
|
if (error && typeof error.name == 'string' && error.name == 'ParameterError') {
|
|
return error.name + '\n\n' + error.message;
|
|
}
|
|
if (error && typeof error.name == 'string' && error.name == 'PluginMessageError') {
|
|
return error.message;
|
|
}
|
|
if (util_1.types.isNativeError(error)) {
|
|
return (_a = error.stack) !== null && _a !== void 0 ? _a : error.toString();
|
|
}
|
|
let text;
|
|
try {
|
|
text = error.toString();
|
|
}
|
|
catch (e) {
|
|
text = 'unknown error';
|
|
}
|
|
return text;
|
|
}
|
|
setBlockingStdout() {
|
|
// Fixes https://github.com/timostamm/protobuf-ts/issues/134
|
|
// Node is buffering chunks to stdout, meaning that for big generated
|
|
// files the CodeGeneratorResponse will not reach protoc completely.
|
|
// To fix this, we set stdout to block using the internal private
|
|
// method setBlocking(true)
|
|
const stdoutHandle = process.stdout._handle;
|
|
if (stdoutHandle) {
|
|
stdoutHandle.setBlocking(true);
|
|
}
|
|
}
|
|
}
|
|
exports.PluginBase = PluginBase;
|