11import { JSONParser } from "@streamparser/json"
2+ import * as fs from "fs"
3+ import * as path from "path"
4+ import * as os from "os"
25
36// Fallback type definition based on the error message: "Property 'value' is optional in type 'ParsedElementInfo'"
47type ParsedElementInfo = {
@@ -9,8 +12,8 @@ type ParsedElementInfo = {
912}
1013
1114export interface ReplacementItem {
12- old_str : string
13- new_str : string
15+ old_string : string
16+ new_string : string
1417}
1518
1619export interface ChangeLocation {
@@ -27,109 +30,243 @@ export class StreamingJsonReplacer {
2730 private onErrorCallback : ( error : Error ) => void
2831 private itemsProcessed : number = 0
2932 private successfullyParsedItems : ReplacementItem [ ] = [ ]
33+ private logFilePath : string
3034
3135 constructor (
3236 initialContent : string ,
3337 onContentUpdatedCallback : ( newContent : string , isFinalItem : boolean , changeLocation ?: ChangeLocation ) => void ,
3438 onErrorCallback : ( error : Error ) => void ,
3539 ) {
40+ // Initialize log file path
41+ const timestamp = new Date ( ) . toISOString ( ) . replace ( / [: .] / g, "-" )
42+ this . logFilePath = path . join ( os . homedir ( ) , "Documents" , `streaming-json-replacer-debug-${ timestamp } .log` )
43+
44+ // Initialize log file
45+ this . log ( "StreamingJsonReplacer Debug Log Started" , "INFO" )
46+ this . log ( "Timestamp: " + new Date ( ) . toISOString ( ) , "INFO" )
47+ this . log ( "Constructor called with initial content length: " + initialContent . length , "INFO" )
48+ this . log ( "Initial content preview: " + initialContent . substring ( 0 , 200 ) + "..." , "INFO" )
49+
3650 this . currentFileContent = initialContent
3751 this . onContentUpdated = onContentUpdatedCallback
3852 this . onErrorCallback = onErrorCallback
3953
40- this . parser = new JSONParser ( { paths : [ "$.replacements.*" ] } )
54+ this . log ( "Initializing JSONParser with paths: ['$.*']" , "INFO" )
55+ this . parser = new JSONParser ( { paths : [ "$.*" ] } )
4156
4257 this . parser . onValue = ( parsedElementInfo : ParsedElementInfo ) => {
58+ this . log ( "onValue callback triggered" )
59+ this . log ( "parsedElementInfo: " + JSON . stringify ( parsedElementInfo , null , 2 ) )
60+
4361 const { value } = parsedElementInfo // Destructure to get value, which might be undefined
62+ this . log ( "Extracted value: " + JSON . stringify ( value ) )
63+ this . log ( "Value type: " + typeof value )
64+
4465 // This callback is triggered for each item matched by '$.replacements.*'
45- if ( value && typeof value === "object" && "old_str" in value && "new_str" in value ) {
66+ if ( value && typeof value === "object" && "old_string" in value && "new_string" in value ) {
67+ this . log ( "Found valid replacement item structure" )
4668 const item = value as ReplacementItem // Value here is confirmed to be an object
69+ this . log ( "Replacement item: " + JSON . stringify ( item , null , 2 ) )
70+
71+ if ( typeof item . old_string === "string" && typeof item . new_string === "string" ) {
72+ this . log ( "Item has valid string types for old_string and new_string" )
73+ this . log ( "old_string length: " + item . old_string . length )
74+ this . log ( "new_string length: " + item . new_string . length )
75+ this . log (
76+ "old_string preview: " +
77+ ( item . old_string . substring ( 0 , 100 ) + ( item . old_string . length > 100 ? "..." : "" ) ) ,
78+ )
79+ this . log (
80+ "new_string preview: " +
81+ ( item . new_string . substring ( 0 , 100 ) + ( item . new_string . length > 100 ? "..." : "" ) ) ,
82+ )
4783
48- if ( typeof item . old_str === "string" && typeof item . new_str === "string" ) {
4984 this . successfullyParsedItems . push ( item ) // Store the structurally valid item
85+ this . log ( "Added item to successfullyParsedItems. Total count: " + this . successfullyParsedItems . length )
86+
87+ if ( this . currentFileContent . includes ( item . old_string ) ) {
88+ this . log ( "old_string found in current file content - proceeding with replacement" )
5089
51- if ( this . currentFileContent . includes ( item . old_str ) ) {
5290 // Calculate the change location before making the replacement
53- const changeLocation = this . calculateChangeLocation ( item . old_str , item . new_str )
91+ const changeLocation = this . calculateChangeLocation ( item . old_string , item . new_string )
92+ this . log ( "Calculated change location: " + JSON . stringify ( changeLocation ) )
93+
94+ const beforeLength = this . currentFileContent . length
95+ this . currentFileContent = this . currentFileContent . replace ( item . old_string , item . new_string )
96+ const afterLength = this . currentFileContent . length
97+ this . log ( "Content length before replacement: " + beforeLength )
98+ this . log ( "Content length after replacement: " + afterLength )
99+ this . log ( "Length difference: " + ( afterLength - beforeLength ) )
54100
55- this . currentFileContent = this . currentFileContent . replace ( item . old_str , item . new_str )
56101 this . itemsProcessed ++
102+ this . log ( "Incremented itemsProcessed to: " + this . itemsProcessed )
103+
57104 // Notify that an item has been processed. The `isFinalItem` argument here is tricky
58105 // as we don't know from the parser alone if this is the *absolute* last item
59106 // until the stream ends. The caller (Task.ts) will manage the final update.
60107 // For now, we'll pass `false` and let Task.ts handle the final diff view update.
108+ this . log ( "Calling onContentUpdated callback" )
61109 this . onContentUpdated ( this . currentFileContent , false , changeLocation )
110+ this . log ( "onContentUpdated callback completed" )
62111 } else {
63- const snippet = item . old_str . length > 50 ? item . old_str . substring ( 0 , 47 ) + "..." : item . old_str
64- const error = new Error ( `Streaming Replacement failed: 'old_str' not found. Snippet: "${ snippet } "` )
112+ this . log ( "old_string NOT found in current file content - generating error" , "ERROR" )
113+ this . log ( "Current file content length: " + this . currentFileContent . length )
114+ this . log ( "Current file content preview: " + this . currentFileContent . substring ( 0 , 200 ) + "..." )
115+
116+ const snippet = item . old_string . length > 50 ? item . old_string . substring ( 0 , 47 ) + "..." : item . old_string
117+ const error = new Error ( `Streaming Replacement failed: 'old_string' not found. Snippet: "${ snippet } "` )
118+ this . log ( "Calling onErrorCallback with error: " + error . message , "ERROR" )
65119 this . onErrorCallback ( error ) // Call our own error callback
66120 }
67121 } else {
122+ this . log (
123+ "Invalid string types - old_string type: " +
124+ typeof item . old_string +
125+ ", new_string type: " +
126+ typeof item . new_string ,
127+ "ERROR" ,
128+ )
68129 const error = new Error ( `Invalid item structure in replacements stream: ${ JSON . stringify ( item ) } ` )
130+ this . log ( "Calling onErrorCallback with error: " + error . message , "ERROR" )
69131 this . onErrorCallback ( error ) // Call our own error callback
70132 }
71133 } else if ( value && ( Array . isArray ( value ) || ( typeof value === "object" && "replacements" in value ) ) ) {
72134 // This might be the 'replacements' array itself or the root object.
73135 // The `paths: ['$.replacements.*']` should mean we only get items.
74136 // If we get here, it's likely the root object if paths wasn't specific enough or if it's an empty replacements array.
75- console . log ( "Streaming parser emitted container:" , value )
137+ this . log ( "Streaming parser emitted container: " + JSON . stringify ( value ) )
138+ this . log (
139+ "Container type - isArray: " +
140+ Array . isArray ( value ) +
141+ ", hasReplacements: " +
142+ ( typeof value === "object" && "replacements" in value ) ,
143+ )
76144 } else {
77145 // Value is not a ReplacementItem or a known container, could be an issue with the JSON structure or path.
78146 // If `paths` is correct, this path should ideally not be hit often for valid streams.
79- console . warn ( "Streaming parser emitted unexpected value:" , value )
147+ this . log ( "Streaming parser emitted unexpected value: " + JSON . stringify ( value ) , "WARN" )
148+ this . log ( "Unexpected value type: " + typeof value , "WARN" )
149+ this . log ( "Has old_string: " + ( value && typeof value === "object" && "old_string" in value ) , "WARN" )
150+ this . log ( "Has new_string: " + ( value && typeof value === "object" && "new_string" in value ) , "WARN" )
80151 }
81152 }
82153
83154 this . parser . onError = ( err : Error ) => {
155+ this . log ( "Parser onError callback triggered" , "ERROR" )
156+ this . log ( "Error details: " + JSON . stringify ( err ) , "ERROR" )
157+ this . log ( "Error message: " + err . message , "ERROR" )
158+ this . log ( "Error stack: " + err . stack , "ERROR" )
159+
84160 // Propagate the error to the caller via the callback
161+ this . log ( "Calling onErrorCallback with parser error" , "ERROR" )
85162 this . onErrorCallback ( err )
86163 // Note: The @streamparser /json library might throw synchronously on write if onError is not set,
87164 // or if it re-throws. We'll ensure Task.ts wraps write/end in try-catch.
88165 }
166+
167+ this . log ( "Constructor completed - parser setup finished" )
168+
169+ // Log to console where the debug file is located
170+ console . log ( `[StreamingJsonReplacer] Debug logging to file: ${ this . logFilePath } ` )
89171 }
90172
91173 public write ( jsonChunk : string ) : void {
92- // Errors during write will be caught by the parser's onError or thrown.
93- this . parser . write ( jsonChunk )
174+ this . log ( "write() called" )
175+ this . log ( "JSON chunk length: " + jsonChunk . length )
176+ this . log ( "JSON chunk preview: " + jsonChunk . substring ( 0 , 200 ) + ( jsonChunk . length > 200 ? "..." : "" ) )
177+
178+ try {
179+ // Errors during write will be caught by the parser's onError or thrown.
180+ this . log ( "Calling parser.write()" )
181+ this . parser . write ( jsonChunk )
182+ this . log ( "parser.write() completed successfully" )
183+ } catch ( error ) {
184+ this . log ( "Exception during parser.write(): " + error , "ERROR" )
185+ throw error
186+ }
94187 }
95188
96189 public getCurrentContent ( ) : string {
190+ this . log ( "getCurrentContent() called" )
191+ this . log ( "Current content length: " + this . currentFileContent . length )
97192 return this . currentFileContent
98193 }
99194
100195 public getSuccessfullyParsedItems ( ) : ReplacementItem [ ] {
196+ this . log ( "getSuccessfullyParsedItems() called" )
197+ this . log ( "Returning copy of " + this . successfullyParsedItems . length + " items" )
101198 return [ ...this . successfullyParsedItems ] // Return a copy
102199 }
103200
104201 private calculateChangeLocation ( oldStr : string , newStr : string ) : ChangeLocation {
202+ this . log ( "calculateChangeLocation() called" )
203+ this . log ( "oldStr length: " + oldStr . length )
204+ this . log ( "newStr length: " + newStr . length )
205+ this . log ( "oldStr preview: " + oldStr . substring ( 0 , 50 ) + ( oldStr . length > 50 ? "..." : "" ) )
206+ this . log ( "newStr preview: " + newStr . substring ( 0 , 50 ) + ( newStr . length > 50 ? "..." : "" ) )
207+
105208 // Find the index where the old string starts
106209 const startIndex = this . currentFileContent . indexOf ( oldStr )
210+ this . log ( "startIndex found: " + startIndex )
211+
107212 if ( startIndex === - 1 ) {
213+ this . log ( "startIndex is -1 - old string not found in content!" , "WARN" )
214+ this . log ( "This shouldn't happen since we already checked includes()" , "WARN" )
108215 // This shouldn't happen since we already checked includes(), but just in case
109216 return { startLine : 0 , endLine : 0 , startChar : 0 , endChar : 0 }
110217 }
111218
112219 // Calculate line numbers by counting newlines before the start index
113220 const contentBeforeStart = this . currentFileContent . substring ( 0 , startIndex )
221+ this . log ( "contentBeforeStart length: " + contentBeforeStart . length )
222+
114223 const startLine = ( contentBeforeStart . match ( / \n / g) || [ ] ) . length
224+ this . log ( "calculated startLine: " + startLine )
115225
116226 // Calculate the end index after replacement
117227 const endIndex = startIndex + oldStr . length
228+ this . log ( "calculated endIndex: " + endIndex )
229+
118230 const contentBeforeEnd = this . currentFileContent . substring ( 0 , endIndex )
231+ this . log ( "contentBeforeEnd length: " + contentBeforeEnd . length )
232+
119233 const endLine = ( contentBeforeEnd . match ( / \n / g) || [ ] ) . length
234+ this . log ( "calculated endLine: " + endLine )
120235
121236 // Calculate character positions within their respective lines
122237 const lastNewlineBeforeStart = contentBeforeStart . lastIndexOf ( "\n" )
238+ this . log ( "lastNewlineBeforeStart: " + lastNewlineBeforeStart )
239+
123240 const startChar = lastNewlineBeforeStart === - 1 ? startIndex : startIndex - lastNewlineBeforeStart - 1
241+ this . log ( "calculated startChar: " + startChar )
124242
125243 const lastNewlineBeforeEnd = contentBeforeEnd . lastIndexOf ( "\n" )
244+ this . log ( "lastNewlineBeforeEnd: " + lastNewlineBeforeEnd )
245+
126246 const endChar = lastNewlineBeforeEnd === - 1 ? endIndex : endIndex - lastNewlineBeforeEnd - 1
247+ this . log ( "calculated endChar: " + endChar )
127248
128- return {
249+ const result = {
129250 startLine,
130251 endLine,
131252 startChar,
132253 endChar,
133254 }
255+
256+ this . log ( "calculateChangeLocation() returning: " + JSON . stringify ( result ) )
257+ return result
258+ }
259+
260+ private log ( message : string , level : "INFO" | "WARN" | "ERROR" = "INFO" ) : void {
261+ const timestamp = new Date ( ) . toISOString ( )
262+ const logLine = `[${ timestamp } ] [${ level } ] ${ message } \n`
263+
264+ try {
265+ fs . appendFileSync ( this . logFilePath , logLine )
266+ } catch ( error ) {
267+ // Fallback to console if file logging fails
268+ console . error ( "Failed to write to log file:" , error )
269+ console . log ( `[${ level } ] ${ message } ` )
270+ }
134271 }
135272}
0 commit comments