use crate::linter::{Diagnostic, LintResult, Severity, Span};
pub fn check(source: &str) -> LintResult {
let mut result = LintResult::new();
for (line_num, line) in source.lines().enumerate() {
let line_num = line_num + 1;
if line_num == 1 {
continue;
}
let trimmed = line.trim_start();
if let Some(after) = trimmed.strip_prefix("#!") {
if after.starts_with("/bin/")
|| after.starts_with("/usr/bin/")
|| after.starts_with("/usr/local/bin/")
|| after.starts_with("/sbin/")
{
let diagnostic = Diagnostic::new(
"SC1128",
Severity::Error,
format!(
"The shebang must be on the first line. Move it from line {} to line 1.",
line_num
),
Span::new(line_num, 1, line_num, line.len() + 1),
);
result.add(diagnostic);
}
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sc1128_shebang_on_line_2() {
let code = "# comment\n#!/bin/bash\necho hello";
let result = check(code);
assert_eq!(result.diagnostics.len(), 1);
assert_eq!(result.diagnostics[0].code, "SC1128");
assert_eq!(result.diagnostics[0].severity, Severity::Error);
assert!(result.diagnostics[0].message.contains("line 2"));
}
#[test]
fn test_sc1128_shebang_on_line_3() {
let code = "# header\n# more header\n#!/usr/bin/env bash\necho hello";
let result = check(code);
assert_eq!(result.diagnostics.len(), 1);
assert!(result.diagnostics[0].message.contains("line 3"));
}
#[test]
fn test_sc1128_shebang_after_code() {
let code = "echo hello\n#!/bin/sh\necho world";
let result = check(code);
assert_eq!(result.diagnostics.len(), 1);
}
#[test]
fn test_sc1128_correct_shebang_ok() {
let code = "#!/bin/bash\n# comment\necho hello";
let result = check(code);
assert_eq!(result.diagnostics.len(), 0);
}
#[test]
fn test_sc1128_no_shebang_ok() {
let code = "echo hello\necho world";
let result = check(code);
assert_eq!(result.diagnostics.len(), 0);
}
#[test]
fn test_sc1128_hash_bang_in_comment_ok() {
let code = "#!/bin/bash\n# Note: #! is the shebang\necho hello";
let result = check(code);
assert_eq!(result.diagnostics.len(), 0);
}
#[test]
fn test_sc1128_hash_bang_in_string_context() {
let code = "#!/bin/bash\necho '#! is special'\necho hello";
let result = check(code);
assert_eq!(result.diagnostics.len(), 0);
}
#[test]
fn test_sc1128_empty_source() {
let result = check("");
assert_eq!(result.diagnostics.len(), 0);
}
#[test]
fn test_sc1128_single_line() {
let code = "#!/bin/bash";
let result = check(code);
assert_eq!(result.diagnostics.len(), 0);
}
#[test]
fn test_sc1128_usr_local_bin() {
let code = "echo hello\n#!/usr/local/bin/bash\necho world";
let result = check(code);
assert_eq!(result.diagnostics.len(), 1);
}
#[test]
fn test_sc1128_multiple_shebangs() {
let code = "#!/bin/bash\n#!/bin/sh\necho hello";
let result = check(code);
assert_eq!(result.diagnostics.len(), 1);
}
}