@@ -191,6 +191,93 @@ export class Diff {
191
191
}
192
192
}
193
193
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
+
194
281
toString (
195
282
options : {
196
283
// Number of lines of context to print before and after each diff segment
@@ -203,7 +290,7 @@ export class Diff {
203
290
let state : "init" | "diff" = "init" ;
204
291
const context : string [ ] = [ ] ;
205
292
206
- for ( const result of this . #results ) {
293
+ for ( const result of this . #resultsForPrint ) {
207
294
if ( result . value === undefined ) {
208
295
continue ;
209
296
}
0 commit comments