@@ -5,6 +5,7 @@ var fs = require('fs');
5
5
var color = require ( 'color-parser' ) ;
6
6
var diff = require ( 'diff' ) ;
7
7
var _ = require ( 'lodash' ) ;
8
+ var util = require ( 'util' ) ;
8
9
9
10
function parseColors ( value ) {
10
11
// Throw everything at colorString, and see what sticks.
@@ -141,15 +142,173 @@ var extensions = {
141
142
removeprefixes : require ( './lib/extensions/removeprefixes' )
142
143
} ;
143
144
144
- function createDiff ( fileName , oldStr , newStr , options ) {
145
- return diff . createPatch ( fileName , oldStr , newStr ) . split ( "\n" ) . filter ( function ( line ) {
146
- if ( options . context ) {
147
- return true ;
145
+ function filterContext ( lines ) {
146
+ return lines . filter ( function ( line ) {
147
+ const trimmed = line . trimRight ( ) ;
148
+ return trimmed !== "+" &&
149
+ trimmed !== "-" &&
150
+ ( trimmed . indexOf ( '-' ) === 0 || trimmed . indexOf ( '+' ) === 0 ) ;
151
+ } ) ;
152
+ }
153
+
154
+ function filterSimilarColors ( patchLines ) {
155
+ function hasPlusLinesDirectlyAhead ( index ) {
156
+ for ( var i = index ; i < patchLines . length ; i ++ ) {
157
+ if ( patchLines [ i ] . startsWith ( "-" ) ) {
158
+ continue ;
159
+ }
160
+
161
+ return patchLines [ i ] . startsWith ( "+" ) && ! patchLines [ i ] . startsWith ( "+++" ) ;
148
162
}
149
163
150
- const trimmed = line . trimRight ( ) ;
151
- return trimmed !== "+" && trimmed !== "-" && ( trimmed . indexOf ( '-' ) === 0 || trimmed . indexOf ( '+' ) === 0 )
152
- } ) . join ( "\n" ) ;
164
+ return false ;
165
+ }
166
+
167
+ // Batch diffs into chunks while maintaining line numbers.
168
+ // [
169
+ // {
170
+ // 'old': [
171
+ // {line: 34, text: '- opacity: 1.0'}
172
+ // {line: 35, text: '- color: rgba(0, 0, 0, 0)'}
173
+ // ],
174
+ // 'new': [
175
+ // {line: 36, text: '+ opacity: 0.9'}
176
+ // {line: 37, text: '+ color: rgba(1, 1, 1, 0)'}
177
+ // ],
178
+ // },
179
+ // ...
180
+ // ]
181
+ let oldLines = [ ] ;
182
+ let newLines = [ ] ;
183
+ let chunks = [ ] ;
184
+ patchLines . forEach ( ( line , index ) => {
185
+ // Only accumulate old lines if we peek ahead and see new lines coming.
186
+ // We don't want to accumulate single removals.
187
+ if ( line . startsWith ( "-" ) && ! line . startsWith ( "---" ) && hasPlusLinesDirectlyAhead ( index ) ) {
188
+ oldLines . push ( { line : index , text : line } ) ;
189
+ return ;
190
+ }
191
+
192
+ // Only accumulate new lines if we found old lines. We don't want to
193
+ // accumulate single additions.
194
+ if ( line . startsWith ( "+" ) && ! line . startsWith ( "+++" ) && oldLines . length > 0 ) {
195
+ newLines . push ( { line : index , text : line } ) ;
196
+ }
197
+
198
+ // Check if we've completed a chunk
199
+ if ( oldLines . length > 0 && newLines . length > 0 &&
200
+ ( index === patchLines . length - 1 || ! patchLines [ index + 1 ] . startsWith ( "+" ) ) ) {
201
+ // We completed a chunk. Accumulate it.
202
+ chunks . push (
203
+ {
204
+ 'old' : oldLines ,
205
+ 'new' : newLines
206
+ }
207
+ ) ;
208
+ // Reset our state
209
+ oldLines = [ ] ;
210
+ newLines = [ ] ;
211
+ }
212
+ } ) ;
213
+
214
+ // Filter our lines that don't include rgba. Whitelist what property values
215
+ // can be.
216
+ // [
217
+ // {
218
+ // 'old': [
219
+ // {line: 35, text: '- color: rgba(0, 0, 0, 0)'}
220
+ // ],
221
+ // 'new': [
222
+ // {line: 37, text: '+ color: rgba(1, 1, 1, 0)'}
223
+ // ],
224
+ // },
225
+ // ...
226
+ // ]
227
+ const ALLOWED_PROPERTIES = [
228
+ "color" ,
229
+ "background-color" ,
230
+ ] ;
231
+ const lineFilter = ( line ) => {
232
+ const rgbaCount = ( line . text . match ( / r g b a \( / g) || [ ] ) . length ;
233
+ const match = line . text . match ( / [ + - ] [ ] + ( [ ^ : ] + ) : / ) ;
234
+
235
+ if ( rgbaCount > 1 ) {
236
+ throw new Error ( "More than one rgba per line. Investigate!" ) ;
237
+ }
238
+
239
+ return rgbaCount === 1 &&
240
+ ( match && ALLOWED_PROPERTIES . includes ( match [ 1 ] ) ) ;
241
+ }
242
+
243
+ chunks . forEach ( ( chunk ) => {
244
+ chunk . old = chunk . old . filter ( lineFilter ) ;
245
+ chunk . new = chunk . new . filter ( lineFilter ) ;
246
+ } ) ;
247
+
248
+ chunks = chunks . filter ( ( chunk ) => {
249
+ return chunk . old . length > 0 && chunk . new . length > 0 ;
250
+ } ) ;
251
+
252
+ // Compare old and new by index. If the colors are the same within a tolerance
253
+ // keep track of what lines to remove
254
+ // [35, 37]
255
+ const linesToRemove = [ ] ;
256
+
257
+ chunks . forEach ( ( chunk ) => {
258
+ for ( var i = 0 ; i < chunk . old . length && i < chunk . new . length ; i ++ ) {
259
+ const oldLine = chunk . old [ i ] . text ;
260
+ const newLine = chunk . new [ i ] . text ;
261
+
262
+ const oldRgbRegex = / r g b a \( ( [ \d ] + ) , ( [ \d ] + ) , ( [ \d ] + ) , / g;
263
+ const oldMatch = oldRgbRegex . exec ( oldLine ) ;
264
+ const newRgbRegex = / r g b a \( ( [ \d ] + ) , ( [ \d ] + ) , ( [ \d ] + ) , / g;
265
+ const newMatch = newRgbRegex . exec ( newLine ) ;
266
+
267
+ if ( ! oldMatch || ! newMatch ) {
268
+ throw new Error ( "Expected rgba to exist on these lines!" ) ;
269
+ }
270
+
271
+ const oldR = parseInt ( oldMatch [ 1 ] , 10 ) ;
272
+ const oldG = parseInt ( oldMatch [ 2 ] , 10 ) ;
273
+ const oldB = parseInt ( oldMatch [ 3 ] , 10 ) ;
274
+
275
+ const newR = parseInt ( newMatch [ 1 ] , 10 ) ;
276
+ const newG = parseInt ( newMatch [ 2 ] , 10 ) ;
277
+ const newB = parseInt ( newMatch [ 3 ] , 10 ) ;
278
+
279
+ if ( Math . abs ( oldR - newR ) <= 1 &&
280
+ Math . abs ( oldG - newG ) <= 1 &&
281
+ Math . abs ( oldB - newB ) <= 1 ) {
282
+ linesToRemove . push ( chunk . old [ i ] . line ) ;
283
+ linesToRemove . push ( chunk . new [ i ] . line ) ;
284
+ }
285
+ }
286
+ } ) ;
287
+
288
+ // Sort the line numbers and remove in reverse
289
+ linesToRemove . sort ( ( a , b ) => a - b ) ;
290
+
291
+ for ( var i = linesToRemove . length - 1 ; i >= 0 ; i -- ) {
292
+ patchLines . splice ( linesToRemove [ i ] , 1 ) ;
293
+ }
294
+
295
+ return patchLines ;
296
+ }
297
+
298
+ function createDiff ( fileName , oldStr , newStr , options ) {
299
+ let patchLines = diff . createPatch ( fileName , oldStr , newStr ) . split ( "\n" ) ;
300
+
301
+ // This should be done before context removal because it helps us
302
+ // differentiate between chunks of differences
303
+ if ( options [ "similar-colors" ] ) {
304
+ patchLines = filterSimilarColors ( patchLines ) ;
305
+ }
306
+
307
+ if ( ! options . context ) {
308
+ patchLines = filterContext ( patchLines ) ;
309
+ }
310
+
311
+ return patchLines . join ( "\n" ) ;
153
312
}
154
313
155
314
module . exports = function ( test , control , options ) {
0 commit comments