@@ -10,6 +10,7 @@ import {
1010 ProtoOneof ,
1111 Annotation
1212} from './types' ;
13+ import { MergeReporter , formatField } from './MergeReporter' ;
1314
1415const DEPRECATED : Annotation = { name : 'deprecated' , value : 'true' } ;
1516
@@ -55,20 +56,25 @@ function addDeprecated<T extends HasAnnotations>(item: T): T {
5556}
5657
5758/**
58- * Check if optional added or removed. If so, push error and return true
59+ * Check if optional modifier changed and report it.
5960 */
60- function hasOptionalError (
61+ function checkOptionalChange (
6162 source : ProtoField ,
6263 upcoming : ProtoField ,
6364 msgName : string ,
64- errors : string [ ]
65+ reporter ?: MergeReporter
6566) : boolean {
6667 const sourceOptional = source . modifier === 'optional' ;
6768 const upcomingOptional = upcoming . modifier === 'optional' ;
6869
6970 if ( sourceOptional !== upcomingOptional ) {
70- const change = sourceOptional ? 'removed' : 'added' ;
71- errors . push ( `${ msgName } .${ source . name } : optional ${ change } ` ) ;
71+ reporter ?. addFieldChange ( {
72+ messageName : msgName ,
73+ changeType : 'optional_error' ,
74+ fieldName : source . name ,
75+ existingType : formatField ( source ) ,
76+ incomingType : formatField ( upcoming )
77+ } ) ;
7278 return true ;
7379 }
7480 return false ;
@@ -88,7 +94,7 @@ function mergeField(
8894 sourceField : ProtoField ,
8995 upcomingMap : Map < string , ProtoField > ,
9096 msgName : string ,
91- errors : string [ ]
97+ reporter ?: MergeReporter
9298) : ProtoField {
9399 if ( isDeprecated ( sourceField ) ) {
94100 return sourceField ;
@@ -100,30 +106,48 @@ function mergeField(
100106 if ( upcomingField ) {
101107 upcomingMap . delete ( baseName ) ;
102108
103- if ( hasOptionalError ( sourceField , upcomingField , msgName , errors ) ) {
104- return sourceField ;
105- }
109+ // Report optional modifier changes (but don't stop)
110+ checkOptionalChange ( sourceField , upcomingField , msgName , reporter ) ;
106111
107112 if ( fieldsMatch ( sourceField , upcomingField ) ) {
108113 return sourceField ;
109114 } else {
110115 // Type or repeated change - deprecate and version
111116 const newName = `${ baseName } _${ getFieldVersion ( sourceField . name ) + 1 } ` ;
112117 upcomingMap . set ( newName , { ...upcomingField , name : newName } ) ;
118+ reporter ?. addFieldChange ( {
119+ messageName : msgName ,
120+ changeType : 'type_changed' ,
121+ fieldName : sourceField . name ,
122+ existingType : formatField ( sourceField ) ,
123+ incomingType : formatField ( upcomingField ) ,
124+ versionedName : newName
125+ } ) ;
113126 return addDeprecated ( sourceField ) ;
114127 }
115128 } else {
129+ reporter ?. addFieldChange ( {
130+ messageName : msgName ,
131+ changeType : 'removed' ,
132+ fieldName : sourceField . name ,
133+ existingType : formatField ( sourceField )
134+ } ) ;
116135 return addDeprecated ( sourceField ) ;
117136 }
118137}
119138
139+ /** Check if field name is a versioned name (ends with _N where N is a number) */
140+ function isVersionedName ( name : string ) : boolean {
141+ return / _ \d + $ / . test ( name ) ;
142+ }
143+
120144/**
121145 * Merge a source message with an upcoming message.
122146 */
123147export function mergeMessage (
124148 sourceMsg : ProtoMessage ,
125149 upcomingMsg : ProtoMessage ,
126- errors : string [ ]
150+ reporter ?: MergeReporter
127151) : ProtoMessage {
128152 const upcomingByName = new Map ( upcomingMsg . fields . map ( f => [ f . name , f ] ) ) ;
129153
@@ -133,7 +157,7 @@ export function mergeMessage(
133157 // Process regular fields
134158 for ( const sourceField of sourceMsg . fields ) {
135159 maxFieldNumber = Math . max ( maxFieldNumber , sourceField . number ) ;
136- mergedFields . push ( mergeField ( sourceField , upcomingByName , sourceMsg . name , errors ) ) ;
160+ mergedFields . push ( mergeField ( sourceField , upcomingByName , sourceMsg . name , reporter ) ) ;
137161 }
138162
139163 // Process oneofs
@@ -155,7 +179,7 @@ export function mergeMessage(
155179 const mergedOneofFields : ProtoField [ ] = [ ] ;
156180 for ( const sourceField of sourceOneof . fields ) {
157181 maxFieldNumber = Math . max ( maxFieldNumber , sourceField . number ) ;
158- mergedOneofFields . push ( mergeField ( sourceField , upcomingOneofByName , sourceMsg . name , errors ) ) ;
182+ mergedOneofFields . push ( mergeField ( sourceField , upcomingOneofByName , sourceMsg . name , reporter ) ) ;
159183 }
160184
161185 mergedOneofs . push ( { ...sourceOneof , fields : mergedOneofFields } ) ;
@@ -165,6 +189,14 @@ export function mergeMessage(
165189
166190 // Assign field max number to remaining fields.
167191 for ( const field of upcomingByName . values ( ) ) {
192+ if ( ! isVersionedName ( field . name ) ) {
193+ reporter ?. addFieldChange ( {
194+ messageName : sourceMsg . name ,
195+ changeType : 'added' ,
196+ fieldName : field . name ,
197+ incomingType : formatField ( field )
198+ } ) ;
199+ }
168200 mergedFields . push ( { ...field , number : ++ maxFieldNumber } ) ;
169201 }
170202
@@ -174,6 +206,14 @@ export function mergeMessage(
174206 const remaining = oneofMaps . get ( oneof . name ) ;
175207 if ( remaining ) {
176208 for ( const field of remaining . values ( ) ) {
209+ if ( ! isVersionedName ( field . name ) ) {
210+ reporter ?. addFieldChange ( {
211+ messageName : sourceMsg . name ,
212+ changeType : 'added' ,
213+ fieldName : `${ oneof . name } .${ field . name } ` ,
214+ incomingType : formatField ( field )
215+ } ) ;
216+ }
177217 oneof . fields . push ( { ...field , number : ++ maxFieldNumber } ) ;
178218 }
179219 }
@@ -192,7 +232,8 @@ export function mergeMessage(
192232 */
193233export function mergeEnum (
194234 sourceEnum : ProtoEnum ,
195- upcomingEnum : ProtoEnum
235+ upcomingEnum : ProtoEnum ,
236+ reporter ?: MergeReporter
196237) : ProtoEnum {
197238 const sourceValueMap = new Map ( sourceEnum . values . map ( v => [ v . name , v ] ) ) ;
198239 const upcomingValueMap = new Map ( upcomingEnum . values . map ( v => [ v . name , v ] ) ) ;
@@ -208,12 +249,22 @@ export function mergeEnum(
208249 if ( upcomingValue ) {
209250 mergedValues . push ( sourceValue ) ;
210251 } else {
252+ reporter ?. addEnumChange ( {
253+ enumName : sourceEnum . name ,
254+ changeType : 'removed' ,
255+ valueName : sourceValue . name
256+ } ) ;
211257 mergedValues . push ( addDeprecated ( sourceValue ) ) ;
212258 }
213259 }
214260
215261 for ( const [ valueName , upcomingValue ] of upcomingValueMap ) {
216262 if ( ! sourceValueMap . has ( valueName ) ) {
263+ reporter ?. addEnumChange ( {
264+ enumName : sourceEnum . name ,
265+ changeType : 'added' ,
266+ valueName : valueName
267+ } ) ;
217268 mergedValues . push ( {
218269 ...upcomingValue ,
219270 number : ++ maxValueNumber
0 commit comments