# Strict Syntax Health Report: Common Errors for nf-core Pipeline Upgrades

## Executive Summary

Analysis of 134 nf-core pipelines, 1,764 modules, and 113 subworkflows linted with Nextflow strict syntax (v2 parser). **120 pipelines (90%) have at least one issue**, while modules and subworkflows are essentially clean (1 warning total). The issues cluster into a small number of recurring patterns.

| Scope | Total | With Issues | Clean |
|-------|-------|-------------|-------|
| Pipelines | 134 | 120 (90%) | 14 (10%) |
| Modules | 1,764 | 1 (0.06%) | 1,763 |
| Subworkflows | 113 | 0 | 113 |

Total lint messages across pipelines: **10,931** (2,911 errors + 8,020 warnings).

---

## Part 1: Lint Warnings (by frequency)

### 1. Deprecated: Implicit closure parameter `it` — 2,354 occurrences, 102 pipelines (76%)

The implicit `it` variable in closures is no longer supported. Every closure must declare its parameters explicitly.

```groovy
// Before (deprecated)
ch_input.map { it[0] }

// After
ch_input.map { item -> item[0] }
```

### 2. Deprecated: Uppercase `Channel` — 2,765 occurrences, 94 pipelines (70%)

The `Channel` class reference must be lowercase `channel`.

```groovy
// Before (deprecated)
Channel.fromPath(params.input)

// After
channel.fromPath(params.input)
```

### 3. Unused parameters — 1,516 occurrences, 102 pipelines (76%)

Parameters declared in `nextflow.config` or `params {}` blocks but never referenced in pipeline code.

### 4. Unused variables — 1,355 occurrences, 98 pipelines (73%)

Variables assigned with `def` but never read. Often leftovers from refactoring.

### 5. Deprecated: `shell` block — 4 occurrences, 2 pipelines

The `shell` block is deprecated; use `script` instead with `"""..."""` string interpolation.

### 6. Params declared inside workflow — 14 occurrences, 4 pipelines

`params.x = ...` inside a `workflow {}` block should be moved to config or top-level scope.

### 7. Use of `projectDir` in processes — 10 occurrences, 2 pipelines

Processes should receive files via input channels rather than referencing `projectDir` directly.

---

## Part 2: Lint Errors (by frequency)

### 1. Identifier is not defined — 1,726 occurrences, 81 pipelines (60%)

