@@ -12,7 +12,7 @@ const tsfmt = require('typescript-formatter');
12
12
const tslint = require ( 'tslint' ) ;
13
13
const relative = require ( 'relative' ) ;
14
14
const ts = require ( 'gulp-typescript' ) ;
15
- const watch = require ( 'gulp-watch' ) ;
15
+ const watch = require ( 'gulp-debounced- watch' ) ;
16
16
const cp = require ( 'child_process' ) ;
17
17
const colors = require ( 'colors/safe' ) ;
18
18
@@ -42,7 +42,7 @@ const eolFilter = [
42
42
'!.vscodeignore' ,
43
43
'!LICENSE' ,
44
44
'!**/node_modules/**' ,
45
- '!**/*.{svg,exe,png,bmp,scpt,bat,cmd,cur,ttf,woff,eot,txt,md,json,yml}' ,
45
+ '!**/*.{svg,exe,png,bmp,scpt,bat,cmd,cur,ttf,woff,eot,txt,md,json,yml,pyc }' ,
46
46
'!out/**/*' ,
47
47
'!images/**/*' ,
48
48
'!.vscode/**/*' ,
@@ -84,7 +84,21 @@ function reportFailures(failures) {
84
84
} ) ;
85
85
}
86
86
87
- const hygiene = exports . hygiene = ( some , options ) => {
87
+
88
+ /**
89
+ * @typedef {Object } hygieneOptions - creates a new type named 'SpecialType'
90
+ * @property {boolean= } skipEOL - skipEOL check.
91
+ * @property {boolean= } skipIndentationCheck - Skip indentation checks.
92
+ * @property {boolean= } skipFormatCheck - Skip format checks.
93
+ */
94
+
95
+ /**
96
+ *
97
+ * @param {string[] } some
98
+ * @param {hygieneOptions } options
99
+ * @returns {NodeJS.EventEmitter }
100
+ */
101
+ const hygiene = ( some , options ) => {
88
102
options = options || { } ;
89
103
let errorCount = 0 ;
90
104
const eol = es . through ( function ( file ) {
@@ -185,19 +199,29 @@ const hygiene = exports.hygiene = (some, options) => {
185
199
return tsProject ( reporter ) ;
186
200
}
187
201
188
- const result = gulp . src ( some || all , {
202
+ // Misc file checks.
203
+ let result = gulp . src ( some || all , {
189
204
base : '.'
190
205
} )
191
206
. pipe ( filter ( f => ! f . stat . isDirectory ( ) ) )
192
207
. pipe ( filter ( eolFilter ) )
193
208
. pipe ( options . skipEOL ? es . through ( ) : eol )
194
- . pipe ( filter ( indentationFilter ) )
195
- . pipe ( indentation ) ;
209
+ . pipe ( filter ( indentationFilter ) ) ;
210
+
211
+ if ( ! options . skipIndentationCheck ) {
212
+ result = result
213
+ . pipe ( indentation ) ;
214
+ }
215
+
216
+ // Type script checks.
217
+ let typescript = result
218
+ . pipe ( filter ( tslintFilter ) ) ;
196
219
197
- const typescript = result
198
- . pipe ( filter ( tslintFilter ) )
199
- . pipe ( formatting )
200
- . pipe ( tsl )
220
+ if ( ! options . skipFormatCheck ) {
221
+ typescript = typescript
222
+ . pipe ( formatting ) ;
223
+ }
224
+ typescript = typescript . pipe ( tsl )
201
225
. pipe ( tscFilesTracker )
202
226
. pipe ( tsc ( ) ) ;
203
227
@@ -211,68 +235,142 @@ const hygiene = exports.hygiene = (some, options) => {
211
235
} ) ) ;
212
236
} ;
213
237
214
- gulp . task ( 'hygiene' , ( ) => hygiene ( ) ) ;
238
+ exports . hygiene = hygiene ;
239
+
240
+ gulp . task ( 'hygiene' , ( ) => run ( { mode : 'all' , skipFormatCheck : true , skipIndentationCheck : true } ) ) ;
241
+
242
+ gulp . task ( 'hygiene-staged' , ( ) => run ( { mode : 'changes' } ) ) ;
243
+
244
+ gulp . task ( 'hygiene-watch' , [ 'hygiene-staged' , 'hygiene-watch-runner' ] ) ;
215
245
216
- gulp . task ( 'hygiene-watch' , function ( ) {
217
- return watch ( all , function ( ) {
218
- console . clear ( ) ;
219
- console . log ( 'Checking hygiene...' ) ;
220
- run ( true , true ) ;
246
+ gulp . task ( 'hygiene-watch-runner' , function ( ) {
247
+ return watch ( all , { events : [ 'add' , 'change' ] } , function ( event ) {
248
+ const start = new Date ( ) ;
249
+ console . log ( `[${ start . toLocaleTimeString ( ) } ] Starting '${ colors . cyan ( 'hygiene-watch-runner' ) } '...` ) ;
250
+ // Skip indentation and formatting checks to speed up linting.
251
+ return run ( { mode : 'watch' , skipFormatCheck : true , skipIndentationCheck : true } )
252
+ . then ( ( ) => {
253
+ const end = new Date ( ) ;
254
+ const time = ( end . getTime ( ) - start . getTime ( ) ) / 1000 ;
255
+ console . log ( `[${ end . toLocaleTimeString ( ) } ] Finished '${ colors . cyan ( 'hygiene-watch-runner' ) } ' after ${ time } seconds` ) ;
256
+ } ) ;
221
257
} ) ;
222
258
} ) ;
223
259
224
- function run ( lintOnlyModifiedFiles , doNotExit ) {
225
- function exitProcessOnError ( ex ) {
226
- console . error ( ) ;
260
+ /**
261
+ * @typedef {Object } runOptions
262
+ * @property {boolean= } exitOnError - Exit on error.
263
+ * @property {'watch'|'changes'|'staged'|'all' } [mode=] - Mode.
264
+ * @property {string[]= } files - Optional list of files to be modified.
265
+ * @property {boolean= } skipIndentationCheck - Skip indentation checks.
266
+ * @property {boolean= } skipFormatCheck - Skip format checks.
267
+ */
268
+
269
+ /**
270
+ * Run the linters.
271
+ * @param {runOptions } options
272
+ * @param {Error } ex
273
+ */
274
+ function exitHandler ( options , ex ) {
275
+ console . error ( ) ;
276
+ if ( ex ) {
277
+ console . error ( ex ) ;
227
278
console . error ( colors . red ( ex ) ) ;
228
- if ( ! doNotExit ) {
229
- process . exit ( 1 ) ;
230
- }
231
- if ( lintOnlyModifiedFiles && doNotExit ) {
232
- console . log ( 'Watching for changes...' ) ;
233
- }
234
279
}
235
- process . on ( 'unhandledRejection' , ( reason , p ) => {
280
+ if ( options . exitOnError ) {
281
+ process . exit ( 1 ) ;
282
+ }
283
+ if ( options . mode === 'watch' ) {
284
+ console . log ( 'Watching for changes...' ) ;
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Run the linters.
290
+ * @param {runOptions } options
291
+ * @return {Promise<void> }
292
+ */
293
+ function run ( options ) {
294
+ options = options ? options : { } ;
295
+ process . once ( 'unhandledRejection' , ( reason , p ) => {
236
296
console . log ( 'Unhandled Rejection at: Promise' , p , 'reason:' , reason ) ;
237
- exitProcessOnError ( ) ;
297
+ exitHandler ( options ) ;
238
298
} ) ;
239
299
240
- cp . exec ( 'git config core.autocrlf' , ( err , out ) => {
241
- const skipEOL = out . trim ( ) === 'true' ;
242
- if ( ! lintOnlyModifiedFiles && process . argv . length > 2 ) {
243
- return hygiene ( process . argv . slice ( 2 ) , {
244
- skipEOL : skipEOL
245
- } ) . on ( 'error' , exitProcessOnError ) ;
246
- }
300
+ return getGitSkipEOL ( )
301
+ . then ( skipEOL => {
302
+ if ( typeof options . mode !== 'string' && process . argv . length > 2 ) {
303
+ return new Promise ( ( resolve , reject ) => {
304
+ return hygiene ( process . argv . slice ( 2 ) , {
305
+ skipEOL : skipEOL
306
+ } )
307
+ . once ( 'error' , reject )
308
+ . once ( 'end' , resolve ) ;
309
+ } ) ;
310
+ }
247
311
248
- let filesPromise ;
249
- if ( lintOnlyModifiedFiles ) {
250
- filesPromise = Promise . all ( [ getCachedFiles ( ) , getModifiedFiles ( ) ] ) . then ( filesList => {
251
- const files1 = filesList [ 0 ] ;
252
- const files2 = filesList [ 1 ] ;
253
- files2 . forEach ( file => {
254
- if ( files1 . indexOf ( file ) === - 1 ) {
255
- files1 . push ( file ) ;
256
- }
312
+ return getFilesToProcess ( options )
313
+ . then ( files => {
314
+ return new Promise ( ( resolve , reject ) => {
315
+ hygiene ( files , {
316
+ skipEOL : skipEOL ,
317
+ skipFormatCheck : options . skipFormatCheck ,
318
+ skipIndentationCheck : options . skipIndentationCheck
319
+ } )
320
+ . once ( 'end' , ( ) => {
321
+ if ( options . mode === 'watch' ) {
322
+ console . log ( colors . green ( 'Hygiene passed with 0 errors 👍.' ) ) ;
323
+ console . log ( 'Watching for changes...' ) ;
324
+ }
325
+ resolve ( ) ;
326
+ } )
327
+ . once ( 'error' , reject ) ;
328
+ } ) ;
257
329
} ) ;
258
- return files1 ;
259
- } ) ;
260
- } else {
261
- filesPromise = getCachedFiles ( ) ;
330
+ } )
331
+ . catch ( exitHandler . bind ( options ) ) ;
332
+ }
333
+ function getGitSkipEOL ( ) {
334
+ return new Promise ( resolve => {
335
+ cp . exec ( 'git config core.autocrlf' , ( err , out ) => {
336
+ const skipEOL = out . trim ( ) === 'true' ;
337
+ resolve ( skipEOL ) ;
338
+ } ) ;
339
+ } ) ;
340
+ }
341
+ /**
342
+ * Gets a list of files to be processed.
343
+ * @param {runOptions } options
344
+ * @return {Promise<string[]> }
345
+ */
346
+ function getFilesToProcess ( options ) {
347
+ switch ( options . mode ) {
348
+ case 'all' : {
349
+ return Promise . resolve ( all ) ;
350
+ }
351
+ case 'watch' :
352
+ case 'changes' : {
353
+ return Promise . all ( [ getCachedFiles ( ) , getModifiedFiles ( ) ] )
354
+ . then ( filesList => mergeFiles ( filesList [ 0 ] , filesList [ 1 ] ) ) ;
355
+ }
356
+ default : {
357
+ return getCachedFiles ( ) ;
358
+ }
359
+ }
360
+ }
361
+ /**
362
+ * Merges a list of files.
363
+ * @param {string[] } files1
364
+ * @param {string[] } files2
365
+ */
366
+ function mergeFiles ( files1 , files2 ) {
367
+ const files = files2 . slice ( ) ;
368
+ files . forEach ( file => {
369
+ if ( files . indexOf ( file ) === - 1 ) {
370
+ files . push ( file ) ;
262
371
}
263
- filesPromise . then ( files => {
264
- hygiene ( files , {
265
- skipEOL : skipEOL
266
- } )
267
- . on ( 'end' , ( ) => {
268
- if ( lintOnlyModifiedFiles && doNotExit ) {
269
- console . log ( colors . green ( 'Hygiene passed with 0 errors 👍.' ) ) ;
270
- console . log ( 'Watching for changes...' ) ;
271
- }
272
- } )
273
- . on ( 'error' , exitProcessOnError ) ;
274
- } ) . catch ( exitProcessOnError ) ;
275
372
} ) ;
373
+ return files ;
276
374
}
277
375
function getCachedFiles ( ) {
278
376
return new Promise ( resolve => {
@@ -306,5 +404,5 @@ function getModifiedFiles() {
306
404
}
307
405
// this allows us to run hygiene as a git pre-commit hook.
308
406
if ( require . main === module ) {
309
- run ( ) ;
407
+ run ( { exitOnError : true , mode : 'staged' } ) ;
310
408
}
0 commit comments