3
3
import * as React from "react" ;
4
4
import * as sui from "./sui" ;
5
5
6
+ type GroupedError = {
7
+ error : pxtc . KsDiagnostic ,
8
+ count : number ,
9
+ index : number
10
+ } ;
11
+
6
12
export interface ErrorListProps {
7
13
onSizeChange : ( state : pxt . editor . ErrorListState ) => void ;
8
14
listenToErrorChanges : ( key : string , onErrorChanges : ( errors : pxtc . KsDiagnostic [ ] ) => void ) => void ;
@@ -41,7 +47,7 @@ export class ErrorList extends React.Component<ErrorListProps, ErrorListState> {
41
47
}
42
48
43
49
render ( ) {
44
- const { isCollapsed, errors, exception} = this . state ;
50
+ const { isCollapsed, errors, exception } = this . state ;
45
51
const errorsAvailable = ! ! errors ?. length || ! ! exception ;
46
52
const collapseTooltip = lf ( "Collapse Error List" ) ;
47
53
@@ -52,12 +58,12 @@ export class ErrorList extends React.Component<ErrorListProps, ErrorListState> {
52
58
< div className = "errorListHeader" role = "button" aria-label = { lf ( "{0} error list" , isCollapsed ? lf ( "Expand" ) : lf ( "Collapse" ) ) } onClick = { this . onCollapseClick } onKeyDown = { sui . fireClickOnEnter } tabIndex = { 0 } >
53
59
< h4 > { lf ( "Problems" ) } </ h4 >
54
60
< div className = "ui red circular label countBubble" > { exception ? 1 : errors . length } </ div >
55
- < div className = "toggleButton" > < sui . Icon icon = { `chevron ${ isCollapsed ? 'up' : 'down' } ` } /> </ div >
61
+ < div className = "toggleButton" > < sui . Icon icon = { `chevron ${ isCollapsed ? 'up' : 'down' } ` } /> </ div >
56
62
</ div >
57
63
{ ! isCollapsed && < div className = "errorListInner" >
58
64
{ exception && < div className = "debuggerSuggestion" role = "button" onClick = { this . props . startDebugger } onKeyDown = { sui . fireClickOnEnter } tabIndex = { 0 } >
59
65
{ lf ( "Debug this project" ) }
60
- < sui . Icon className = "debug-icon" icon = "icon bug" />
66
+ < sui . Icon className = "debug-icon" icon = "icon bug" />
61
67
</ div > }
62
68
{ errorListContent }
63
69
</ div > }
@@ -88,7 +94,7 @@ export class ErrorList extends React.Component<ErrorListProps, ErrorListState> {
88
94
}
89
95
90
96
onErrorMessageClick ( e : pxtc . LocationInfo , index : number ) {
91
- pxt . tickEvent ( 'errorlist.goto' , { errorIndex : index } , { interactiveConsent : true } ) ;
97
+ pxt . tickEvent ( 'errorlist.goto' , { errorIndex : index } , { interactiveConsent : true } ) ;
92
98
this . props . goToError ( e )
93
99
}
94
100
@@ -115,8 +121,9 @@ export class ErrorList extends React.Component<ErrorListProps, ErrorListState> {
115
121
return `${ error . messageText } -${ error . fileName } -${ error . line } -${ error . column } `
116
122
}
117
123
124
+ const grouped = groupErrors ( errors ) ;
118
125
return < div className = "ui selection list" >
119
- { ( errors ) . map ( ( e , index ) => < ErrorListItem key = { errorKey ( e ) } index = { index } error = { e } revealError = { this . onErrorMessageClick } /> ) }
126
+ { grouped . map ( ( e , index ) => < ErrorListItem key = { errorKey ( e . error ) } index = { index } error = { e } revealError = { this . onErrorMessageClick } /> ) }
120
127
</ div >
121
128
}
122
129
@@ -129,7 +136,7 @@ export class ErrorList extends React.Component<ErrorListProps, ErrorListState> {
129
136
130
137
if ( ! location ) return null ;
131
138
132
- return < ErrorListItem key = { index } index = { index } stackframe = { sf } location = { location } revealError = { this . onErrorMessageClick } />
139
+ return < ErrorListItem key = { index } index = { index } stackframe = { sf } location = { location } revealError = { this . onErrorMessageClick } />
133
140
} ) }
134
141
</ div >
135
142
</ div > ;
@@ -139,7 +146,7 @@ export class ErrorList extends React.Component<ErrorListProps, ErrorListState> {
139
146
interface ErrorListItemProps {
140
147
index : number ;
141
148
revealError : ( location : pxtc . LocationInfo , index : number ) => void ;
142
- error ?: pxtc . KsDiagnostic ;
149
+ error ?: GroupedError ;
143
150
stackframe ?: pxsim . StackFrameInfo ;
144
151
location ?: pxtc . LocationInfo ;
145
152
}
@@ -156,22 +163,53 @@ class ErrorListItem extends React.Component<ErrorListItemProps, ErrorListItemSta
156
163
}
157
164
158
165
render ( ) {
159
- const { error, stackframe, location} = this . props
166
+ const { error, stackframe, location } = this . props
160
167
161
- const message = stackframe ? lf ( "at {0} (line {1})" , stackframe . funcInfo . functionName , location . line + 1 )
162
- : lf ( "Line {0}: {1}" , error . endLine ? error . endLine + 1 : error . line + 1 , error . messageText )
168
+ const message = stackframe ?
169
+ stackFrameMessageStringWithLineNumber ( stackframe , location ) :
170
+ errorMessageStringWithLineNumber ( error . error ) ;
171
+ const errorCount = stackframe ? 1 : error . count ;
163
172
164
173
return < div className = { `item ${ stackframe ? 'stackframe' : '' } ` } role = "button"
165
- onClick = { this . onErrorListItemClick }
166
- onKeyDown = { sui . fireClickOnEnter }
167
- aria-label = { lf ( "Go to {0}: {1}" , stackframe ? '' : 'error' , message ) }
168
- tabIndex = { 0 } >
169
- { message }
174
+ onClick = { this . onErrorListItemClick }
175
+ onKeyDown = { sui . fireClickOnEnter }
176
+ aria-label = { lf ( "Go to {0}: {1}" , stackframe ? '' : 'error' , message ) }
177
+ tabIndex = { 0 } >
178
+ { message } { ( errorCount <= 1 ) ? null : < div className = "ui gray circular label countBubble" > { errorCount } </ div > }
170
179
</ div >
171
180
}
172
181
173
182
onErrorListItemClick ( ) {
174
- const location = this . props . stackframe ? this . props . location : this . props . error
183
+ const location = this . props . stackframe ? this . props . location : this . props . error . error
175
184
this . props . revealError ( location , this . props . index )
176
185
}
177
186
}
187
+
188
+ function errorMessageStringWithLineNumber ( error : pxtc . KsDiagnostic ) {
189
+ return lf ( "Line {0}: {1}" , error . endLine ? error . endLine + 1 : error . line + 1 , error . messageText ) ;
190
+ }
191
+
192
+ function stackFrameMessageStringWithLineNumber ( stackframe : pxsim . StackFrameInfo , location : pxtc . LocationInfo ) {
193
+ return lf ( "at {0} (line {1})" , stackframe . funcInfo . functionName , location . line + 1 ) ;
194
+ }
195
+
196
+ function groupErrors ( errors : pxtc . KsDiagnostic [ ] ) {
197
+ const grouped = new Map < string , GroupedError > ( ) ;
198
+ let index = 0 ;
199
+ for ( const error of errors ) {
200
+ const key = errorMessageStringWithLineNumber ( error ) ;
201
+ if ( ! grouped . has ( key ) ) {
202
+ grouped . set ( key , {
203
+ index : index ++ ,
204
+ count : 1 ,
205
+ error
206
+ } ) ;
207
+ }
208
+ else {
209
+ grouped . get ( key ) . count ++ ;
210
+ }
211
+ }
212
+ const sorted : GroupedError [ ] = [ ] ;
213
+ grouped . forEach ( value => sorted . push ( value ) ) ;
214
+ return sorted . sort ( ( a , b ) => a . index - b . index ) ;
215
+ }
0 commit comments