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;
let trimmed = line.trim_start();
if trimmed.starts_with('#') {
continue;
}
check_line(line, line_num, &mut result);
}
result
}
fn check_line(line: &str, line_num: usize, result: &mut LintResult) {
let bytes = line.as_bytes();
let len = bytes.len();
let mut in_double_quote = false;
let mut in_single_quote = false;
let mut i = 0;
while i < len {
if bytes[i] == b'\'' && !in_double_quote {
in_single_quote = !in_single_quote;
i += 1;
continue;
}
if in_single_quote {
i += 1;
continue;
}
if in_double_quote && bytes[i] == b'\\' && i + 1 < len {
i += 2;
continue;
}
if bytes[i] == b'"' {
if in_double_quote {
if i + 1 < len && bytes[i + 1].is_ascii_alphanumeric() {
let diagnostic = Diagnostic::new(
"SC1079",
Severity::Warning,
"This is actually an end quote, but it looks suspicious. Did you mean to concatenate?",
Span::new(line_num, i + 1, line_num, i + 3),
);
result.add(diagnostic);
}
in_double_quote = false;
} else {
in_double_quote = true;
}
}
i += 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sc1079_end_quote_followed_by_alpha() {
let script = r#"echo "hello"world"#;
let result = check(script);
assert_eq!(result.diagnostics.len(), 1);
assert_eq!(result.diagnostics[0].code, "SC1079");
assert_eq!(result.diagnostics[0].severity, Severity::Warning);
}
#[test]
fn test_sc1079_normal_quote() {
let script = r#"echo "hello" world"#;
let result = check(script);
assert_eq!(result.diagnostics.len(), 0);
}
#[test]
fn test_sc1079_end_quote_followed_by_digit() {
let script = r#"echo "test"123"#;
let result = check(script);
assert_eq!(result.diagnostics.len(), 1);
}
#[test]
fn test_sc1079_comment_skipped() {
let script = r#"# "hello"world"#;
let result = check(script);
assert_eq!(result.diagnostics.len(), 0);
}
#[test]
fn test_sc1079_end_quote_followed_by_space() {
let script = r#"echo "hello" "world""#;
let result = check(script);
assert_eq!(result.diagnostics.len(), 0);
}
}