Skip to content

Commit a656361

Browse files
Add tests for proto targets and support valid edition 2023 inputs
1 parent 869a95b commit a656361

File tree

5 files changed

+297
-37
lines changed

5 files changed

+297
-37
lines changed

cli/targets/proto.js

+30-15
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ function buildRoot(root) {
102102
if (pkg.length)
103103
out.push("", "package " + pkg.join(".") + ";");
104104

105-
buildOptions(ptr);
105+
buildOptions(ptr, ["edition", "syntax"]);
106106
ptr.nestedArray.forEach(build);
107107
}
108108

@@ -184,8 +184,12 @@ function buildType(type) {
184184
}
185185

186186
function buildField(field, passExtend) {
187-
if (field.partOf || field.declaringField || field.extend !== undefined && !passExtend)
187+
if (field.partOf && !field.partOf.isProto3Optional) {
188188
return;
189+
}
190+
if (field.declaringField || field.extend !== undefined && !passExtend) {
191+
return;
192+
}
189193
if (first) {
190194
first = false;
191195
push("");
@@ -199,8 +203,10 @@ function buildField(field, passExtend) {
199203
sb.push("map<" + field.keyType + ", " + field.type + ">");
200204
else if (field.repeated)
201205
sb.push("repeated", field.type);
202-
else if (syntax === 2 || field.parent.group)
206+
else if (syntax === 2)
203207
sb.push(field.required ? "required" : "optional", field.type);
208+
else if (syntax === 3 && field.hasPresence)
209+
sb.push("optional", field.type);
204210
else
205211
sb.push(field.type);
206212
sb.push(underScore(field.name), "=", field.id);
@@ -211,7 +217,7 @@ function buildField(field, passExtend) {
211217
}
212218

213219
function buildGroup(field) {
214-
push(field.rule + " group " + field.resolvedType.name + " = " + field.id + " {");
220+
push((field.rule || "optional") + " group " + field.resolvedType.name + " = " + field.id + " {");
215221
++indent;
216222
buildOptions(field.resolvedType);
217223
first = true;
@@ -223,20 +229,16 @@ function buildGroup(field) {
223229
}
224230

225231
function buildFieldOptions(field) {
226-
var keys;
227-
if (!field.options || !(keys = Object.keys(field.options)).length)
228-
return null;
232+
var keys = [];
233+
if (field.options) {
234+
keys = Object.keys(field.options);
235+
}
229236
var sb = [];
230237
keys.forEach(function(key) {
238+
if (key === "proto3_optional" || key === "packed" || key.startsWith("features.")) return;
239+
231240
var val = field.options[key];
232-
var wireType = types.packed[field.resolvedType instanceof Enum ? "int32" : field.type];
233241
switch (key) {
234-
case "packed":
235-
val = Boolean(val);
236-
// skip when not packable or syntax default
237-
if (wireType === undefined || syntax === 3 === val)
238-
return;
239-
break;
240242
case "default":
241243
if (syntax === 3)
242244
return;
@@ -253,6 +255,14 @@ function buildFieldOptions(field) {
253255
}
254256
sb.push(key + "=" + val);
255257
});
258+
var packable = types.packed[field.resolvedType instanceof Enum ? "int32" : field.type];
259+
if (packable !== undefined) {
260+
if (field.packed && syntax == 2) {
261+
sb.push("packed=true");
262+
} else if(!field.packed && syntax == 3) {
263+
sb.push("packed=false");
264+
}
265+
}
256266
return sb.length
257267
? "[" + sb.join(", ") + "]"
258268
: null;
@@ -282,6 +292,10 @@ function consolidateExtends(nested) {
282292
}
283293

284294
function buildOneOf(oneof) {
295+
if (oneof.isProto3Optional) {
296+
return;
297+
}
298+
285299
push("");
286300
push("oneof " + underScore(oneof.name) + " {");
287301
++indent; first = true;
@@ -311,11 +325,12 @@ function buildMethod(method) {
311325
push(method.type + " " + method.name + " (" + (method.requestStream ? "stream " : "") + method.requestType + ") returns (" + (method.responseStream ? "stream " : "") + method.responseType + ");");
312326
}
313327

314-
function buildOptions(object) {
328+
function buildOptions(object, ignore = []) {
315329
if (!object.options)
316330
return;
317331
first = true;
318332
Object.keys(object.options).forEach(function(key) {
333+
if (ignore.includes(key) || key.startsWith("features.")) return;
319334
if (first) {
320335
first = false;
321336
push("");

cli/targets/static.js

+6-22
Original file line numberDiff line numberDiff line change
@@ -362,24 +362,8 @@ function toJsType(field, parentIsInterface = false) {
362362
return type;
363363
}
364364

365-
function hasPresence(field) {
366-
if (field.repeated || field.map) {
367-
return false;
368-
}
369-
return field.partOf || // oneofs
370-
field.declaringField || field.extensionField || // extensions
371-
field._features.field_presence === "EXPLICIT";
372-
}
373-
374-
function isProto3Optional(oneof) {
375-
376-
if (oneof.fieldsArray == null || oneof.fieldsArray.length !== 1) {
377-
return false;
378-
}
379-
380-
var field = oneof.fieldsArray[0];
381-
382-
return field.options != null && field.options["proto3_optional"] === true;
365+
function isNullable(field) {
366+
return field.hasPresence && !field.required
383367
}
384368

385369
function buildType(ref, type) {
@@ -400,7 +384,7 @@ function buildType(ref, type) {
400384
// Implicit fields (proto3), maps and lists can be omitted, but if specified must be non-null
401385
// Implicit fields will take their default value when the message is constructed
402386
if (field.optional) {
403-
if (hasPresence(field)) {
387+
if (isNullable(field)) {
404388
jsType = jsType + "|null|undefined";
405389
nullable = true;
406390
}
@@ -448,7 +432,7 @@ function buildType(ref, type) {
448432
// With semantic nulls, fields are nullable if they are explicitly optional or part of a one-of
449433
// Maps, repeated values and fields with implicit defaults are never null after construction
450434
// Members are never undefined, at a minimum they are initialized to null
451-
if (hasPresence(field) && field.optional) {
435+
if (isNullable(field)) {
452436
jsType = jsType + "|null";
453437
}
454438
}
@@ -472,7 +456,7 @@ function buildType(ref, type) {
472456
// With semantic nulls, only explict optional fields and one-of members are null by default
473457
// Otherwise use field.optional, which doesn't consider proto3, maps, repeated fields etc.
474458
var nullDefault = config["null-semantics"]
475-
? hasPresence(field) && field.optional
459+
? isNullable(field)
476460
: field.optional && config["null-defaults"];
477461
if (field.repeated)
478462
push(escapeName(type.name) + ".prototype" + prop + " = $util.emptyArray;"); // overwritten in constructor
@@ -504,7 +488,7 @@ function buildType(ref, type) {
504488
}
505489
oneof.resolve();
506490
push("");
507-
if (isProto3Optional(oneof)) {
491+
if (oneof.isProto3Optional) {
508492
push("// Virtual OneOf for proto3 optional field");
509493
}
510494
else {

src/field.js

+17
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,23 @@ Object.defineProperty(Field.prototype, "packed", {
228228
}
229229
});
230230

231+
/**
232+
* Determines whether this field tracks presence.
233+
* @name Field#hasPresence
234+
* @type {boolean}
235+
* @readonly
236+
*/
237+
Object.defineProperty(Field.prototype, "hasPresence", {
238+
get: function() {
239+
if (this.repeated || this.map) {
240+
return false;
241+
}
242+
return this.partOf || // oneofs
243+
this.declaringField || this.extensionField || // extensions
244+
this._features.field_presence !== "IMPLICIT";
245+
}
246+
});
247+
231248
/**
232249
* @override
233250
*/

src/oneof.js

+19
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,25 @@ OneOf.prototype.onRemove = function onRemove(parent) {
171171
ReflectionObject.prototype.onRemove.call(this, parent);
172172
};
173173

174+
/**
175+
* Determines whether this field corresponds to a synthetic oneof created for
176+
* a proto3 optional field. No behavioral logic should depend on this, but it
177+
* can be relevant for reflection.
178+
* @name OneOf#isProto3Optional
179+
* @type {boolean}
180+
* @readonly
181+
*/
182+
Object.defineProperty(OneOf.prototype, "isProto3Optional", {
183+
get: function() {
184+
if (this.fieldsArray == null || this.fieldsArray.length !== 1) {
185+
return false;
186+
}
187+
188+
var field = this.fieldsArray[0];
189+
return field.options != null && field.options["proto3_optional"] === true;
190+
}
191+
});
192+
174193
/**
175194
* Decorator function as returned by {@link OneOf.d} (TypeScript).
176195
* @typedef OneOfDecorator

0 commit comments

Comments
 (0)