{"record":{"id":"a1d9de524a35cff3","repo":"gastownhall/beads","slug":"empty-range-expression","errorCode":null,"errorMessage":"empty range expression","messagePattern":"empty range expression","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/formula/range.go","lineNumber":45,"sourceCode":"\n// rangePattern matches \"start..end\" format.\nvar rangePattern = regexp.MustCompile(`^(.+)\\.\\.(.+)$`)\n\n// rangeVarPattern matches {varname} placeholders in range expressions.\nvar rangeVarPattern = regexp.MustCompile(`\\{(\\w+)\\}`)\n\n// ParseRange parses a range expression and evaluates it using the given variables.\n// Returns the start and end values of the range.\n//\n// Examples:\n//\n//\tParseRange(\"1..10\", nil)           -> {1, 10}\n//\tParseRange(\"1..2^3\", nil)          -> {1, 8}\n//\tParseRange(\"1..2^{n}\", {\"n\":\"3\"})  -> {1, 8}\nfunc ParseRange(expr string, vars map[string]string) (*RangeSpec, error) {\n\texpr = strings.TrimSpace(expr)\n\tif expr == \"\" {\n\t\treturn nil, fmt.Errorf(\"empty range expression\")\n\t}\n\n\t// Parse start..end format\n\tm := rangePattern.FindStringSubmatch(expr)\n\tif m == nil {\n\t\treturn nil, fmt.Errorf(\"invalid range format %q: expected start..end\", expr)\n\t}\n\n\tstartExpr := strings.TrimSpace(m[1])\n\tendExpr := strings.TrimSpace(m[2])\n\n\t// Evaluate start expression\n\tstart, err := EvaluateExpr(startExpr, vars)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"evaluating range start %q: %w\", startExpr, err)\n\t}\n\n\t// Evaluate end expression","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/formula/range.go#L27-L63","documentation":"ParseRange() trims the expression and rejects an empty string outright, because there is no start..end to parse. The range expression (e.g. inside a loop operator) ended up empty after whitespace trimming.","triggerScenarios":"Calling ParseRange(\"\", vars) or ParseRange(\"   \", vars); a loop expansion where the range operand is an unbound or empty template variable (e.g. 'for i in {{range}}' with range unset), so expandLoopWithVars receives an empty expression.","commonSituations":"A loop variable in a molecule left blank or undefined; frontmatter key typo so the intended range value is never picked up; whitespace-only value that looks set but trims to nothing.","solutions":["Provide a concrete range expression like \"1..10\" where the loop's range operand is defined.","Check that the variable feeding the range expression is defined in the vars map / frontmatter.","Add a guard that skips the loop when the range operand is empty instead of parsing it."],"exampleFix":"# before: molecule frontmatter\nloop:\n  range: \"\"   # empty\n\n# after\nloop:\n  range: \"1..5\"","handlingStrategy":"validation","validationCode":"expr := strings.TrimSpace(rawRange)\nif expr == \"\" {\n\t// skip loop or use a default\n\trawRange = \"1..1\"\n}","typeGuard":null,"tryCatchPattern":"spec, err := ParseRange(expr, vars)\nif err != nil {\n\tif strings.Contains(err.Error(), \"empty range expression\") {\n\t\t// skip iteration set gracefully\n\t}\n}","preventionTips":["Never leave loop range fields blank; comment them out or set an explicit default.","Check that template vars feeding ranges are defined before expansion.","Trim user-supplied range input and treat whitespace-only as unset."],"tags":["go","range","parsing","loop"],"backgroundTag":"empty-range-expression","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}