package lint import ( "strings" "github.com/vladimir/goherence/internal/parser" ) // SetFactStateRule ловит паттерн `set_fact: x="{{ .x + 1 }}"` — попытку // завести изменяемое состояние (счётчик, накопитель) поверх декларативной // модели, где каждый таск должен описывать конечное состояние, а не шаг // вычисления, зависящий от предыдущего запуска этого же таска. type SetFactStateRule struct{} func (r *SetFactStateRule) Name() string { return "set-fact-self-reference" } func (r *SetFactStateRule) Check(pb *parser.Playbook) []Violation { var out []Violation walkTasks(pb, func(loc string, t *parser.Task) { if t.Module != "set_fact" { return } for varName, val := range t.Args { expr, ok := val.(string) if !ok { continue } if referencesVar(expr, varName) { out = append(out, Violation{ Rule: r.Name(), Severity: Error, Location: loc, Message: "set_fact: " + varName + " ссылается сам на себя — похоже на попытку " + "завести изменяемое состояние между тасками, что нарушает идемпотентность плейбука", }) } } }) return out } // referencesVar — грубая, но достаточная для первой версии проверка: // содержит ли выражение шаблона `.varName` внутри себя же (self-reference). func referencesVar(expr, varName string) bool { return strings.Contains(expr, "."+varName) }