Notably, this gets us `@actions/github` 6.0.1, which is necessary for `github.context.runAttempt` (so I have also updated `package.json` accordingly). On that note... great job following semver, GitHub! Between this and the non-breaking major version change on `@actions/cache`, I'm starting to feel like nobody on the Actions team understands how software versioning works.
279 lines
10 KiB
JavaScript
279 lines
10 KiB
JavaScript
// Copyright 2021-2025 Buf Technologies, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
import { isFieldSet, ScalarType, } from "@bufbuild/protobuf";
|
|
import { protoCamelCase, reflect } from "@bufbuild/protobuf/reflect";
|
|
import { Edition, FieldDescriptorProto_Label, FieldDescriptorProto_Type, FieldDescriptorProtoSchema, FieldOptions_JSType, FieldOptionsSchema, FeatureSetSchema, SourceCodeInfo_LocationSchema, FileDescriptorProtoSchema, DescriptorProtoSchema, EnumDescriptorProtoSchema, ServiceDescriptorProtoSchema, } from "@bufbuild/protobuf/wkt";
|
|
/**
|
|
* Get comments on the package element in the protobuf source.
|
|
*/
|
|
export function getPackageComments(desc) {
|
|
return findComments(desc.proto.sourceCodeInfo, [
|
|
FileDescriptorProtoSchema.field.package.number,
|
|
]);
|
|
}
|
|
/**
|
|
* Get comments on the syntax element in the protobuf source.
|
|
*/
|
|
export function getSyntaxComments(desc) {
|
|
return findComments(desc.proto.sourceCodeInfo, [
|
|
FileDescriptorProtoSchema.field.syntax.number,
|
|
]);
|
|
}
|
|
/**
|
|
* Get comments on the element in the protobuf source.
|
|
*/
|
|
export function getComments(desc) {
|
|
let path = [];
|
|
let file;
|
|
switch (desc.kind) {
|
|
case "enum":
|
|
path = desc.parent
|
|
? [
|
|
...getComments(desc.parent).sourcePath,
|
|
DescriptorProtoSchema.field.enumType.number,
|
|
desc.parent.proto.enumType.indexOf(desc.proto),
|
|
]
|
|
: [
|
|
FileDescriptorProtoSchema.field.enumType.number,
|
|
desc.file.proto.enumType.indexOf(desc.proto),
|
|
];
|
|
file = desc.file;
|
|
break;
|
|
case "oneof":
|
|
path = [
|
|
...getComments(desc.parent).sourcePath,
|
|
DescriptorProtoSchema.field.oneofDecl.number,
|
|
desc.parent.proto.oneofDecl.indexOf(desc.proto),
|
|
];
|
|
file = desc.parent.file;
|
|
break;
|
|
case "message":
|
|
path = desc.parent
|
|
? [
|
|
...getComments(desc.parent).sourcePath,
|
|
DescriptorProtoSchema.field.nestedType.number,
|
|
desc.parent.proto.nestedType.indexOf(desc.proto),
|
|
]
|
|
: [
|
|
FileDescriptorProtoSchema.field.messageType.number,
|
|
desc.file.proto.messageType.indexOf(desc.proto),
|
|
];
|
|
file = desc.file;
|
|
break;
|
|
case "enum_value":
|
|
path = [
|
|
...getComments(desc.parent).sourcePath,
|
|
EnumDescriptorProtoSchema.field.value.number,
|
|
desc.parent.proto.value.indexOf(desc.proto),
|
|
];
|
|
file = desc.parent.file;
|
|
break;
|
|
case "field":
|
|
path = [
|
|
...getComments(desc.parent).sourcePath,
|
|
DescriptorProtoSchema.field.field.number,
|
|
desc.parent.proto.field.indexOf(desc.proto),
|
|
];
|
|
file = desc.parent.file;
|
|
break;
|
|
case "extension":
|
|
path = desc.parent
|
|
? [
|
|
...getComments(desc.parent).sourcePath,
|
|
DescriptorProtoSchema.field.extension.number,
|
|
desc.parent.proto.extension.indexOf(desc.proto),
|
|
]
|
|
: [
|
|
FileDescriptorProtoSchema.field.extension.number,
|
|
desc.file.proto.extension.indexOf(desc.proto),
|
|
];
|
|
file = desc.file;
|
|
break;
|
|
case "service":
|
|
path = [
|
|
FileDescriptorProtoSchema.field.service.number,
|
|
desc.file.proto.service.indexOf(desc.proto),
|
|
];
|
|
file = desc.file;
|
|
break;
|
|
case "rpc":
|
|
path = [
|
|
...getComments(desc.parent).sourcePath,
|
|
ServiceDescriptorProtoSchema.field.method.number,
|
|
desc.parent.proto.method.indexOf(desc.proto),
|
|
];
|
|
file = desc.parent.file;
|
|
break;
|
|
}
|
|
return findComments(file.proto.sourceCodeInfo, path);
|
|
}
|
|
/**
|
|
* Get feature options set on the element in the protobuf source. This returns
|
|
* compact (e.g. fields) or regular options (e.g. files) as an array of strings.
|
|
*/
|
|
export function getFeatureOptionStrings(desc) {
|
|
var _a, _b;
|
|
const strings = [];
|
|
const features = (_a = desc.proto.options) === null || _a === void 0 ? void 0 : _a.features;
|
|
if (features !== undefined) {
|
|
const r = reflect(FeatureSetSchema, features);
|
|
for (const f of r.fields) {
|
|
if (f.fieldKind != "enum" || !r.isSet(f)) {
|
|
continue;
|
|
}
|
|
const val = r.get(f);
|
|
const name = (_b = f.enum.values.find((v) => v.number == val)) === null || _b === void 0 ? void 0 : _b.name;
|
|
if (name !== undefined) {
|
|
strings.push(`features.${f.name} = ${name}`);
|
|
}
|
|
}
|
|
}
|
|
return strings;
|
|
}
|
|
/**
|
|
* Return a string that matches the definition of a field in the protobuf
|
|
* source. Does not take custom options into account.
|
|
*/
|
|
export function getDeclarationString(desc) {
|
|
var _a;
|
|
if (desc.kind === "enum_value") {
|
|
let str = `${desc.name} = ${desc.number}`;
|
|
if (((_a = desc.proto.options) === null || _a === void 0 ? void 0 : _a.deprecated) === true) {
|
|
str += " [deprecated = true]";
|
|
}
|
|
return str;
|
|
}
|
|
const parts = [];
|
|
function typeName(f) {
|
|
if (f.message) {
|
|
return f.message.typeName;
|
|
}
|
|
if (f.enum) {
|
|
return f.enum.typeName;
|
|
}
|
|
return ScalarType[f.scalar].toLowerCase();
|
|
}
|
|
switch (desc.fieldKind) {
|
|
case "scalar":
|
|
case "enum":
|
|
case "message":
|
|
if (fieldHasRequiredKeyword(desc)) {
|
|
parts.push("required");
|
|
}
|
|
if (fieldHasOptionalKeyword(desc)) {
|
|
parts.push("optional");
|
|
}
|
|
parts.push(typeName(desc));
|
|
break;
|
|
case "list":
|
|
parts.push("repeated", typeName(desc));
|
|
break;
|
|
case "map": {
|
|
const k = ScalarType[desc.mapKey].toLowerCase();
|
|
const v = typeName(desc);
|
|
parts.push(`map<${k}, ${v}>`);
|
|
break;
|
|
}
|
|
}
|
|
parts.push(desc.name, "=", desc.number.toString());
|
|
const options = [];
|
|
const protoOptions = desc.proto.options;
|
|
if (protoOptions !== undefined &&
|
|
isFieldSet(protoOptions, FieldOptionsSchema.field.packed)) {
|
|
options.push(`packed = ${protoOptions.packed.toString()}`);
|
|
}
|
|
if (isFieldSet(desc.proto, FieldDescriptorProtoSchema.field.defaultValue)) {
|
|
let defaultValue = desc.proto.defaultValue;
|
|
if (desc.proto.type == FieldDescriptorProto_Type.BYTES ||
|
|
desc.proto.type == FieldDescriptorProto_Type.STRING) {
|
|
defaultValue = '"' + defaultValue.replace('"', '\\"') + '"';
|
|
}
|
|
options.push(`default = ${defaultValue}`);
|
|
}
|
|
if (desc.kind == "field" && desc.jsonName !== protoCamelCase(desc.name)) {
|
|
options.push(`json_name = "${desc.jsonName}"`);
|
|
}
|
|
if (protoOptions !== undefined &&
|
|
isFieldSet(protoOptions, FieldOptionsSchema.field.jstype)) {
|
|
options.push(`jstype = ${FieldOptions_JSType[protoOptions.jstype]}`);
|
|
}
|
|
if (protoOptions !== undefined &&
|
|
isFieldSet(protoOptions, FieldOptionsSchema.field.deprecated)) {
|
|
options.push("deprecated = true");
|
|
}
|
|
options.push(...getFeatureOptionStrings(desc));
|
|
if (options.length > 0) {
|
|
parts.push("[" + options.join(", ") + "]");
|
|
}
|
|
return parts.join(" ");
|
|
}
|
|
/**
|
|
* Whether this field was declared with `required` in the protobuf source.
|
|
*/
|
|
function fieldHasRequiredKeyword(field) {
|
|
const edition = (field.kind == "extension" ? field.file : field.parent.file)
|
|
.edition;
|
|
return (edition == Edition.EDITION_PROTO2 &&
|
|
field.proto.label == FieldDescriptorProto_Label.REQUIRED);
|
|
}
|
|
/**
|
|
* Whether this field was declared with `optional` in the protobuf source.
|
|
* Note that message fields are always optional. It is impossible to determine
|
|
* whether the keyword was used.
|
|
*/
|
|
function fieldHasOptionalKeyword(field) {
|
|
const edition = (field.kind == "extension" ? field.file : field.parent.file)
|
|
.edition;
|
|
if (edition == Edition.EDITION_PROTO2) {
|
|
return (!field.oneof && field.proto.label == FieldDescriptorProto_Label.OPTIONAL);
|
|
}
|
|
if (edition == Edition.EDITION_PROTO3) {
|
|
return field.proto.proto3Optional;
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* Find comments.
|
|
*/
|
|
function findComments(sourceCodeInfo, sourcePath) {
|
|
if (!sourceCodeInfo) {
|
|
return {
|
|
leadingDetached: [],
|
|
sourcePath,
|
|
};
|
|
}
|
|
for (const location of sourceCodeInfo.location) {
|
|
if (location.path.length !== sourcePath.length) {
|
|
continue;
|
|
}
|
|
if (location.path.some((value, index) => sourcePath[index] !== value)) {
|
|
continue;
|
|
}
|
|
return {
|
|
leadingDetached: location.leadingDetachedComments,
|
|
leading: isFieldSet(location, SourceCodeInfo_LocationSchema.field.leadingComments)
|
|
? location.leadingComments
|
|
: undefined,
|
|
trailing: isFieldSet(location, SourceCodeInfo_LocationSchema.field.trailingComments)
|
|
? location.trailingComments
|
|
: undefined,
|
|
sourcePath,
|
|
};
|
|
}
|
|
return {
|
|
leadingDetached: [],
|
|
sourcePath,
|
|
};
|
|
}
|