///|
// Lexical, deliberately bounded subset of ShellCheck diagnostics. Never executes a shell.
fn substitution_flags(raw : String) -> (Bool, Bool, Bool, Bool) {
  let cs = raw.to_array()
  let mut quote : Char? = None
  let mut backticks = false
  let mut unquoted = false
  let mut double_quoted = false
  let mut single_command = false
  let mut i = 0
  while i < cs.length() {
    let c = cs[i]
    if c == '\\' && quote != Some('\'') {
      i += 2
      continue
    }
    if quote == Some('\'') {
      if c == '\'' {
        quote = None
      }
      i += 1
      continue
    }
    if c == '\'' && quote == None {
      quote = Some(c)
      i += 1
      continue
    }
    if c == '"' {
      quote = if quote == None { Some(c) } else { None }
      i += 1
      continue
    }
    if c == '`' ||
      (
        c == '$' &&
        i + 1 < cs.length() &&
        (cs[i + 1] == '(' || cs[i + 1] == '{')
      ) {
      let opening = if c == '`' { '`' } else { cs[i + 1] }
      let closing = if opening == '(' {
        ')'
      } else if opening == '{' {
        '}'
      } else {
        '`'
      }
      let command = c == '`' ||
        (opening == '(' && !(i + 2 < cs.length() && cs[i + 2] == '('))
      let start = i
      if command {
        backticks = backticks || c == '`'
      }
      i += if c == '`' { 1 } else { 2 }
      let mut depth = 1
      let mut inner : Char? = None
      while i < cs.length() && depth > 0 {
        let n = cs[i]
        if n == '\\' && inner != Some('\'') {
          i += 2
          continue
        }
        if inner is Some(q) {
          if n == q {
            inner = None
          }
        } else if n == '\'' || n == '"' {
          inner = Some(n)
        } else if n == closing {
          depth -= 1
        } else if n == opening && opening != '`' {
          depth += 1
        }
        i += 1
      }
      if command && c != '`' {
        let body = String::from_array(cs[start + 2:i - 1]).trim().to_owned()
        let parts = body.split(" ").filter(s => !s.is_empty()).collect()
        let numeric_seq = parts.length() >= 2 &&
          parts[0] == "seq" &&
          parts[1:]
          .iter()
          .all(s => {
            s.iter().all(n => (n >= '0' && n <= '9') || n == '.' || n == '-')
          })
        unquoted = unquoted || (quote == None && !numeric_seq)
        double_quoted = double_quoted || quote == Some('"')
        single_command = single_command ||
          (start == 0 && i == cs.length()) ||
          (
            start == 1 &&
            quote == Some('"') &&
            i + 1 == cs.length() &&
            cs[i] == '"'
          )
      }
      continue
    }
    i += 1
  }
  (backticks, unquoted, double_quoted, single_command)
}

///|
fn shell_diagnostics(ins : Instruction) -> Array[Diagnostic] {
  let source = run_script(ins)
  if source.is_empty() {
    return []
  }
  let tokens = shell_tokens(source) catch { _ => return [] }
  let commands = shell_commands(source) catch { _ => return [] }
  let out : Array[Diagnostic] = []
  fn emit(code : String, message : String) -> Unit {
    if !out.iter().any(d => d.code == code) {
      out.push({ line: ins.line, code, message, })
    }
  }
  for i, token in tokens {
    if token.kind != "word" {
      continue
    }
    if substitution_flags(token.raw).0 {
      emit("SC2006", "Use $(...) instead of legacy backticks")
    }
    let raw = if token.raw.contains("=") {
      token.raw.split("=").collect().last().unwrap().to_owned()
    } else {
      token.raw
    }
    if raw.has_prefix("\"~/") || raw.has_prefix("'~/") {
      emit("SC2088", "Tilde does not expand inside quotes; use $HOME")
    }
    if i > 0 &&
      tokens[i - 1].kind == "redirect" &&
      substitution_flags(token.raw).1 {
      emit("SC2046", "Quote command substitution to prevent word splitting")
    }
  }
  for command in commands {
    let name = executable_name(command[0].value)
    if command[0].dynamic {
      continue
    }
    let pure_echo = name == "echo" &&
      command.length() == 2 &&
      substitution_flags(command[1].raw).3
    if pure_echo {
      emit(
        "SC2005", "Execute the command directly instead of echoing its output",
      )
    }
    let mut end_options = false
    for i in 1..