@@ -191,6 +191,93 @@ export class Diff {
191191 }
192192 }
193193
194+ /**
195+ * Results but refined to be printed/stringified.
196+ *
197+ * In particular the results returned here are ordered to try to avoid cases
198+ * in which an addition/removal is incorrectly split.
199+ *
200+ * For example, the standard results can produce something like:
201+ * ```
202+ * ...
203+ * - "vars": {
204+ * + "vars": {},
205+ * - "MY_VAR": "variable set in the dash"
206+ * - },
207+ * ...
208+ * ```
209+ * (notice how the first removal is separated from the last two,
210+ * making the diff much less readable).
211+ * Such change in the refined results will instead look like:
212+ * ```
213+ * ...
214+ * + "vars": {},
215+ * - "vars": {
216+ * - "MY_VAR": "variable set in the dash"
217+ * - },
218+ * ...
219+ * ```
220+ */
221+ get #resultsForPrint( ) {
222+ const results = [
223+ ...this . #results. filter ( ( r ) => ! ! r . value && r . value !== "\n" ) ,
224+ ] ;
225+
226+ const swapLines = ( i : number , j : number ) => {
227+ const tmp = results [ i ] ;
228+ results [ i ] = results [ j ] ;
229+ results [ j ] = tmp ;
230+ } ;
231+
232+ const numOfLines = ( str : string ) => str . split ( "\n" ) . length ;
233+
234+ const isLoneResult = ( index : number , target : - 1 | 1 ) : boolean => {
235+ const currentIdx = index ;
236+ const adjacentIdx = currentIdx + target ;
237+ const nextIdx = currentIdx + target + target ;
238+
239+ // If any of the results we need to analize is not present we return false
240+ if ( ! results [ adjacentIdx ] || ! results [ nextIdx ] || ! results [ nextIdx ] ) {
241+ return false ;
242+ }
243+
244+ const previousIdx = index - target ;
245+
246+ const isAlternation = ( type : "added" | "removed" ) =>
247+ results [ currentIdx ] [ type ] === true &&
248+ results [ previousIdx ] ?. [ type ] !== results [ currentIdx ] [ type ] &&
249+ results [ adjacentIdx ] [ type === "added" ? "removed" : "added" ] === true &&
250+ results [ nextIdx ] [ type ] === true ;
251+
252+ // If there isn't an alternation between added and removed results then we return false
253+ if ( ! isAlternation ( "added" ) && ! isAlternation ( "removed" ) ) {
254+ return false ;
255+ }
256+
257+ // We might have found a lone result but to make sure we need to check that the next index
258+ // contains multiple lines while the current and adjacent ones both only contain one
259+ return (
260+ numOfLines ( results [ currentIdx ] . value ?? "" ) === 1 &&
261+ numOfLines ( results [ adjacentIdx ] . value ?? "" ) === 1 &&
262+ numOfLines ( results [ nextIdx ] . value ?? "" ) > 1
263+ ) ;
264+ } ;
265+
266+ for ( let i = 0 ; i < results . length ; i ++ ) {
267+ if ( isLoneResult ( i , + 1 ) ) {
268+ swapLines ( i , i + 1 ) ;
269+ continue ;
270+ }
271+
272+ if ( isLoneResult ( i , - 1 ) ) {
273+ swapLines ( i , i - 1 ) ;
274+ continue ;
275+ }
276+ }
277+
278+ return results ;
279+ }
280+
194281 toString (
195282 options : {
196283 // Number of lines of context to print before and after each diff segment
@@ -203,7 +290,7 @@ export class Diff {
203290 let state : "init" | "diff" = "init" ;
204291 const context : string [ ] = [ ] ;
205292
206- for ( const result of this . #results ) {
293+ for ( const result of this . #resultsForPrint ) {
207294 if ( result . value === undefined ) {
208295 continue ;
209296 }
0 commit comments