import * as fs from "fs" ; import * as glob from "glob" ; type Listener < T extends string , D > = { [ K in T ] ? : ( data : D ) => void ; } ; class Emitter < T extends string , D > { private listeners : Listener < T , D > [ ] = [ ] ; constructor ( listeners ) { this . listeners = listeners ; } emit ( type , data ) { this . listeners . forEach ( listener => listener [ type ] ?. ( data ) ) ; } } type Data = { filename : string ; text : string ; line : number ; col ? : number ; } ; enum Node { Char = "Char" , Line = "Line" } function print ( filename , message ) { console . log ( "\x1b[35m" , `${ filename }-` , "\x1b[36m" , `${ message }` , "\x1b[0m" ) ; } /**
 * 출력될 수 없는 문자가 있는지 검사합니다.
 */ function checkNonPrintableChar ( ) { const NON_PRINTABLE_CHAR_CODES = [ 0x1B , 0x1C , 0x8 ] ; return { [ Node . Char ] ( { filename , text : char , line , col = 0 } ) { const charCode = char . charCodeAt ( 0 ) ; if ( NON_PRINTABLE_CHAR_CODES . includes ( char . charCodeAt ( 0 ) ) ) { print ( filename , `출력할 수 없는 문자 ${ charCode } 가 있습니다. (${ line }:${ col })` ) ; } } } ; } const rules = [ checkNonPrintableChar ( ) , ] ; ( function run ( [ , , pattern ] , rules ) { const filenames = glob . sync ( pattern , { ignore : "**/node_modules/**" } ) ; const emitter = new Emitter < Node , Data > ( rules ) ; filenames . forEach ( filename => { fs . readFile ( `${ process . cwd ( ) }/${ filename }` , "utf8" , ( err , file ) => { if ( err ) { throw new Error ( err . message ) ; } const lines = file . split ( "\n" ) ; lines . forEach ( ( text , line ) => { emitter . emit ( Node . Line , ( { filename , text , line } ) ) ; [ ... text ] . forEach ( ( char , col ) => emitter . emit ( Node . Char , ( { filename , text : char , line , col } ) ) ) ; } ) ; } ) ; } ) ; } ) ( process . argv , rules ) ;