@@ -409,22 +409,23 @@ function buildType(ref, type) {
409409 prop = prop . substring ( 1 , prop . charAt ( 0 ) === "[" ? prop . length - 1 : prop . length ) ;
410410 var jsType = toJsType ( field ) ;
411411 var nullable = false ;
412-
413- // New behaviour - respect explicit optional semantics in both proto2 and proto3
414- if ( config [ "force-optional" ] ) {
412+ if ( config [ "null-semantics" ] ) {
413+ // With semantic nulls, decide which fields are required for the current protobuf version
414+ // Fields with implicit defaults in proto3 are required for the purpose of constructing objects
415+ // Optional fields can be undefined, i.e. they can be omitted for the source object altogether
415416 if ( isOptional ( field , syntax ) || field . partOf || field . repeated || field . map ) {
416- jsType = jsType + "|null" ;
417+ jsType = jsType + "|null|undefined " ;
417418 nullable = true ;
418419 }
419420 }
420- // Old behaviour - field.optional is true for all fields in proto3
421421 else {
422+ // Without semantic nulls, everything is optional in proto3
423+ // Do not allow |undefined to keep backwards compatibility
422424 if ( field . optional ) {
423425 jsType = jsType + "|null" ;
424426 nullable = true ;
425427 }
426428 }
427-
428429 typeDef . push ( "@property {" + jsType + "} " + ( nullable ? "[" + prop + "]" : prop ) + " " + ( field . comment || type . name + " " + field . name ) ) ;
429430 } ) ;
430431 push ( "" ) ;
@@ -451,19 +452,19 @@ function buildType(ref, type) {
451452 if ( config . comments ) {
452453 push ( "" ) ;
453454 var jsType = toJsType ( field ) ;
454-
455- // New behaviour - fields explicitly marked as optional and members of a one-of are nullable
456- // Maps and repeated fields are not nullable, they default to empty instances
457- if ( config [ "force-optional" ] ) {
455+ if ( config [ "null-semantics" ] ) {
456+ // With semantic nulls, fields are nullable if they are explicitly optional or part of a one-of
457+ // Maps, repeated values and fields with implicit defaults are never null after construction
458+ // Members are never undefined, at a minimum they are initialized to null
458459 if ( isOptional ( field , syntax ) || field . partOf )
459- jsType = jsType + "|null|undefined " ;
460+ jsType = jsType + "|null" ;
460461 }
461- // Old behaviour - field.optional is true for all fields in proto3
462462 else {
463+ // Without semantic nulls, everything is optional in proto3
464+ // Keep |undefined for backwards compatibility
463465 if ( field . optional && ! field . map && ! field . repeated && ( field . resolvedType instanceof Type || config [ "null-defaults" ] ) || field . partOf )
464466 jsType = jsType + "|null|undefined" ;
465467 }
466-
467468 pushComment ( [
468469 field . comment || type . name + " " + field . name + "." ,
469470 "@member {" + jsType + "} " + field . name ,
@@ -474,9 +475,9 @@ function buildType(ref, type) {
474475 push ( "" ) ;
475476 firstField = false ;
476477 }
477- // New behaviour sets a null default when the optional keyword is used explicitly
478- // Old behaviour considers all proto3 fields optional and uses the null-defaults config flag
479- var nullDefault = config [ "force-optional " ]
478+ // Semantic nulls respect the optional semantics for the current protobuf version
479+ // Otherwise use field.optional, which doesn't consider proto3, maps, repeated fields etc.
480+ var nullDefault = config [ "null-semantics " ]
480481 ? isOptional ( field , syntax )
481482 : field . optional && config [ "null-defaults" ] ;
482483 if ( field . repeated )
0 commit comments