@@ -33,7 +33,17 @@ interface EslintResult {
33
33
source ?: string ;
34
34
}
35
35
36
+ interface ResultItem {
37
+ commit : string ;
38
+ date : string ;
39
+ email : string ;
40
+ message : EslintMessage ;
41
+ result : EslintResult ;
42
+ }
43
+
36
44
interface FinalConfig {
45
+ /** If set, show only results for Emails matching this pattern */
46
+ emailRegExp ?: RegExp ;
37
47
/** Whitespace to insert between items when formatting */
38
48
gutter : string ;
39
49
/** Translations for plain text used when formatting */
@@ -77,6 +87,7 @@ export interface GitLogFormatter {
77
87
export type CreateGitLogFormatter = ( config : Config ) => GitLogFormatter ;
78
88
79
89
export const defaultConfig : FinalConfig = Object . freeze ( {
90
+ emailRegExp : undefined ,
80
91
gutter : ' ' ,
81
92
label : {
82
93
error : 'error' ,
@@ -98,53 +109,151 @@ export const defaultConfig: FinalConfig = Object.freeze({
98
109
/** Create an instance of the Formatter with your own alternative config */
99
110
export const createGitLogFormatter : CreateGitLogFormatter = ( config ) => {
100
111
const formatter = ( results : EslintResult [ ] ) => {
101
- const getConfig = ( path : string ) =>
102
- getIn ( path , config ) || getIn ( path , defaultConfig ) ;
103
-
104
- const gutter = getConfig ( 'gutter' ) ;
105
- const locationColumnWidth = getConfig ( 'locationColumnWidth' ) ;
106
- const errorLabel = getConfig ( 'label.error' ) ;
107
- const warningLabel = getConfig ( 'label.warning' ) ;
108
- const styledError = getConfig ( 'style.error' ) ;
109
- const styledFilePath = getConfig ( 'style.filePath' ) ;
110
- const styledWarning = getConfig ( 'style.warning' ) ;
111
- const styledLocation = getConfig ( 'style.location' ) ;
112
- const styledRule = getConfig ( 'style.rule' ) ;
113
- const styledCommit = getConfig ( 'style.commit' ) ;
114
- const styledDate = getConfig ( 'style.date' ) ;
115
- const styledEmail = getConfig ( 'style.email' ) ;
112
+ const getConfig = < T > ( path : string ) =>
113
+ getIn < T > ( path , config ) || ( getIn < T > ( path , defaultConfig ) as T ) ;
114
+
115
+ const emailRegExp = getIn < RegExp | undefined > ( 'emailRegExp' , config ) ;
116
+ const gutter = getConfig < string > ( 'gutter' ) ;
117
+ const locationColumnWidth = getConfig < number > ( 'locationColumnWidth' ) ;
118
+ const errorLabel = getConfig < string > ( 'label.error' ) ;
119
+ const warningLabel = getConfig < string > ( 'label.warning' ) ;
120
+ const styledError = getConfig < typeof chalk > ( 'style.error' ) ;
121
+ const styledFilePath = getConfig < typeof chalk > ( 'style.filePath' ) ;
122
+ const styledWarning = getConfig < typeof chalk > ( 'style.warning' ) ;
123
+ const styledLocation = getConfig < typeof chalk > ( 'style.location' ) ;
124
+ const styledRule = getConfig < typeof chalk > ( 'style.rule' ) ;
125
+ const styledCommit = getConfig < typeof chalk > ( 'style.commit' ) ;
126
+ const styledDate = getConfig < typeof chalk > ( 'style.date' ) ;
127
+ const styledEmail = getConfig < typeof chalk > ( 'style.email' ) ;
116
128
const WARNING = styledWarning ( warningLabel ) ;
117
129
const ERROR = styledError ( errorLabel ) ;
118
130
119
- return results . reduce ( ( output , { filePath, messages } ) => {
120
- if ( messages . length > 0 ) {
121
- output += `\n${ styledFilePath ( filePath ) } \n` ;
122
- messages . forEach ( ( { ruleId, severity, message, line, column } ) => {
123
- const command = `git blame --date=relative --show-email -L ${ line } ,${ line } -- "${ filePath } "` ;
124
- const blame = execSync ( command , { encoding : 'utf8' } ) ;
125
- const rawLocation = `${ line } :${ column } ` ;
126
- const status = severity === 1 ? WARNING : ERROR ;
127
- const commitMatch = blame . match ( / ^ [ ^ ] + / ) || [ '' ] ;
128
- const dateMatch = blame . match ( / > ( .+ a g o ) / ) || [ '' , '' ] ;
129
- const emailMatch = blame . match ( / < ( [ ^ > ] + ) > / ) || [ '' , '' ] ;
130
- const rightAlignLocations = ' ' . repeat (
131
- locationColumnWidth - rawLocation . length ,
132
- ) ;
133
- const leftAlignCommitsWithStatuses = ' ' . repeat (
134
- rightAlignLocations . length + rawLocation . length + gutter . length ,
131
+ const mergeMessageWith = ( result : EslintResult ) => (
132
+ message : EslintMessage ,
133
+ ) : ResultItem => {
134
+ const { filePath } = result ;
135
+ const { line } = message ;
136
+ const command = `git blame --date=relative --show-email -L ${ line } ,${ line } -- "${ filePath } "` ;
137
+ const blame = execSync ( command , { encoding : 'utf8' } ) ;
138
+ const commitMatch = blame . match ( / ^ [ ^ ] + / ) || [ '' ] ;
139
+ const dateMatch = blame . match ( / > ( .+ a g o ) / ) || [ '' , '' ] ;
140
+ const emailMatch = blame . match ( / < ( [ ^ > ] + ) > / ) || [ '' , '' ] ;
141
+ const commit = commitMatch [ 0 ] ;
142
+ const date = dateMatch [ 1 ] . trim ( ) ;
143
+ const email = emailMatch [ 1 ] ;
144
+ return {
145
+ commit,
146
+ date,
147
+ email,
148
+ message,
149
+ result,
150
+ } ;
151
+ } ;
152
+
153
+ const rightAlignToCol1 = ( col1Contents : string ) =>
154
+ ' ' . repeat ( locationColumnWidth - col1Contents . length ) ;
155
+
156
+ const leftAlignToCol2 = ( col1Contents : string ) =>
157
+ ' ' . repeat (
158
+ rightAlignToCol1 ( col1Contents ) . length +
159
+ col1Contents . length +
160
+ gutter . length ,
161
+ ) ;
162
+
163
+ const cols = ( col1 : string | number , col2 : string | number ) =>
164
+ `${ rightAlignToCol1 ( `${ col1 } ` ) } ${ col1 } ${ gutter } ${ col2 } ` ;
165
+
166
+ const formatMessage = ( {
167
+ commit : rawCommit ,
168
+ date : rawDate ,
169
+ email : rawEmail ,
170
+ message : { ruleId, severity, message, line, column } ,
171
+ } : ResultItem ) => {
172
+ const rawLocation = `${ line } :${ column } ` ;
173
+ const status = severity === 1 ? WARNING : ERROR ;
174
+ const headIndent = rightAlignToCol1 ( rawLocation ) ;
175
+ const footIndent = leftAlignToCol2 ( rawLocation ) ;
176
+ const location = styledLocation ( rawLocation ) ;
177
+ const rule = ruleId ? styledRule ( ruleId ) : '' ;
178
+ const commit = styledCommit ( `${ rawCommit } ` ) ;
179
+ const date = styledDate ( `(${ rawDate } )` ) ;
180
+ const email = styledEmail ( `<${ rawEmail } >` ) ;
181
+ let output = '' ;
182
+ output += `${ headIndent } ${ location } ${ gutter } ${ status } ${ gutter } ${ message } ${ gutter } ${ rule } \n` ;
183
+ output += `${ footIndent } ${ commit } ${ email } ${ date } \n` ;
184
+ return output ;
185
+ } ;
186
+
187
+ const isIncluded = ( { email } : ResultItem ) =>
188
+ emailRegExp ? emailRegExp . test ( email ) : true ;
189
+
190
+ const authors = new Set ( ) ;
191
+ const total = {
192
+ all : 0 ,
193
+ errors : 0 ,
194
+ userErrors : 0 ,
195
+ userWarnings : 0 ,
196
+ visible : 0 ,
197
+ warnings : 0 ,
198
+ } ;
199
+
200
+ const body = results . reduce ( ( output , result ) => {
201
+ if ( result . messages . length > 0 ) {
202
+ const items = result . messages
203
+ . map ( mergeMessageWith ( result ) )
204
+ . map ( ( item ) => {
205
+ authors . add ( item . email ) ;
206
+ return item ;
207
+ } )
208
+ . filter ( isIncluded )
209
+ . map ( ( item ) => {
210
+ if ( item . message . severity === 2 ) {
211
+ total . userErrors ++ ;
212
+ } else if ( item . message . severity === 1 ) {
213
+ total . userWarnings ++ ;
214
+ }
215
+ return item ;
216
+ } ) ;
217
+
218
+ total . errors += result . errorCount ;
219
+ total . warnings += result . warningCount ;
220
+ total . all += result . messages . length ;
221
+ total . visible += items . length ;
222
+
223
+ if ( items . length > 0 ) {
224
+ output += `\n${ styledFilePath ( result . filePath ) } \n` ;
225
+ output += items . reduce < string > (
226
+ ( str : string , item : ResultItem ) => `${ str } ${ formatMessage ( item ) } ` ,
227
+ '' ,
135
228
) ;
136
- const location = styledLocation ( rawLocation ) ;
137
- const rule = ruleId ? styledRule ( ruleId ) : '' ;
138
- const commit = styledCommit ( `${ commitMatch [ 0 ] } ` ) ;
139
- const date = styledDate ( `(${ dateMatch [ 1 ] . trim ( ) } )` ) ;
140
- const email = styledEmail ( `<${ emailMatch [ 1 ] } >` ) ;
141
- output += `${ rightAlignLocations } ${ location } ${ gutter } ${ status } ${ gutter } ${ message } ${ gutter } ${ rule } \n` ;
142
- output += `${ leftAlignCommitsWithStatuses } ${ commit } ${ email } ${ date } \n` ;
143
- } ) ;
229
+ }
144
230
}
145
231
return output ;
146
232
} , '' ) ;
233
+
234
+ const banner = chalk . inverse ( ' REPORT COMPLETE ' ) ;
235
+
236
+ let footer = '' ;
237
+
238
+ if ( emailRegExp ) {
239
+ const totalWarnings = `${ total . userWarnings } /${ total . warnings } ` ;
240
+ const totalErrors = `${ total . userErrors } /${ total . errors } ` ;
241
+ const totalWarningsLabel = `Warnings assigned to ${ emailRegExp } ` ;
242
+ const totalErrorsLabel = `Errors assigned to ${ emailRegExp } ` ;
243
+ footer += `${ cols ( results . length , 'Files' ) } \n` ;
244
+ footer += `${ cols ( totalWarnings , totalWarningsLabel ) } \n` ;
245
+ footer += `${ cols ( totalErrors , totalErrorsLabel ) } \n` ;
246
+ footer += `${ cols ( authors . size , 'Assignees' ) } \n` ;
247
+ } else {
248
+ footer += `${ cols ( results . length , 'Files' ) } \n` ;
249
+ footer += `${ cols ( total . warnings , 'Warnings' ) } \n` ;
250
+ footer += `${ cols ( total . errors , 'Errors' ) } \n` ;
251
+ footer += `${ cols ( authors . size , 'Assignees' ) } \n` ;
252
+ }
253
+
254
+ return `${ body } \n${ banner } \n\n${ footer } ` ;
147
255
} ;
256
+
148
257
formatter . defaultConfig = defaultConfig ;
149
258
formatter . withConfig = createGitLogFormatter ;
150
259
return formatter ;
0 commit comments