use crate::linter::{Diagnostic, LintResult, Severity, Span};
pub fn check(source: &str) -> LintResult {
let mut result = LintResult::new();
let first_line = match source.lines().next() {
Some(line) => line,
None => return result,
};
let trimmed = first_line.trim_start();
if !trimmed.starts_with('#') || trimmed.starts_with("#!") {
return result;
}
let after_hash = &trimmed[1..];
let after_hash_trimmed = after_hash.trim_start();
if after_hash_trimmed.starts_with("/bin/")
|| after_hash_trimmed.starts_with("/usr/bin/env ")
|| after_hash_trimmed.starts_with("/usr/bin/env\t")
|| after_hash_trimmed.starts_with("/usr/bin/")
|| after_hash_trimmed.starts_with("/sbin/")
{
let diagnostic = Diagnostic::new(
"SC1113",
Severity::Warning,
"Use #! for the shebang, not just #. Missing the ! after #.".to_string(),
Span::new(1, 1, 1, first_line.len() + 1),
);
result.add(diagnostic);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sc1113_missing_bang() {
let code = "# /bin/sh\necho hello";
let result = check(code);
assert_eq!(result.diagnostics.len(), 1);
assert_eq!(result.diagnostics[0].code, "SC1113");
assert_eq!(result.diagnostics[0].severity, Severity::Warning);
}
#[test]
fn test_sc1113_missing_bang_bash() {
let code = "# /bin/bash\necho hello";
let result = check(code);
assert_eq!(result.diagnostics.len(), 1);
}
#[test]
fn test_sc1113_missing_bang_env() {
let code = "# /usr/bin/env bash\necho hello";
let result = check(code);
assert_eq!(result.diagnostics.len(), 1);
}
#[test]
fn test_sc1113_missing_bang_usr_bin() {
let code = "# /usr/bin/bash\necho hello";
let result = check(code);
assert_eq!(result.diagnostics.len(), 1);
}
#[test]
fn test_sc1113_correct_shebang_ok() {
let code = "#!/bin/bash\necho hello";
let result = check(code);
assert_eq!(result.diagnostics.len(), 0);
}
#[test]
fn test_sc1113_regular_comment_ok() {
let code = "# This is a regular comment\necho hello";
let result = check(code);
assert_eq!(result.diagnostics.len(), 0);
}
#[test]
fn test_sc1113_path_in_comment_ok() {
let code = "# See /etc/config for details\necho hello";
let result = check(code);
assert_eq!(result.diagnostics.len(), 0);
}
#[test]
fn test_sc1113_no_shebang_ok() {
let code = "echo hello\necho world";
let result = check(code);
assert_eq!(result.diagnostics.len(), 0);
}
#[test]
fn test_sc1113_empty_source() {
let result = check("");
assert_eq!(result.diagnostics.len(), 0);
}
#[test]
fn test_sc1113_hash_alone() {
let code = "#\necho hello";
let result = check(code);
assert_eq!(result.diagnostics.len(), 0);
}
#[test]
fn test_sc1113_with_leading_spaces() {
let code = " # /bin/sh\necho hello";
let result = check(code);
assert_eq!(result.diagnostics.len(), 1);
}
}