@@ -25,7 +25,7 @@ import * as _ from 'lodash';
2525import * as path from 'path' ;
2626
2727// TODO: Make this more precise.
28- const split : ( ) => { } = require ( 'split' ) ;
28+ const split : ( ) => fs . WriteStream = require ( 'split' ) ;
2929
3030export interface FileStats {
3131 // TODO: Verify that this member should actually be optional.
@@ -37,6 +37,7 @@ export interface FileStats {
3737export interface ScanStats { [ filename : string ] : FileStats | undefined ; }
3838
3939export interface ScanResults {
40+ errors ( ) : Map < string , Error > ;
4041 all ( ) : ScanStats ;
4142 selectStats ( regex : RegExp ) : ScanStats ;
4243 selectFiles ( regex : RegExp , baseDir : string ) : string [ ] ;
@@ -56,7 +57,13 @@ class ScanResultsImpl implements ScanResults {
5657 * attributes respectively
5758 * @param hash A hashcode computed from the contents of all the files.
5859 */
59- constructor ( private readonly stats : ScanStats , readonly hash ?: string ) { }
60+ constructor (
61+ private readonly stats : ScanStats , readonly errorMap : Map < string , Error > ,
62+ readonly hash ?: string ) { }
63+
64+ errors ( ) : Map < string , Error > {
65+ return this . errorMap ;
66+ }
6067
6168 /**
6269 * Used to get all of the file scan results.
@@ -105,22 +112,10 @@ class ScanResultsImpl implements ScanResults {
105112 }
106113}
107114
108- export function scan (
115+ export async function scan (
109116 shouldHash : boolean , baseDir : string , regex : RegExp ) : Promise < ScanResults > {
110- return new Promise < ScanResults > ( ( resolve , reject ) => {
111- findFiles ( baseDir , regex , ( err1 : Error | null , fileList ?: string [ ] ) => {
112- if ( err1 ) {
113- return reject ( err1 ) ;
114- }
115- // TODO: Handle the case where `fileList` is undefined.
116- computeStats ( fileList as string [ ] , shouldHash , ( err2 , results ) => {
117- if ( err2 ) {
118- return reject ( err2 ) ;
119- }
120- resolve ( results ) ;
121- } ) ;
122- } ) ;
123- } ) ;
117+ const fileList = await findFiles ( baseDir , regex ) ;
118+ return await computeStats ( fileList , shouldHash ) ;
124119}
125120
126121/**
@@ -135,46 +130,40 @@ export function scan(
135130// TODO: Typescript: Fix the docs associated with this function to match the
136131// call signature
137132function computeStats (
138- fileList : string [ ] , shouldHash : boolean ,
139- callback : ( err : Error | null , results ?: ScanResults ) => void ) : void {
140- let pending = fileList . length ;
141- // return a valid, if fake, result when there are no js files to hash.
142- if ( pending === 0 ) {
143- callback ( null , new ScanResultsImpl ( { } , 'EMPTY-no-js-files' ) ) ;
144- return ;
145- }
133+ fileList : string [ ] , shouldHash : boolean ) : Promise < ScanResults > {
134+ return new Promise < ScanResults > ( async ( resolve , reject ) => {
135+ // return a valid, if fake, result when there are no js files to hash.
136+ if ( fileList . length === 0 ) {
137+ resolve ( new ScanResultsImpl ( { } , new Map ( ) , 'EMPTY-no-js-files' ) ) ;
138+ return ;
139+ }
146140
147- // TODO: Address the case where the array contains `undefined`.
148- const hashes : Array < string | undefined > = [ ] ;
149- const statistics : ScanStats = { } ;
150- fileList . forEach ( ( filename ) => {
151- statsForFile (
152- filename , shouldHash , ( err : Error | null , fileStats ?: FileStats ) => {
153- if ( err ) {
154- callback ( err ) ;
155- return ;
156- }
141+ // TODO: Address the case where the array contains `undefined`.
142+ const hashes : Array < string | undefined > = [ ] ;
143+ const statistics : ScanStats = { } ;
144+ const errors : Map < string , Error > = new Map < string , Error > ( ) ;
157145
158- pending -- ;
159- if ( shouldHash ) {
160- // TODO: Address the case when `fileStats` is `undefined`
161- hashes . push ( ( fileStats as FileStats ) . hash ) ;
162- }
163- statistics [ filename ] = fileStats ;
146+ for ( const filename of fileList ) {
147+ try {
148+ const fileStats = await statsForFile ( filename , shouldHash ) ;
149+ if ( shouldHash ) {
150+ hashes . push ( fileStats . hash ) ;
151+ }
152+ statistics [ filename ] = fileStats ;
153+ } catch ( err ) {
154+ errors . set ( filename , err ) ;
155+ }
156+ }
164157
165- if ( pending === 0 ) {
166- let hash ;
167- if ( shouldHash ) {
168- // Sort the hashes to get a deterministic order as the files may
169- // not be in the same order each time we scan the disk.
170- const buffer = hashes . sort ( ) . join ( ) ;
171- const sha1 =
172- crypto . createHash ( 'sha1' ) . update ( buffer ) . digest ( 'hex' ) ;
173- hash = 'SHA1-' + sha1 ;
174- }
175- callback ( null , new ScanResultsImpl ( statistics , hash ) ) ;
176- }
177- } ) ;
158+ let hash ;
159+ if ( shouldHash ) {
160+ // Sort the hashes to get a deterministic order as the files may
161+ // not be in the same order each time we scan the disk.
162+ const buffer = hashes . sort ( ) . join ( ) ;
163+ const sha1 = crypto . createHash ( 'sha1' ) . update ( buffer ) . digest ( 'hex' ) ;
164+ hash = 'SHA1-' + sha1 ;
165+ }
166+ resolve ( new ScanResultsImpl ( statistics , errors , hash ) ) ;
178167 } ) ;
179168}
180169
@@ -187,45 +176,44 @@ function computeStats(
187176 * files to find based on their filename
188177 * @param {!function(?Error, Array<string>) } callback error-back callback
189178 */
190- function findFiles (
191- baseDir : string , regex : RegExp ,
192- callback : ( err : Error | null , fileList ?: string [ ] ) => void ) : void {
193- let errored = false ;
179+ function findFiles ( baseDir : string , regex : RegExp ) : Promise < string [ ] > {
180+ return new Promise < string [ ] > ( ( resolve , reject ) => {
181+ let error : Error | undefined ;
194182
195- if ( ! baseDir ) {
196- callback ( new Error ( 'hasher.findJSFiles requires a baseDir argument' ) ) ;
197- return ;
198- }
183+ if ( ! baseDir ) {
184+ reject ( new Error ( 'hasher.findJSFiles requires a baseDir argument' ) ) ;
185+ return ;
186+ }
199187
200- const find = findit ( baseDir ) ;
201- const fileList : string [ ] = [ ] ;
188+ const find = findit ( baseDir ) ;
189+ const fileList : string [ ] = [ ] ;
202190
203- find . on ( 'error' , ( err : Error ) => {
204- errored = true ;
205- callback ( err ) ;
206- return ;
207- } ) ;
191+ find . on ( 'error' , ( err : Error ) => {
192+ error = err ;
193+ return ;
194+ } ) ;
208195
209- find . on ( 'directory' , ( dir : string , ignore : fs . Stats , stop : ( ) => void ) => {
210- const base = path . basename ( dir ) ;
211- if ( base === '.git' || base === 'node_modules' ) {
212- stop ( ) ; // do not descend
213- }
214- } ) ;
196+ find . on ( 'directory' , ( dir : string , ignore : fs . Stats , stop : ( ) => void ) => {
197+ const base = path . basename ( dir ) ;
198+ if ( base === '.git' || base === 'node_modules' ) {
199+ stop ( ) ; // do not descend
200+ }
201+ } ) ;
215202
216- find . on ( 'file' , ( file : string ) => {
217- if ( regex . test ( file ) ) {
218- fileList . push ( file ) ;
219- }
220- } ) ;
203+ find . on ( 'file' , ( file : string ) => {
204+ if ( regex . test ( file ) ) {
205+ fileList . push ( file ) ;
206+ }
207+ } ) ;
221208
222- find . on ( 'end' , ( ) => {
223- if ( errored ) {
224- // the end event fires even after an error
225- // simply return because the on('error') has already called back
226- return ;
227- }
228- callback ( null , fileList ) ;
209+ find . on ( 'end' , ( ) => {
210+ // Note: the `end` event fires even after an error
211+ if ( error ) {
212+ reject ( error ) ;
213+ } else {
214+ resolve ( fileList ) ;
215+ }
216+ } ) ;
229217 } ) ;
230218}
231219
@@ -237,31 +225,38 @@ function findFiles(
237225 * @private
238226 */
239227function statsForFile (
240- filename : string , shouldHash : boolean ,
241- cb : ( err : Error | null , stats ?: FileStats ) => void ) : void {
242- let shasum : crypto . Hash ;
243- if ( shouldHash ) {
244- shasum = crypto . createHash ( 'sha1' ) ;
245- }
246- // TODO: Determine why property 'ReadStream' does not exist on type 'fs'
247- const s = ( fs as { } as { ReadStream : Function } ) . ReadStream ( filename ) ;
248- let lines = 0 ;
249- const byLine = s . pipe ( split ( ) ) ;
250- byLine . on ( 'error' , ( e : Error ) => {
251- cb ( e ) ;
252- } ) ;
253- byLine . on ( 'data' , ( d : string ) => {
254- if ( shouldHash ) {
255- shasum . update ( d ) ;
256- }
257- lines ++ ;
258- } ) ;
259- byLine . on ( 'end' , ( ) => {
260- // TODO: Address the case where `d` is `undefined`.
261- let d : string | undefined ;
262- if ( shouldHash ) {
263- d = shasum . digest ( 'hex' ) ;
264- }
265- cb ( null , { hash : d , lines} ) ;
228+ filename : string , shouldHash : boolean ) : Promise < FileStats > {
229+ return new Promise < FileStats > ( ( resolve , reject ) => {
230+ const reader = fs . createReadStream ( filename ) ;
231+ reader . on ( 'error' , ( err ) => {
232+ reject ( err ) ;
233+ } ) ;
234+ reader . on ( 'open' , ( ) => {
235+ let shasum : crypto . Hash ;
236+ if ( shouldHash ) {
237+ shasum = crypto . createHash ( 'sha1' ) ;
238+ }
239+
240+ let lines = 0 ;
241+ let error : Error | undefined ;
242+ const byLine = reader ! . pipe ( split ( ) ) ;
243+ byLine . on ( 'error' , ( e : Error ) => {
244+ error = e ;
245+ } ) ;
246+ byLine . on ( 'data' , ( d : string ) => {
247+ if ( shouldHash ) {
248+ shasum . update ( d ) ;
249+ }
250+ lines ++ ;
251+ } ) ;
252+ byLine . on ( 'end' , ( ) => {
253+ if ( error ) {
254+ reject ( error ) ;
255+ } else {
256+ const hash = shouldHash ? shasum . digest ( 'hex' ) : undefined ;
257+ resolve ( { hash, lines} ) ;
258+ }
259+ } ) ;
260+ } ) ;
266261 } ) ;
267262}
0 commit comments