@@ -25,7 +25,6 @@ import com.beust.jcommander.Parameters
25
25
import com.beust.jcommander.ParameterException
26
26
import groovy.json.JsonOutput
27
27
import groovy.transform.CompileStatic
28
- import groovy.transform.Memoized
29
28
import groovy.util.logging.Slf4j
30
29
import nextflow.config.control.ConfigParser
31
30
import nextflow.config.formatter.ConfigFormattingVisitor
@@ -35,13 +34,14 @@ import nextflow.script.control.ParanoidWarning
35
34
import nextflow.script.control.ScriptParser
36
35
import nextflow.script.formatter.FormattingOptions
37
36
import nextflow.script.formatter.ScriptFormattingVisitor
37
+ import nextflow.script.parser.v2.ErrorListener
38
+ import nextflow.script.parser.v2.ErrorSummary
39
+ import nextflow.script.parser.v2.StandardErrorListener
38
40
import nextflow.util.PathUtils
39
41
import org.codehaus.groovy.control.SourceUnit
40
42
import org.codehaus.groovy.control.messages.SyntaxErrorMessage
41
43
import org.codehaus.groovy.control.messages.WarningMessage
42
44
import org.codehaus.groovy.syntax.SyntaxException
43
- import org.fusesource.jansi.Ansi
44
- import org.fusesource.jansi.AnsiConsole
45
45
/**
46
46
* CLI sub-command LINT
47
47
*
@@ -266,248 +266,6 @@ class CmdLint extends CmdBase {
266
266
}
267
267
268
268
269
- class ErrorSummary {
270
- int errors = 0
271
- int filesWithErrors = 0
272
- int filesWithoutErrors = 0
273
- int filesFormatted = 0
274
- }
275
-
276
-
277
- interface ErrorListener {
278
- void beforeAll ()
279
- void beforeFile (File file )
280
- void beforeErrors ()
281
- void onError (SyntaxException error , String filename , SourceUnit source )
282
- void onWarning (WarningMessage warning , String filename , SourceUnit source )
283
- void afterErrors ()
284
- void beforeFormat (File file )
285
- void afterAll (ErrorSummary summary )
286
- }
287
-
288
-
289
- @CompileStatic
290
- class StandardErrorListener implements ErrorListener {
291
- private String mode
292
- private boolean ansiLog
293
-
294
- StandardErrorListener (String mode , boolean ansiLog ) {
295
- this . mode = mode
296
- this . ansiLog = ansiLog
297
- }
298
-
299
- private Ansi ansi () {
300
- final ansi = Ansi . ansi()
301
- ansi. setEnabled(ansiLog)
302
- return ansi
303
- }
304
-
305
- @Override
306
- void beforeAll () {
307
- final line = ansi(). a(" Linting Nextflow code.." ). newline()
308
- AnsiConsole . out. print (line)
309
- AnsiConsole . out. flush()
310
- }
311
-
312
- @Override
313
- void beforeFile (File file ) {
314
- final line = ansi()
315
- .cursorUp(1 ). eraseLine()
316
- .a(Ansi.Attribute . INTENSITY_FAINT ). a(" Linting: ${ file} " )
317
- .reset(). newline(). toString()
318
- AnsiConsole . out. print (line)
319
- AnsiConsole . out. flush()
320
- }
321
-
322
- private Ansi term
323
-
324
- @Override
325
- void beforeErrors () {
326
- term = ansi(). cursorUp(1 ). eraseLine()
327
- }
328
-
329
- @Override
330
- void onError (SyntaxException error , String filename , SourceUnit source ) {
331
- term. bold(). a(filename). reset()
332
- term. a(" :${ error.getStartLine()} :${ error.getStartColumn()} : " )
333
- term = highlightString(error. getOriginalMessage(), term)
334
- if ( mode != ' concise' ) {
335
- term. newline()
336
- term = printCodeBlock(source, Range . of(error), term, Ansi.Color . RED )
337
- }
338
- term. newline()
339
- }
340
-
341
- @Override
342
- void onWarning (WarningMessage warning , String filename , SourceUnit source ) {
343
- final token = warning. getContext(). getRoot()
344
- term. bold(). a(filename). reset()
345
- term. a(" :${ token.getStartLine()} :${ token.getStartColumn()} : " )
346
- term. fg(Ansi.Color . YELLOW ). a(warning. getMessage()). fg(Ansi.Color . DEFAULT )
347
- if ( mode != ' concise' ) {
348
- term. newline()
349
- term = printCodeBlock(source, Range . of(warning), term, Ansi.Color . YELLOW )
350
- }
351
- term. newline()
352
- }
353
-
354
- private Ansi highlightString (String str , Ansi term ) {
355
- final matcher = str =~ / ^(.*)([`'][^`']+[`'])(.*)$/
356
- if ( matcher. find() ) {
357
- term. a(matcher. group(1 ))
358
- .fg(Ansi.Color . CYAN ). a(matcher. group(2 )). fg(Ansi.Color . DEFAULT )
359
- .a(matcher. group(3 ))
360
- }
361
- else {
362
- term. a(str)
363
- }
364
- return term
365
- }
366
-
367
- private Ansi printCodeBlock (SourceUnit source , Range range , Ansi term , Ansi.Color color ) {
368
- final startLine = range. startLine()
369
- final startColumn = range. startColumn()
370
- final endLine = range. endLine()
371
- final endColumn = range. endColumn()
372
- final lines = getSourceText(source)
373
-
374
- // get context window (up to 5 lines)
375
- int padding = mode == ' extended' ? 2 : 0
376
- int fromLine = Math . max(1 , startLine - padding)
377
- int toLine = Math . min(lines. size(), endLine + padding)
378
- if ( toLine - fromLine + 1 > 5 ) {
379
- if ( startLine <= 3 ) {
380
- toLine = fromLine + 4
381
- }
382
- else if ( endLine >= lines. size() - 2 ) {
383
- fromLine = toLine - 4
384
- }
385
- else {
386
- fromLine = startLine - 2
387
- toLine = startLine + 2
388
- }
389
- }
390
-
391
- for ( int i = fromLine; i <= toLine; i++ ) {
392
- String fullLine = lines[i - 1 ]
393
- int start = (i == startLine) ? startColumn - 1 : 0
394
- int end = (i == endLine) ? endColumn - 1 : fullLine. length()
395
-
396
- // Truncate to max 70 characters
397
- int maxLen = 70
398
- int lineLen = fullLine. length()
399
- int windowStart = 0
400
- if ( lineLen > maxLen ) {
401
- if ( start < maxLen - 10 )
402
- windowStart = 0
403
- else if ( end > lineLen - 10 )
404
- windowStart = lineLen - maxLen
405
- else
406
- windowStart = start - 30
407
- }
408
-
409
- String line = fullLine. substring(windowStart, Math . min(lineLen, windowStart + maxLen))
410
- int adjStart = Math . max(0 , start - windowStart)
411
- int adjEnd = Math . max(adjStart + 1 , Math . min(end - windowStart, line. length()))
412
-
413
- // Line number
414
- term. fg(Ansi.Color . BLUE ). a(String . format(" %3d | " , i)). reset()
415
-
416
- if ( i == startLine ) {
417
- // Print line with range highlighted
418
- term. a(Ansi.Attribute . INTENSITY_FAINT ). a(line. substring(0 , adjStart)). reset()
419
- term. fg(color). a(line. substring(adjStart, adjEnd)). reset()
420
- term. a(Ansi.Attribute . INTENSITY_FAINT ). a(line. substring(adjEnd)). reset(). newline()
421
-
422
- // Print carets underneath the range
423
- String marker = ' ' * adjStart
424
- String carets = ' ^' * Math . max(1 , adjEnd - adjStart)
425
- term. a(" | " )
426
- .fg(color). bold(). a(marker + carets). reset(). newline()
427
- }
428
- else {
429
- term. a(Ansi.Attribute . INTENSITY_FAINT ). a(line). reset(). newline()
430
- }
431
- }
432
-
433
- return term
434
- }
435
-
436
- @Memoized
437
- private List<String > getSourceText (SourceUnit source ) {
438
- return source. getSource(). getReader(). readLines()
439
- }
440
-
441
- @Override
442
- void afterErrors () {
443
- // print extra newline since next file status will chomp back one
444
- term. fg(Ansi.Color . DEFAULT ). newline()
445
- AnsiConsole . out. print (term)
446
- AnsiConsole . out. flush()
447
- }
448
-
449
- @Override
450
- void beforeFormat (File file ) {
451
- final line = ansi()
452
- .cursorUp(1 ). eraseLine()
453
- .a(Ansi.Attribute . INTENSITY_FAINT ). a(" Formatting: ${ file} " )
454
- .reset(). newline(). toString()
455
- AnsiConsole . out. print (line)
456
- AnsiConsole . out. flush()
457
- }
458
-
459
- @Override
460
- void afterAll (ErrorSummary summary ) {
461
- final term = ansi()
462
- term. cursorUp(1 ). eraseLine(). cursorUp(1 ). eraseLine()
463
- // print extra newline if no code is being shown
464
- if ( mode == ' concise' )
465
- term. newline()
466
- term. bold(). a(" Nextflow linting complete!" ). reset(). newline()
467
- if ( summary. filesWithErrors > 0 ) {
468
- term. fg(Ansi.Color . RED ). a(" ❌ ${ summary.filesWithErrors} file${ summary.filesWithErrors==1 ? '' : 's'} had ${ summary.errors} error${ summary.errors==1 ? '' : 's'} " ). newline()
469
- }
470
- if ( summary. filesWithoutErrors > 0 ) {
471
- term. fg(Ansi.Color . GREEN ). a(" ✅ ${ summary.filesWithoutErrors} file${ summary.filesWithoutErrors==1 ? '' : 's'} had no errors" )
472
- if ( summary. filesFormatted > 0 )
473
- term. fg(Ansi.Color . BLUE ). a(" (${ summary.filesFormatted} formatted)" )
474
- term. newline()
475
- }
476
- if ( summary. filesWithErrors == 0 && summary. filesWithoutErrors == 0 ) {
477
- term. a(" No files found to process" ). newline()
478
- }
479
- AnsiConsole . out. print (term)
480
- AnsiConsole . out. flush()
481
- }
482
-
483
- private static record Range (
484
- int startLine ,
485
- int startColumn ,
486
- int endLine ,
487
- int endColumn
488
- ) {
489
- public static Range of(SyntaxException error) {
490
- return new Range (
491
- error. getStartLine(),
492
- error. getStartColumn(),
493
- error. getEndLine(),
494
- error. getEndColumn(),
495
- )
496
- }
497
-
498
- public static Range of(WarningMessage warning) {
499
- final token = warning. getContext(). getRoot()
500
- return new Range (
501
- token. getStartLine(),
502
- token. getStartColumn(),
503
- token. getStartLine(),
504
- token. getStartColumn() + token. getText(). length(),
505
- )
506
- }
507
- }
508
- }
509
-
510
-
511
269
@CompileStatic
512
270
class JsonErrorListener implements ErrorListener {
513
271
0 commit comments