@@ -354,6 +354,27 @@ function toJsType(field) {
354
354
return type ;
355
355
}
356
356
357
+ function isOptional ( type , field ) {
358
+
359
+ // Figure out if a field is explicitly marked optional, depending on the proto syntax (proto2 vs proto3)
360
+ // If the syntax has not been recorded in the AST, proto2 semantics will be the default
361
+
362
+ var syntax = null ;
363
+ var namespace = type ;
364
+
365
+ while ( syntax === null && namespace !== null ) {
366
+ if ( namespace . options != null && "syntax" in namespace . options )
367
+ syntax = namespace . options [ "syntax" ]
368
+ else
369
+ namespace = namespace . parent
370
+ }
371
+
372
+ if ( syntax === "proto3" )
373
+ return field . proto3Optional
374
+ else
375
+ return field . optional
376
+ }
377
+
357
378
function buildType ( ref , type ) {
358
379
359
380
if ( config . comments ) {
@@ -366,20 +387,25 @@ function buildType(ref, type) {
366
387
var prop = util . safeProp ( field . name ) ; // either .name or ["name"]
367
388
prop = prop . substring ( 1 , prop . charAt ( 0 ) === "[" ? prop . length - 1 : prop . length ) ;
368
389
var jsType = toJsType ( field ) ;
390
+ var nullable = false ;
369
391
370
392
// New behaviour - fields explicitly marked as optional and members of a one-of are nullable
371
393
// Maps and repeated fields are not explicitly optional, they default to empty instances
372
394
if ( config [ "force-optional" ] ) {
373
- if ( field . explicitOptional || field . partOf )
395
+ if ( isOptional ( type , field ) || field . partOf ) {
374
396
jsType = jsType + "|null|undefined" ;
397
+ nullable = true ;
398
+ }
375
399
}
376
400
// Old behaviour - field.optional is true for all fields in proto3
377
401
else {
378
- if ( field . optional )
402
+ if ( field . optional ) {
379
403
jsType = jsType + "|null" ;
404
+ nullable = true ;
405
+ }
380
406
}
381
407
382
- typeDef . push ( "@property {" + jsType + "} " + ( field . optional ? "[" + prop + "]" : prop ) + " " + ( field . comment || type . name + " " + field . name ) ) ;
408
+ typeDef . push ( "@property {" + jsType + "} " + ( nullable ? "[" + prop + "]" : prop ) + " " + ( field . comment || type . name + " " + field . name ) ) ;
383
409
} ) ;
384
410
push ( "" ) ;
385
411
pushComment ( typeDef ) ;
@@ -409,7 +435,7 @@ function buildType(ref, type) {
409
435
// New behaviour - fields explicitly marked as optional and members of a one-of are nullable
410
436
// Maps and repeated fields are not explicitly optional, they default to empty instances
411
437
if ( config [ "force-optional" ] ) {
412
- if ( field . explicitOptional || field . partOf )
438
+ if ( isOptional ( type , field ) || field . partOf )
413
439
jsType = jsType + "|null|undefined" ;
414
440
}
415
441
// Old behaviour - field.optional is true for all fields in proto3
@@ -431,7 +457,7 @@ function buildType(ref, type) {
431
457
// New behaviour sets a null default when the optional keyword is used explicitly
432
458
// Old behaviour considers all proto3 fields optional and uses the null-defaults config flag
433
459
var nullDefault = config [ "force-optional" ]
434
- ? field . explicitOptional
460
+ ? isOptional ( type , field )
435
461
: field . optional && config [ "null-defaults" ] ;
436
462
if ( field . repeated )
437
463
push ( escapeName ( type . name ) + ".prototype" + prop + " = $util.emptyArray;" ) ; // overwritten in constructor
0 commit comments