The single largest error category. Most are **cascade failures** from unparseable modules (see #2 below), but several are genuine missing references:

| Identifier | Occurrences | Pipelines | Cause |
|------------|------------|-----------|-------|
| `check_max` | 336 | 20 | Old nf-core utility function from `lib/` Groovy classes |
| `Utils` / `WorkflowMain` / `NfcoreTemplate` | 173 | 11 | Groovy `lib/` classes not resolved by strict parser |
| `manifest` | 112 | 25 | Direct `manifest.name` access; use `workflow.manifest` instead |
| `validation` | 50 | 25 | nf-schema plugin object not resolved |
| `param` (typo for `params`) | 54 | 11 | Missing `s` in `params` |
| `PIPELINE_INITIALISATION` | 24 | 9 | Workflow name not found (cascading from parse failure) |

### 2. Module could not be parsed — 133 occurrences, 34 pipelines (25%)

When a local module or subworkflow has syntax errors, it fails to parse entirely. Everything imported from it then shows as "is not defined" — creating a cascade effect. **Fixing these 133 root parse errors would eliminate a large proportion of the 1,726 "not defined" errors.**

### 3. Statements mixed with script declarations — 189 occurrences, 33 pipelines (25%)

Top-level variable assignments (e.g. `ch_input = channel.fromPath(...)`) outside of `workflow {}` blocks, mixed with `include`/`process`/`workflow` declarations.

```groovy
// Before (error)
ch_input = channel.fromPath(params.input)
include { FOO } from './modules/foo'
workflow { ... }

// After — move into workflow block
include { FOO } from './modules/foo'
workflow {
    ch_input = channel.fromPath(params.input)
    ...
}
```

### 4. Config: `if` statements mixed with config / `includeConfig` in conditionals — 109 occurrences, 30 pipelines (22%)

Conditional logic in config files is restricted. `if` blocks cannot be mixed with config assignments, and `includeConfig` cannot appear inside conditionals.

```groovy
// Before (error in conf/modules.config)
if (!params.skip_qc) {
    process { withName: 'FASTQC' { ... } }
}

// Before (error in nextflow.config)
if (params.custom_config) {
    includeConfig 'custom.config'
}
```

### 5. Variables in closures not declared with `def` — 64 occurrences, 17 pipelines (13%)

Variables assigned inside `.map{}` or other closures without `def` leak to outer scope.

```groovy
// Before (error)
.map { meta, fastq ->
    num_lanes = fastq.size()  // leaks to outer scope
    [meta + [lanes: num_lanes], fastq]
}

// After
.map { meta, fastq ->
    def num_lanes = fastq.size()
    [meta + [lanes: num_lanes], fastq]
}
```

### 6. `for` loops removed — 31 occurrences, 13 pipelines (10%)

Traditional `for` loops are no longer supported. Use functional alternatives.

```groovy
// Before (error)
for (param in args.keySet().sort()) { ... }

// After
args.keySet().sort().each { param -> ... }
```

### 7. Invalid process directives — 63 occurrences, 13 pipelines (10%)

`if` statements in the directive section of a process (e.g. conditional container selection).

```groovy
// Before (error)
process FOO {
    if (workflow.profile.contains('conda')) {
        conda "bioconda::tool=1.0"
    }
    container "quay.io/tool:1.0"
    ...
}
```

### 8. Config `withName:` / `withLabel:` syntax — 30 occurrences, 12 pipelines (9%)

Old-style `withName:` config selectors that the strict parser rejects.

### 9. Unexpected input `=` — 25 occurrences, 14 pipelines (10%)

Assignment operators in unsupported contexts (named arguments, `while(line=...)` patterns).

### 10. Groovy `import` declarations not supported — 48 occurrences, 8 pipelines (6%)

`import` statements must be replaced with fully-qualified class names.

```groovy
// Before (error)
import org.yaml.snakeyaml.Yaml

// After — use inline
def yaml = new org.yaml.snakeyaml.Yaml()
```

### 11. Variable shadowing (`X` is already declared) — ~170 occurrences, ~40 pipelines

Reusing variable names in nested closures, especially common with `fasta`, `reads`, `meta`, `bam`, `vcf`, `index`.

```groovy
// Before (error) — inner `fasta` shadows outer
fasta.map { meta, fasta -> fasta }

// After — use different name
fasta.map { meta, fa -> fa }
```

### 12. Config `def` variable declarations — 7 occurrences, 6 pipelines (4%)

`def` statements at the top level of config files are not allowed.

```groovy
// Before (error in nextflow.config)
def trace_timestamp = new java.util.Date().format('yyyy-MM-dd_HH-mm-ss')
```

### 13. `_` not allowed as identifier — 8 occurrences, 3 pipelines

The underscore is reserved and cannot be used as a throwaway variable name.

```groovy
// Before (error)
.map { meta, _ -> meta }

// After
.map { meta, _unused -> meta }
```

### 14. Slashy string syntax — 9 occurrences, 9 pipelines

`/pattern/` regex-style strings not supported in strict mode.

---

## Part 3: `--help` Test Failures

20 pipelines fail to print help text with strict syntax, plus 4 more with null results that also error.

### 1. `param 'true' does not exist in JSON schema` — 19 pipelines (79% of failures)

The dominant help failure. When `--help` is passed, the strict parser treats it as `--help=true` (string `"true"`). The nf-schema plugin's `utils_nfschema_plugin` then tries to look up `"true"` as a parameter name in the JSON schema.

```
ERROR ~ Unable to create help message: Specified param 'true' does not exist in JSON schema.
 -- Check script 'subworkflows/nf-core/utils_nfschema_plugin/main.nf' at line: 41
```

**Affected pipelines**: atacseq, createpanelrefs, epitopeprediction, fastquorum, genephylomodeler, gwas, hlatyping, isoseq, longraredisease, lsmquant, methylong, molkart, ncrnannotator, raredisease, ribomsqc, scdownstream, seqsubmit, spatialxe, stableexpression

**Fix**: Update the nf-schema plugin to a version that handles boolean flag parsing correctly under strict syntax.

### 2. Other individual failures (1 pipeline each)

| Pipeline | Issue |
|----------|-------|
| alleleexpression | Old `nf-validation` plugin dumps raw params instead of formatted help |
| demultiplex | Script compilation error in dynamic publish path syntax |
| phaseimpute | `NullPointerException` — `.split()` on null object at runtime |
| references | Help displays but pipeline doesn't exit before validation |
| drop | Timeout (120s) — pipeline hangs during initialization |

---

## Summary: Priority Order for Migration Documentation

| Priority | Issue | Impact | Pipelines |
|----------|-------|--------|-----------|
| **P0** | Implicit `it` in closures → declare explicit params | 2,354 warnings | 102 |
| **P0** | `Channel` → `channel` | 2,765 warnings | 94 |
| **P0** | `--help` broken by nf-schema plugin compat | Blocks help text | 19 |
| **P1** | Cascade parse failures from local modules | 1,726+ errors | 81 |
| **P1** | Statements mixed with declarations → move into `workflow {}` | 189 errors | 33 |
| **P1** | Config conditionals (`if`/`includeConfig`) | 109 errors | 30 |
| **P1** | Groovy `lib/` classes (`check_max`, `WorkflowMain`, etc.) | 336+ errors | 20 |
| **P2** | Undeclared variables in closures (missing `def`) | 64 errors | 17 |
| **P2** | `for` loops → functional alternatives | 31 errors | 13 |
| **P2** | Invalid process directives (conditional containers) | 63 errors | 13 |
| **P2** | Variable shadowing in closures | ~170 errors | ~40 |
| **P3** | `import` statements → fully-qualified names | 48 errors | 8 |
| **P3** | Config `def` declarations | 7 errors | 6 |
| **P3** | Slashy strings, `_` identifier, `=` in wrong context | Misc | <10 each |

The 14 clean pipelines (no issues at all): bamtofastq, demo, demultiplex, drop, magmap, metatdenovo, nanostring, pixelator, proteinannotator, rnavar, seqinspector, spatialvi, variantprioritization, viralmetagenome.
