-
-
Notifications
You must be signed in to change notification settings - Fork 277
/
compute-lines.ts
271 lines (253 loc) · 7.28 KB
/
compute-lines.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
import * as diff from 'diff';
const jsDiff: { [key: string]: any } = diff;
export enum DiffType {
DEFAULT = 0,
ADDED = 1,
REMOVED = 2,
}
// See https://github.com/kpdecker/jsdiff/tree/v4.0.1#api for more info on the below JsDiff methods
export enum DiffMethod {
CHARS = 'diffChars',
WORDS = 'diffWords',
WORDS_WITH_SPACE = 'diffWordsWithSpace',
LINES = 'diffLines',
TRIMMED_LINES = 'diffTrimmedLines',
SENTENCES = 'diffSentences',
CSS = 'diffCss',
}
export interface DiffInformation {
value?: string | DiffInformation[];
lineNumber?: number;
type?: DiffType;
}
export interface LineInformation {
left?: DiffInformation;
right?: DiffInformation;
}
export interface ComputedLineInformation {
lineInformation: LineInformation[];
diffLines: number[];
}
export interface ComputedDiffInformation {
left?: DiffInformation[];
right?: DiffInformation[];
}
// See https://github.com/kpdecker/jsdiff/tree/v4.0.1#change-objects for more info on JsDiff
// Change Objects
export interface JsDiffChangeObject {
added?: boolean;
removed?: boolean;
value?: string;
}
/**
* Splits diff text by new line and computes final list of diff lines based on
* conditions.
*
* @param value Diff text from the js diff module.
*/
const constructLines = (value: string): string[] => {
const lines = value.split('\n');
const isAllEmpty = lines.every((val): boolean => !val);
if (isAllEmpty) {
// This is to avoid added an extra new line in the UI.
if (lines.length === 2) {
return [];
}
lines.pop();
return lines;
}
const lastLine = lines[lines.length - 1];
const firstLine = lines[0];
// Remove the first and last element if they are new line character. This is
// to avoid addition of extra new line in the UI.
if (!lastLine) {
lines.pop();
}
if (!firstLine) {
lines.shift();
}
return lines;
};
/**
* Computes word diff information in the line.
* [TODO]: Consider adding options argument for JsDiff text block comparison
*
* @param oldValue Old word in the line.
* @param newValue New word in the line.
* @param compareMethod JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api
*/
const computeDiff = (
oldValue: string,
newValue: string,
compareMethod: string = DiffMethod.CHARS,
): ComputedDiffInformation => {
const diffArray: JsDiffChangeObject[] = jsDiff[compareMethod](
oldValue,
newValue,
);
const computedDiff: ComputedDiffInformation = {
left: [],
right: [],
};
diffArray.forEach(
({ added, removed, value }): DiffInformation => {
const diffInformation: DiffInformation = {};
if (added) {
diffInformation.type = DiffType.ADDED;
diffInformation.value = value;
computedDiff.right.push(diffInformation);
}
if (removed) {
diffInformation.type = DiffType.REMOVED;
diffInformation.value = value;
computedDiff.left.push(diffInformation);
}
if (!removed && !added) {
diffInformation.type = DiffType.DEFAULT;
diffInformation.value = value;
computedDiff.right.push(diffInformation);
computedDiff.left.push(diffInformation);
}
return diffInformation;
},
);
return computedDiff;
};
/**
* [TODO]: Think about moving common left and right value assignment to a
* common place. Better readability?
*
* Computes line wise information based in the js diff information passed. Each
* line contains information about left and right section. Left side denotes
* deletion and right side denotes addition.
*
* @param oldString Old string to compare.
* @param newString New string to compare with old string.
* @param disableWordDiff Flag to enable/disable word diff.
* @param compareMethod JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api
* @param linesOffset line number to start counting from
*/
const computeLineInformation = (
oldString: string,
newString: string,
disableWordDiff: boolean = false,
compareMethod: string = DiffMethod.CHARS,
linesOffset: number = 0,
): ComputedLineInformation => {
const diffArray = diff.diffLines(
oldString.trimRight(),
newString.trimRight(),
{
newlineIsToken: true,
ignoreWhitespace: false,
ignoreCase: false,
},
);
let rightLineNumber = linesOffset;
let leftLineNumber = linesOffset;
let lineInformation: LineInformation[] = [];
let counter = 0;
const diffLines: number[] = [];
const ignoreDiffIndexes: string[] = [];
const getLineInformation = (
value: string,
diffIndex: number,
added?: boolean,
removed?: boolean,
evaluateOnlyFirstLine?: boolean,
): LineInformation[] => {
const lines = constructLines(value);
return lines
.map(
(line: string, lineIndex): LineInformation => {
const left: DiffInformation = {};
const right: DiffInformation = {};
if (
ignoreDiffIndexes.includes(`${diffIndex}-${lineIndex}`) ||
(evaluateOnlyFirstLine && lineIndex !== 0)
) {
return undefined;
}
if (added || removed) {
if (!diffLines.includes(counter)) {
diffLines.push(counter);
}
if (removed) {
leftLineNumber += 1;
left.lineNumber = leftLineNumber;
left.type = DiffType.REMOVED;
left.value = line || ' ';
// When the current line is of type REMOVED, check the next item in
// the diff array whether it is of type ADDED. If true, the current
// diff will be marked as both REMOVED and ADDED. Meaning, the
// current line is a modification.
const nextDiff = diffArray[diffIndex + 1];
if (nextDiff && nextDiff.added) {
const nextDiffLines = constructLines(nextDiff.value)[lineIndex];
if (nextDiffLines) {
const {
value: rightValue,
lineNumber,
type,
} = getLineInformation(
nextDiff.value,
diffIndex,
true,
false,
true,
)[0].right;
// When identified as modification, push the next diff to ignore
// list as the next value will be added in this line computation as
// right and left values.
ignoreDiffIndexes.push(`${diffIndex + 1}-${lineIndex}`);
right.lineNumber = lineNumber;
right.type = type;
// Do word level diff and assign the corresponding values to the
// left and right diff information object.
if (disableWordDiff) {
right.value = rightValue;
} else {
const computedDiff = computeDiff(
line,
rightValue as string,
compareMethod,
);
right.value = computedDiff.right;
left.value = computedDiff.left;
}
}
}
} else {
rightLineNumber += 1;
right.lineNumber = rightLineNumber;
right.type = DiffType.ADDED;
right.value = line;
}
} else {
leftLineNumber += 1;
rightLineNumber += 1;
left.lineNumber = leftLineNumber;
left.type = DiffType.DEFAULT;
left.value = line;
right.lineNumber = rightLineNumber;
right.type = DiffType.DEFAULT;
right.value = line;
}
counter += 1;
return { right, left };
},
)
.filter(Boolean);
};
diffArray.forEach(({ added, removed, value }: diff.Change, index): void => {
lineInformation = [
...lineInformation,
...getLineInformation(value, index, added, removed),
];
});
return {
lineInformation,
diffLines,
};
};
export { computeLineInformation };