///|
pub fn lint(text : String) -> Array[String] {
let warnings : Array[String] = []
let mut line_no = 0
let mut current_agents : Array[String] = []
let mut current_rules : Array[String] = []
let mut saw_rule = false
for raw in split_lines(text) {
line_no += 1
let line = trim_ascii(strip_inline_comment(raw))
if line == "" {
current_agents = []
current_rules = []
saw_rule = false
continue
}
let parts = split_once(line, ":")
if parts.length() < 2 {
warnings.push(
"line " + line_no.to_string() + ": ignored malformed directive",
)
continue
}
let key = lower_ascii(trim_ascii(parts[0]))
let value = trim_ascii(parts[1])
if key == "user-agent" {
let agent = lower_ascii(value)
if value == "" {
warnings.push("line " + line_no.to_string() + ": empty user-agent")
}
if saw_rule {
warnings.push(
"line " +
line_no.to_string() +
": user-agent starts a new implicit group",
)
current_agents = []
current_rules = []
saw_rule = false
}
if contains_string(current_agents, agent) {
warnings.push(
"line " + line_no.to_string() + ": duplicate user-agent " + value,
)
}
current_agents.push(agent)
} else if key == "allow" || key == "disallow" {
saw_rule = true
let normalized = if value == "" && key == "disallow" {
""
} else {
normalize_path(value)
}
let signature = key + ":" + normalized
if contains_string(current_rules, signature) {
warnings.push(
"line " + line_no.to_string() + ": duplicate rule " + signature,
)
}
current_rules.push(signature)
if current_agents.is_empty() {
warnings.push(
"line " + line_no.to_string() + ": rule without user-agent",
)
}
} else if key == "crawl-delay" {
saw_rule = true
if parse_non_negative_int(value) is None {
warnings.push("line " + line_no.to_string() + ": invalid crawl-delay")
}
} else if key == "sitemap" || key == "host" {
()
} else {
warnings.push(
"line " + line_no.to_string() + ": unknown directive " + key,
)
}
}
warnings
}
///|
pub fn lint_report(text : String) -> String {
let warnings = lint(text)
if warnings.is_empty() {
"ok"
} else {
join_lines(warnings)
}
}
///|
pub fn is_clean(text : String) -> Bool {
lint(text).is_empty() && parse_errors(text).is_empty()
}
///|
pub fn health_report(text : String) -> String {
let parse = validation_report(text)
let style = lint_report(text)
if parse == "ok" && style == "ok" {
"ok"
} else {
"parse:\n" + parse + "\nlint:\n" + style
}
}