Skip to content

Commit b0191fa

Browse files
Add config error checking to run command (#5998)
--------- Signed-off-by: Ben Sherman <[email protected]> Signed-off-by: Paolo Di Tommaso <[email protected]> Co-authored-by: Paolo Di Tommaso <[email protected]>
1 parent b676e99 commit b0191fa

26 files changed

+848
-734
lines changed

modules/nextflow/src/main/groovy/nextflow/NF.groovy

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ class NF {
3636
return SysEnv.get('NXF_SYNTAX_PARSER', 'v1')
3737
}
3838

39+
static boolean isSyntaxParserV2() {
40+
return getSyntaxParserVersion() == 'v2'
41+
}
42+
3943
static void init() {
4044
NextflowDelegatingMetaClass.provider = PluginExtensionProvider.INSTANCE()
4145
CH.init()

modules/nextflow/src/main/groovy/nextflow/cli/CmdConfig.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ class CmdConfig extends CmdBase {
118118
final config = builder.buildConfigObject()
119119

120120
// -- validate config options
121-
if( NF.getSyntaxParserVersion() == 'v2' ) {
121+
if( NF.isSyntaxParserV2() ) {
122122
Plugins.load(config)
123123
new ConfigValidator().validate(config)
124124
}

modules/nextflow/src/main/groovy/nextflow/cli/CmdLint.groovy

Lines changed: 3 additions & 245 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import com.beust.jcommander.Parameters
2525
import com.beust.jcommander.ParameterException
2626
import groovy.json.JsonOutput
2727
import groovy.transform.CompileStatic
28-
import groovy.transform.Memoized
2928
import groovy.util.logging.Slf4j
3029
import nextflow.config.control.ConfigParser
3130
import nextflow.config.formatter.ConfigFormattingVisitor
@@ -35,13 +34,14 @@ import nextflow.script.control.ParanoidWarning
3534
import nextflow.script.control.ScriptParser
3635
import nextflow.script.formatter.FormattingOptions
3736
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
3840
import nextflow.util.PathUtils
3941
import org.codehaus.groovy.control.SourceUnit
4042
import org.codehaus.groovy.control.messages.SyntaxErrorMessage
4143
import org.codehaus.groovy.control.messages.WarningMessage
4244
import org.codehaus.groovy.syntax.SyntaxException
43-
import org.fusesource.jansi.Ansi
44-
import org.fusesource.jansi.AnsiConsole
4545
/**
4646
* CLI sub-command LINT
4747
*
@@ -266,248 +266,6 @@ class CmdLint extends CmdBase {
266266
}
267267

268268

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-
511269
@CompileStatic
512270
class JsonErrorListener implements ErrorListener {
513271

modules/nextflow/src/main/groovy/nextflow/cli/CmdRun.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ class CmdRun extends CmdBase implements HubOptions {
340340
Plugins.load(cfg)
341341

342342
// -- validate config options
343-
if( NF.getSyntaxParserVersion() == 'v2' )
343+
if( NF.isSyntaxParserV2() )
344344
new ConfigValidator().validate(config)
345345

346346
// -- create a new runner instance

modules/nextflow/src/main/groovy/nextflow/cli/Launcher.groovy

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import groovy.transform.CompileStatic
3030
import groovy.transform.PackageScope
3131
import groovy.util.logging.Slf4j
3232
import nextflow.BuildInfo
33+
import nextflow.NF
3334
import nextflow.exception.AbortOperationException
3435
import nextflow.exception.AbortRunException
3536
import nextflow.exception.ConfigParseException
@@ -535,11 +536,16 @@ class Launcher {
535536
}
536537

537538
catch( ConfigParseException e ) {
538-
def message = e.message
539-
if( e.cause?.message ) {
540-
message += "\n\n${e.cause.message.toString().indent(' ')}"
539+
if( NF.isSyntaxParserV2() ) {
540+
log.error(e.message, e)
541+
}
542+
else {
543+
def message = e.message
544+
if( e.cause?.message ) {
545+
message += "\n\n${e.cause.message.toString().indent(' ')}"
546+
}
547+
log.error(message, e.cause ?: e)
541548
}
542-
log.error(message, e.cause ?: e)
543549
return(1)
544550
}
545551

0 commit comments

Comments
 (0)