11import chalk from 'chalk' ;
22import { ResourceConfig } from '../entities/resource-config.js' ;
33import * as Diff from 'diff'
4+ import * as jsonSourceMap from 'json-source-map' ;
5+
46import { FileType , InMemoryFile } from '../parser/entities.js' ;
5- import { SourceLocation , SourceMap , SourceMapCache } from '../parser/source-maps.js' ;
7+ import { SourceMap , SourceMapCache } from '../parser/source-maps.js' ;
68import detectIndent from 'detect-indent' ;
79import { Project } from '../entities/project.js' ;
810import { ProjectConfig } from '../entities/project-config.js' ;
@@ -27,13 +29,17 @@ export class FileModificationCalculator {
2729 private existingConfigs : ResourceConfig [ ] ;
2830 private sourceMap : SourceMap ;
2931 private totalConfigLength : number ;
32+ private indentString : string ;
3033
3134 constructor ( existing : Project ) {
3235 const { file, sourceMap } = existing . sourceMaps ?. getSourceMap ( existing . codifyFiles [ 0 ] ) ! ;
3336 this . existingFile = file ;
3437 this . sourceMap = sourceMap ;
3538 this . existingConfigs = [ ...existing . resourceConfigs ] ;
3639 this . totalConfigLength = existing . resourceConfigs . length + ( existing . projectConfig ? 1 : 0 ) ;
40+
41+ const fileIndents = detectIndent ( this . existingFile . contents ) ;
42+ this . indentString = fileIndents . indent ;
3743 }
3844
3945 async calculate ( modifications : ModifiedResource [ ] ) : Promise < FileModificationResult > {
@@ -54,13 +60,8 @@ export class FileModificationCalculator {
5460
5561 this . validate ( modifications ) ;
5662
57- const fileIndents = detectIndent ( this . existingFile . contents ) ;
58- const indentString = fileIndents . indent ;
59-
6063 let newFile = this . existingFile . contents . trimEnd ( ) ;
6164
62- console . log ( JSON . stringify ( this . sourceMap , null , 2 ) )
63-
6465 // Reverse the traversal order so we edit from the back. This way the line numbers won't be messed up with new edits.
6566 for ( const existing of this . existingConfigs . reverse ( ) ) {
6667 const duplicateIndex = modifications . findIndex ( ( modified ) => existing . isSameOnSystem ( modified . resource ) )
@@ -75,23 +76,14 @@ export class FileModificationCalculator {
7576 const sourceIndex = Number . parseInt ( duplicateSourceKey . split ( '/' ) . at ( 1 ) ! )
7677
7778 if ( modified . modification === ModificationType . DELETE ) {
78- const isLast = sourceIndex === this . totalConfigLength - 1 ;
79- const isFirst = sourceIndex === 0 ;
80-
81- // We try to start deleting from the previous element to the next element if possible. This covers any spaces as well.
82- const value = ! isFirst ? this . sourceMap . lookup ( `/${ sourceIndex - 1 } ` ) ?. valueEnd : this . sourceMap . lookup ( duplicateSourceKey ) ?. value ;
83- const valueEnd = ! isLast ? this . sourceMap . lookup ( `/${ sourceIndex + 1 } ` ) ?. value : this . sourceMap . lookup ( duplicateSourceKey ) ?. valueEnd ;
79+ newFile = this . remove ( newFile , this . sourceMap , sourceIndex ) ;
80+ this . totalConfigLength -= 1 ;
8481
85- newFile = this . remove ( newFile , value ! , valueEnd ! , isFirst , isLast ) ;
8682 continue ;
8783 }
8884
89- if ( modified . modification === ModificationType . INSERT_OR_UPDATE ) {
90- const config = JSON . stringify ( modified . resource . raw , null , indentString )
91- newFile = this . insertConfig ( newFile , config , indentString ) ;
92- }
93-
94- resultResources . splice ( duplicateIndex , 1 , modified . resource ) ;
85+ newFile = this . remove ( newFile , this . sourceMap , sourceIndex ) ;
86+ newFile = this . update ( newFile , modified . resource , this . sourceMap , sourceIndex ) ;
9587 }
9688
9789 return {
@@ -111,9 +103,9 @@ export class FileModificationCalculator {
111103 }
112104
113105 if ( this . existingConfigs . some ( ( r ) => ! r . resourceInfo ) ) {
114- const badResources = this . existingConfigs
115- . filter ( ( r ) => this . isResourceConfig ( r ) )
116- . map ( ( r ) => r . id )
106+ const badResources = this . existingConfigs
107+ . filter ( ( r ) => this . isResourceConfig ( r ) )
108+ . map ( ( r ) => r . id )
117109
118110 throw new Error ( `All resources must have resource info attached to generate diff. Found bad resources: ${ badResources } ` ) ;
119111 }
@@ -160,24 +152,74 @@ export class FileModificationCalculator {
160152
161153 private remove (
162154 file : string ,
163- value : SourceLocation ,
164- valueEnd : SourceLocation ,
165- isFirst : boolean ,
166- isLast : boolean ,
155+ sourceMap : SourceMap ,
156+ sourceIndex : number ,
167157 ) : string {
158+ const isLast = sourceIndex === this . totalConfigLength - 1 ;
159+ const isFirst = sourceIndex === 0 ;
160+
161+ // We try to start deleting from the previous element to the next element if possible. This covers any spaces as well.
162+ const value = ! isFirst ? this . sourceMap . lookup ( `/${ sourceIndex - 1 } ` ) ?. valueEnd : this . sourceMap . lookup ( `/${ sourceIndex } ` ) ?. value ;
163+ const valueEnd = ! isLast ? this . sourceMap . lookup ( `/${ sourceIndex + 1 } ` ) ?. value : this . sourceMap . lookup ( `/${ sourceIndex } ` ) ?. valueEnd ;
164+
168165 // Start one later so we leave the previous trailing comma alone
169- const start = isFirst || isLast ? value . position : value . position + 1 ;
166+ const start = isFirst || isLast ? value ! . position : value ! . position + 1 ;
170167
171- let result = this . r ( file , start , valueEnd . position )
168+ let result = this . r ( file , start , valueEnd ! . position )
172169
173170 // If there's no gap between the remaining elements, we add a space.
174171 if ( ! isFirst && ! / \s / . test ( result [ start ] ) ) {
175- result = this . splice ( result , start , 0 , ' ' ) ;
172+ result = this . splice ( result , start , 0 , `\n ${ this . indentString } ` ) ;
176173 }
177174
178175 return result ;
179176 }
180177
178+ /** Updates an existing resource config JSON with new values, this method replaces the old object but tries be either 1 line or multi-line like the original */
179+ private update (
180+ file : string ,
181+ resource : ResourceConfig ,
182+ sourceMap : SourceMap ,
183+ sourceIndex : number ,
184+ ) : string {
185+ // Updates: for now let's remove and re-add the entire object. Only two formatting availalbe either same line or multi-line
186+ const { value, valueEnd } = this . sourceMap . lookup ( `/${ sourceIndex } ` ) ! ;
187+ const isSameLine = value . line === valueEnd . line ;
188+
189+ const isLast = sourceIndex === this . totalConfigLength - 1 ;
190+ const isFirst = sourceIndex === 0 ;
191+
192+ // We try to start deleting from the previous element to the next element if possible. This covers any spaces as well.
193+ const start = ! isFirst ? this . sourceMap . lookup ( `/${ sourceIndex - 1 } ` ) ?. valueEnd : this . sourceMap . lookup ( `/${ sourceIndex } ` ) ?. value ;
194+
195+ let content = isSameLine ? JSON . stringify ( resource . raw ) : JSON . stringify ( resource . raw , null , this . indentString ) ;
196+ content = this . updateParamsToOnelineIfNeeded ( content , sourceMap , sourceIndex ) ;
197+
198+ content = content . split ( / \n / ) . map ( ( l ) => `${ this . indentString } ${ l } ` ) . join ( '\n' ) ;
199+ content = isFirst ? `\n${ content } ,` : `,\n${ content } `
200+
201+ return this . splice ( file , start ?. position ! , 0 , content ) ;
202+ }
203+
204+ /** Attempt to make arrays and objects oneliners if they were before. It does this by creating a new source map */
205+ private updateParamsToOnelineIfNeeded ( content : string , sourceMap : SourceMap , sourceIndex : number ) : string {
206+ // Attempt to make arrays and objects oneliners if they were before. It does this by creating a new source map
207+ const parsedContent = JSON . parse ( content ) ;
208+ const parsedPointers = jsonSourceMap . parse ( content ) ;
209+ const parsedSourceMap = new SourceMapCache ( )
210+ parsedSourceMap . addSourceMap ( { filePath : '' , fileType : FileType . JSON , contents : parsedContent } , parsedPointers ) ;
211+
212+ for ( const [ key , value ] of Object . entries ( parsedContent ) ) {
213+ const source = sourceMap . lookup ( `/${ sourceIndex } /${ key } ` ) ;
214+ if ( ( Array . isArray ( value ) || typeof value === 'object' ) && source && source . value . line === source . valueEnd . line ) {
215+ const { value, valueEnd } = parsedSourceMap . lookup ( `#/${ key } ` ) !
216+ content = this . splice ( content , value . position , valueEnd . position - value . position , JSON . stringify ( parsedContent [ key ] ) )
217+ }
218+ }
219+
220+ return content ;
221+ }
222+
181223 private splice ( s : string , start : number , deleteCount = 0 , insert = '' ) {
182224 return s . substring ( 0 , start ) + insert + s . substring ( start + deleteCount ) ;
183225 }
0 commit comments