Recursively check all steps in a workflow or nested block.
(
self,
steps: list,
errors: list[str],
warnings: list[str],
defined_vars: set[str],
path: str,
)
| 153 | # ── Per-step-type semantic checks ──────────────────────────────── |
| 154 | |
| 155 | def _check_steps( |
| 156 | self, |
| 157 | steps: list, |
| 158 | errors: list[str], |
| 159 | warnings: list[str], |
| 160 | defined_vars: set[str], |
| 161 | path: str, |
| 162 | ) -> None: |
| 163 | """Recursively check all steps in a workflow or nested block.""" |
| 164 | if not isinstance(steps, list): |
| 165 | errors.append( |
| 166 | f"{path}: expected a list of steps, got {type(steps).__name__}" |
| 167 | ) |
| 168 | return |
| 169 | |
| 170 | for i, step in enumerate(steps): |
| 171 | if not isinstance(step, dict): |
| 172 | errors.append( |
| 173 | f"{path}[{i}]: step must be a dict, got {type(step).__name__}" |
| 174 | ) |
| 175 | continue |
| 176 | |
| 177 | # Identify step type |
| 178 | step_type = None |
| 179 | for key in step: |
| 180 | if key in _STEP_TYPES: |
| 181 | step_type = key |
| 182 | break |
| 183 | |
| 184 | step_path = f"{path}[{i}]" |
| 185 | if step_type is None: |
| 186 | known_keys = list(step.keys()) |
| 187 | errors.append( |
| 188 | f"{step_path}: unrecognized step type '{known_keys[0] if known_keys else '?'}'. " |
| 189 | f"Valid types: {', '.join(sorted(_STEP_TYPES))}" |
| 190 | ) |
| 191 | continue |
| 192 | |
| 193 | body = step[step_type] |
| 194 | |
| 195 | # Dispatch to type-specific checker |
| 196 | checker = getattr(self, f"_check_{step_type}", None) |
| 197 | if checker: |
| 198 | checker(body, errors, warnings, defined_vars, step_path) |
| 199 | |
| 200 | def _check_step(self, body, errors, warnings, defined_vars, path): |
| 201 | """Check 'step' type — stateful, requires instruction.""" |
no outgoing calls
no test coverage detected