Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support strict:true (#154), AsObject type, add clear_ methods, fix map default values #157

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,201 changes: 1,289 additions & 912 deletions src/compiler/descriptor.ts

Large diffs are not rendered by default.

271 changes: 161 additions & 110 deletions src/compiler/plugin.ts

Large diffs are not rendered by default.

288 changes: 210 additions & 78 deletions src/descriptor.ts

Large diffs are not rendered by default.

93 changes: 89 additions & 4 deletions src/field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ export function wrapRepeatedType(type: any, fieldDescriptor: descriptor.FieldDes
return type;
}

/**
* Given the type T constructs T | undefined.
* Null is not added, cos Message.setWrapperField is not accepting null.
* @param type
*/
export function wrapNullableType(type: ts.TypeNode) {
return ts.factory.createUnionTypeNode([
type,
ts.factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword),
]);
}

/**
* @param {descriptor.FileDescriptorProto} rootDescriptor
* @param {descriptor.FieldDescriptorProto} fieldDescriptor
Expand All @@ -31,11 +43,15 @@ export function getMapType(rootDescriptor: descriptor.FileDescriptorProto, field
/**
* @param {descriptor.FieldDescriptorProto} fieldDescriptor
* @param {descriptor.FileDescriptorProto} rootDescriptor
* @param asObject to return message AsObject type reference
* @param asObjectPartial to return AsObjectPartial type referenct
* @returns {ts.TypeReferenceNode | ts.Identifier | ts.PropertyAccessExpression}
*/
export function getType(
fieldDescriptor: descriptor.FieldDescriptorProto,
rootDescriptor: descriptor.FileDescriptorProto,
asObject = false,
asObjectPartial = false,
): ts.TypeReferenceNode {
if (isMap(fieldDescriptor)) {
return getMapType(rootDescriptor, fieldDescriptor);
Expand Down Expand Up @@ -64,8 +80,14 @@ export function getType(
case descriptor.FieldDescriptorProto.Type.TYPE_BYTES:
return ts.factory.createTypeReferenceNode("Uint8Array");
case descriptor.FieldDescriptorProto.Type.TYPE_MESSAGE:
return type.getTypeReference(
rootDescriptor,
fieldDescriptor.type_name,
asObject,
asObjectPartial,
);
case descriptor.FieldDescriptorProto.Type.TYPE_ENUM:
return type.getTypeReference(rootDescriptor, fieldDescriptor.type_name)
return type.getTypeReference(rootDescriptor, fieldDescriptor.type_name);
default:
throw new Error("Unhandled type " + fieldDescriptor.type);
}
Expand Down Expand Up @@ -182,7 +204,7 @@ export function isOptional(
if (rootDescriptor.syntax == "proto3") {
return (
fieldDescriptor.label !=
descriptor.FieldDescriptorProto.Label.LABEL_REQUIRED ||
descriptor.FieldDescriptorProto.Label.LABEL_REQUIRED ||
fieldDescriptor.proto3_optional
);
}
Expand All @@ -191,6 +213,69 @@ export function isOptional(
descriptor.FieldDescriptorProto.Label.LABEL_OPTIONAL
);
}

/**
* Function is used to determine, whether the field
* must always be provided when passed to a constructor or fromObject method.
* @param {descriptor.FileDescriptorProto} rootDescriptor
* @param {descriptor.FieldDescriptorProto} fieldDescriptor
*/
export function canBeCreatedWithoutValue(
rootDescriptor: descriptor.FileDescriptorProto,
fieldDescriptor: descriptor.FieldDescriptorProto,
) {
return (
isOptional(rootDescriptor, fieldDescriptor) ||
// Both proto2 and proto3 don't track presence for maps and repeated fields.
// https://github.com/protocolbuffers/protobuf/blob/main/docs/field_presence.md#presence-in-proto2-apis
isMap(fieldDescriptor) ||
isRepeated(fieldDescriptor)
);
}

/**
* Function is used to determine, whether the field must be nullable
* in the toObject() method return type.
* Getters always
* Need to be in sync with getters.
* @param {descriptor.FileDescriptorProto} rootDescriptor
* @param {descriptor.FieldDescriptorProto} fieldDescriptor
*/
export function canHaveNullValue(
rootDescriptor: descriptor.FileDescriptorProto,
fieldDescriptor: descriptor.FieldDescriptorProto,
): boolean {
return (
isOptional(rootDescriptor, fieldDescriptor) &&
!(
isNumber(fieldDescriptor) ||
isEnum(fieldDescriptor) ||
isRepeated(fieldDescriptor) ||
isBytes(fieldDescriptor) ||
isString(fieldDescriptor) ||
isBoolean(fieldDescriptor) ||
isMap(fieldDescriptor)
)
// isRequiredWithoutExplicitDefault is needed, cos
// protoc-gen-ts does not throw an exception when deserializing a message
// that does not have some required fields
// (neither does Google's javascript implementation).
// Thanks to @tomasz-szypenbejl-td for reference.
) || isRequiredWithoutExplicitDefault(rootDescriptor, fieldDescriptor);
// Fields that are not optional and have explicit default return that default.
// Fields that are optional and are not messages, i.e.:
// (
// isNumber(fieldDescriptor) ||
// isEnum(fieldDescriptor) ||
// isRepeated(fieldDescriptor) ||
// isBytes(fieldDescriptor) ||
// isString(fieldDescriptor) ||
// isBoolean(fieldDescriptor) ||
// isMap(fieldDescriptor)
// ) === true
// return their default value (zero, [], new Unit8Array(), '', false, new Map())
}

/**
* @param {descriptor.FileDescriptorProto} rootDescriptor
* @param {descriptor.FieldDescriptorProto} fieldDescriptor
Expand Down Expand Up @@ -218,7 +303,7 @@ export function isString(fieldDescriptor: descriptor.FieldDescriptorProto) {
/**
* @param {descriptor.FieldDescriptorProto} fieldDescriptor
*/
export function isBytes(fieldDescriptor: descriptor.FieldDescriptorProto) {
export function isBytes(fieldDescriptor: descriptor.FieldDescriptorProto) {
return (
fieldDescriptor.type == descriptor.FieldDescriptorProto.Type.TYPE_BYTES
);
Expand Down Expand Up @@ -273,7 +358,7 @@ export function isPacked(
* @param {descriptor.FileDescriptorProto} rootDescriptor
* @param {descriptor.FieldDescriptorProto} fieldDescriptor
*/
export function hasPresenceGetter(
export function hasPresenceGetter(
rootDescriptor: descriptor.FileDescriptorProto,
fieldDescriptor: descriptor.FieldDescriptorProto,
) {
Expand Down
40 changes: 25 additions & 15 deletions src/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,9 +308,11 @@ function createUnaryRpcPromiseMethod(
// }
// }
const promiseBody = ts.factory.createCallExpression(
ts.factory.createPropertyAccessExpression(
ts.factory.createSuper(),
methodDescriptor.name,
ts.factory.createNonNullExpression(
ts.factory.createElementAccessExpression(
ts.factory.createSuper(),
ts.factory.createStringLiteral(methodDescriptor.name),
)
),
undefined,
[
Expand Down Expand Up @@ -794,9 +796,11 @@ function createUnaryRpcMethod(
[
ts.factory.createReturnStatement(
ts.factory.createCallExpression(
ts.factory.createPropertyAccessExpression(
ts.factory.createSuper(),
methodDescriptor.name,
ts.factory.createNonNullExpression(
ts.factory.createElementAccessExpression(
ts.factory.createSuper(),
ts.factory.createStringLiteral(methodDescriptor.name),
)
),
undefined,
[
Expand Down Expand Up @@ -895,9 +899,11 @@ function createClientStreamingRpcMethod(
[
ts.factory.createReturnStatement(
ts.factory.createCallExpression(
ts.factory.createPropertyAccessExpression(
ts.factory.createSuper(),
methodDescriptor.name,
ts.factory.createNonNullExpression(
ts.factory.createElementAccessExpression(
ts.factory.createSuper(),
ts.factory.createStringLiteral(methodDescriptor.name),
)
),
undefined,
[
Expand Down Expand Up @@ -976,9 +982,11 @@ function createServerStreamingRpcMethod(
[
ts.factory.createReturnStatement(
ts.factory.createCallExpression(
ts.factory.createPropertyAccessExpression(
ts.factory.createSuper(),
methodDescriptor.name,
ts.factory.createNonNullExpression(
ts.factory.createElementAccessExpression(
ts.factory.createSuper(),
ts.factory.createStringLiteral(methodDescriptor.name),
)
),
undefined,
[
Expand Down Expand Up @@ -1057,9 +1065,11 @@ function createBidiStreamingRpcMethod(
[
ts.factory.createReturnStatement(
ts.factory.createCallExpression(
ts.factory.createPropertyAccessExpression(
ts.factory.createSuper(),
methodDescriptor.name,
ts.factory.createNonNullExpression(
ts.factory.createElementAccessExpression(
ts.factory.createSuper(),
ts.factory.createStringLiteral(methodDescriptor.name),
)
),
undefined,
[
Expand Down
33 changes: 27 additions & 6 deletions src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,27 +68,41 @@ export function getTypeReferenceExpr(
export function getTypeReference(
rootDescriptor: descriptor.FileDescriptorProto,
typeName: string,
asObject = false, // add 'AsObject' to the end of the type reference
asObjectPartial = false, // add 'Partial', used with AsObject
): ts.TypeReferenceNode {
const path = symbolMap.get(typeName);

if (!path || !dependencyMap.has(path)) {
if (config.no_namespace) {
return ts.factory.createTypeReferenceNode(
removeRootParentName(typeName, rootDescriptor.package).replace(/\./g, ''),
addAsObject(
removeRootParentName(typeName, rootDescriptor.package).replace(/\./g, ""),
asObject,
asObjectPartial
),
);
}
return ts.factory.createTypeReferenceNode(
removeRootParentName(typeName, rootDescriptor.package),
addAsObject(
removeRootParentName(typeName, rootDescriptor.package),
asObject,
asObjectPartial
),
);
}

const name = removeNamespace(removeLeadingDot(typeName));
const name = addAsObject(
removeNamespace(removeLeadingDot(typeName)),
asObject,
asObjectPartial
);

return ts.factory.createTypeReferenceNode(
ts.factory.createQualifiedName(
dependencyMap.get(path)!,
name,
)
dependencyMap.get(path)!,
name,
),
);
}

Expand All @@ -106,6 +120,13 @@ function removeRootParentName(name: string, parentName: string): string {
);
}

export function addAsObject(name: string, asObject: boolean, partial = false) {
if (asObject) {
return name + (config.no_namespace ? "" : ".") + "AsObject" + (partial ? "Partial" : "");
}
return name;
}

function removeNamespace(name: string): string {
if(config.no_namespace)
{
Expand Down
17 changes: 10 additions & 7 deletions test/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,20 @@ diff_and_update(
checked = PROT_SOURCES,
)

# Allow any subpackages to reference the config
exports_files(
[
"tsconfig.json",
],
visibility = [":__subpackages__"],
)


ts_project(
name = "tests",
srcs = glob(["*.ts"]),
tsconfig = {
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"noImplicitAny": True
},
},
tsconfig = {}, # no special options
extends = "//test:tsconfig.json",
deps = [
"@npm//@types/jasmine",
"@npm//@types/node",
Expand Down
24 changes: 19 additions & 5 deletions test/conformance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ export namespace main {
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], this.#one_of_decls);
if (!Array.isArray(data) && typeof data == "object") { }
}
static fromObject(data: {}): Message {
static fromObject(data?: Message.AsObjectPartial): Message {
if (!data) {
return new Message();
}
const message = new Message({});
return message;
}
toObject() {
const data: {} = {};
const data: Message.AsObject = {};
return data;
}
serialize(): Uint8Array;
Expand Down Expand Up @@ -46,19 +49,26 @@ export namespace main {
return Message.deserialize(bytes);
}
}
export namespace Message {
export type AsObject = {};
export type AsObjectPartial = {};
}
export class MessageResult extends pb_1.Message {
#one_of_decls: number[][] = [];
constructor(data?: any[] | {}) {
super();
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], this.#one_of_decls);
if (!Array.isArray(data) && typeof data == "object") { }
}
static fromObject(data: {}): MessageResult {
static fromObject(data?: MessageResult.AsObjectPartial): MessageResult {
if (!data) {
return new MessageResult();
}
const message = new MessageResult({});
return message;
}
toObject() {
const data: {} = {};
const data: MessageResult.AsObject = {};
return data;
}
serialize(): Uint8Array;
Expand Down Expand Up @@ -86,6 +96,10 @@ export namespace main {
return MessageResult.deserialize(bytes);
}
}
export namespace MessageResult {
export type AsObject = {};
export type AsObjectPartial = {};
}
interface GrpcUnaryServiceInterface<P, R> {
(message: P, metadata: grpc_1.Metadata, options: grpc_1.CallOptions, callback: grpc_1.requestCallback<R>): grpc_1.ClientUnaryCall;
(message: P, metadata: grpc_1.Metadata, callback: grpc_1.requestCallback<R>): grpc_1.ClientUnaryCall;
Expand Down Expand Up @@ -130,7 +144,7 @@ export namespace main {
super(address, credentials, options);
}
Method: GrpcUnaryServiceInterface<Message, MessageResult> = (message: Message, metadata: grpc_1.Metadata | grpc_1.CallOptions | grpc_1.requestCallback<MessageResult>, options?: grpc_1.CallOptions | grpc_1.requestCallback<MessageResult>, callback?: grpc_1.requestCallback<MessageResult>): grpc_1.ClientUnaryCall => {
return super.Method(message, metadata, options, callback);
return super["Method"]!(message, metadata, options, callback);
};
}
}
9 changes: 2 additions & 7 deletions test/conformance/map/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,8 @@ diff_and_update(
ts_project(
name = "test_lib",
srcs = glob(["**/*.ts"]),
tsconfig = {
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"noImplicitAny": True
},
},
tsconfig = {}, # no special options
extends = "//test:tsconfig.json",
deps = [
"@npm//@types/jasmine",
"@npm//@types/node",
Expand Down
Loading