@@ -103,46 +103,156 @@ export default class VerilatorLinter extends BaseLinter {
103
103
this . logger . info ( '[verilator] command: ' + command ) ;
104
104
this . logger . info ( '[verilator] cwd : ' + cwd ) ;
105
105
106
+
107
+
106
108
var _ : child . ChildProcess = child . exec (
107
109
command ,
108
110
{ cwd : cwd } ,
109
111
( _error : Error , _stdout : string , stderr : string ) => {
110
- let diagnostics : vscode . Diagnostic [ ] = [ ] ;
111
- stderr . split ( / \r ? \n / g) . forEach ( ( line , _ ) => {
112
- if ( line . search ( "No such file or directory" ) >= 0 || line . search ( "Not a directory" ) >= 0 || line . search ( "command not found" ) >= 0 ) {
113
- this . logger . error ( `Could not execute command: ${ command } ` ) ;
114
- return ;
112
+
113
+ // basically DiagnosticsCollection but with ability to append diag lists
114
+ let filesDiag = new Map ( ) ;
115
+
116
+ stderr . split ( / \r ? \n / g) . forEach ( ( line , _ , stderrLines ) => {
117
+
118
+
119
+ // if lineIndex is 0 and it doesn't start with %Error or %Warning,
120
+ // the whole loop would skip
121
+ // and it is probably a system error (wrong file name/directory/something)
122
+ let lastDiagMessageType : string = "Error" ;
123
+
124
+ // parsing previous lines for message type
125
+ // shouldn't be more than 5 or so
126
+ for ( let lineIndex = _ ; lineIndex >= 0 ; lineIndex -- )
127
+ {
128
+ if ( stderrLines [ lineIndex ] . startsWith ( "%Error" ) )
129
+ {
130
+ lastDiagMessageType = "Error" ;
131
+ break ;
132
+ }
133
+ if ( stderrLines [ lineIndex ] . startsWith ( "%Warning" ) )
134
+ {
135
+ lastDiagMessageType = "Warning" ;
136
+ break ;
137
+ }
115
138
}
116
139
117
- if ( ! line . startsWith ( '%' ) || line . indexOf ( docUri ) <= 0 ) {
140
+ // first line would be normal stderr output like "directory name is invalid"
141
+ // others are verilator sort of "highlighting" the issue, the block with "^~~~~"
142
+ // this can actually be used for better error/warning highlighting
143
+
144
+ // also this might have some false positives
145
+ // probably something like "stderr passthrough setting" would be a good idea
146
+ if ( ! line . startsWith ( '%' ) ) {
147
+
148
+ // allows for persistent
149
+ if ( lastDiagMessageType === 'Warning' ) { this . logger . warn ( line ) ; }
150
+ else { this . logger . error ( line ) ; }
118
151
return ;
119
152
}
120
153
121
- let rex = line . match (
122
- / % ( \w + ) ( - [ A - Z 0 - 9 _ ] + ) ? : \s * ( \w + : ) ? (?: [ ^ : ] + ) : \s * ( \d + ) : (?: \s * ( \d + ) : ) ? \s * ( \s * .+ ) /
154
+
155
+ // important match sections are named now:
156
+ // severity - Error or Warning
157
+ // errorCode - error code, if there is one, something like PINNOTFOUND
158
+ // filePath - full path to the file, including it's name and extension
159
+ // lineNumber - line number
160
+ // columNumber - columnNumber
161
+ // verboseError - error elaboration by verilator
162
+
163
+ let errorParserRegex = new RegExp (
164
+ / % (?< severity > \w + ) / . source + // matches "%Warning" or "%Error"
165
+
166
+ // this matches errorcode with "-" before it, but the "-" doesn't go into ErrorCode match group
167
+ / ( - (?< errorCode > [ A - Z 0 - 9 ] + ) ) ? / . source + // matches error code like -PINNOTFOUND
168
+
169
+ / : / . source + // ": " before file path or error message
170
+
171
+ // this one's a bit of a mess, but apparently one can't cleanly split regex match group between lines
172
+ // and this is a large group since it matches file path and line and column numbers which may not exist at all
173
+
174
+ // note: end of file path is detected using file extension at the end of it
175
+ // this also allows for spaces in path.
176
+ // (neiter Linux, nor Windows actually prohibits it, and Verilator handles spaces just fine)
177
+ // In my testing, didn't lead cause any problems, but it potentially can
178
+ // extension names are placed so that longest one is first and has highest priority
179
+
180
+ / ( (?< filePath > ( \S | ) + (?< fileExtension > ( \. s v h ) | ( \. s v ) | ( \. S V ) | ( \. v h ) | ( \. v l ) | ( \. v ) ) ) : ( (?< lineNumber > \d + ) : ) ? ( (?< columnNumber > \d + ) : ) ? ) ? / . source +
181
+
182
+ // matches error message produced by Verilator
183
+ / (?< verboseError > .* ) / . source
184
+ , "g"
123
185
) ;
124
186
187
+ let rex = errorParserRegex . exec ( line ) ;
188
+
189
+ // stderr passthrough
190
+ // probably better toggled with a parameter
191
+ if ( rex . groups [ "severity" ] === "Error" ) { this . logger . error ( line ) ; }
192
+ else if ( rex . groups [ "severity" ] === "Warning" ) { this . logger . warn ( line ) ; }
193
+
194
+ // theoretically, this shoudn't "fire", but just in case
195
+ else { this . logger . error ( line ) ; }
196
+
197
+
198
+
199
+
200
+ // vscode problems are tied to files
201
+ // if there isn't a file name, no point going further
202
+ if ( ! rex . groups [ "filePath" ] ) {
203
+ return ;
204
+ }
205
+
206
+ // replacing "\\" and "\" with "/" for consistency
207
+ if ( isWindows )
208
+ {
209
+ rex . groups [ "filePath" ] = rex . groups [ "filePath" ] . replace ( / ( \\ \\ ) | ( \\ ) / g, "/" ) ;
210
+ }
211
+
212
+ // if there isn't a list of errors for this file already, it
213
+ // needs to be created
214
+ if ( ! filesDiag . has ( rex . groups [ "filePath" ] ) )
215
+ {
216
+ filesDiag . set ( rex . groups [ "filePath" ] , [ ] ) ;
217
+ }
218
+
219
+
125
220
if ( rex && rex [ 0 ] . length > 0 ) {
126
- let lineNum = Number ( rex [ 4 ] ) - 1 ;
127
- let colNum = Number ( rex [ 5 ] ) - 1 ;
128
- // Type of warning is in rex[2]
221
+ let lineNum = Number ( rex . groups [ "lineNumber" ] ) - 1 ;
222
+ let colNum = Number ( rex . groups [ "columnNumber" ] ) - 1 ;
223
+
129
224
colNum = isNaN ( colNum ) ? 0 : colNum ; // for older Verilator versions (< 4.030 ~ish)
130
225
131
226
if ( ! isNaN ( lineNum ) ) {
132
- diagnostics . push ( {
133
- severity : this . convertToSeverity ( rex [ 1 ] ) ,
227
+
228
+ // appending diagnostic message to an array of messages
229
+ // tied to a file
230
+ filesDiag . get ( rex . groups [ "filePath" ] ) . push ( {
231
+ severity : this . convertToSeverity ( rex . groups [ "severity" ] ) ,
134
232
range : new vscode . Range ( lineNum , colNum , lineNum , Number . MAX_VALUE ) ,
135
- message : rex [ 6 ] ,
136
- code : 'verilator' ,
233
+ message : rex . groups [ "verboseError" ] ,
234
+ code : rex . groups [ "errorCode" ] ,
137
235
source : 'verilator' ,
138
236
} ) ;
237
+
139
238
}
140
239
return ;
141
240
}
142
- this . logger . warn ( '[verilator] failed to parse error: ' + line ) ;
143
241
} ) ;
144
- this . logger . info ( `[verilator] ${ diagnostics . length } errors/warnings returned` ) ;
145
- this . diagnosticCollection . set ( doc . uri , diagnostics ) ;
242
+
243
+ // since error parsing has been redone "from the ground up"
244
+ // earlier errors are discarded
245
+ this . diagnosticCollection . clear ( ) ;
246
+
247
+ filesDiag . forEach ( ( issuesArray , fileName ) =>
248
+ {
249
+ let fileURI = vscode . Uri . file ( fileName ) ;
250
+ this . diagnosticCollection . set (
251
+ fileURI ,
252
+ issuesArray
253
+ ) ;
254
+ }
255
+ ) ;
146
256
}
147
257
) ;
148
258
}
0 commit comments