use std::collections::{HashMap, HashSet};
use crate::linter::{Fix, LintError, Severity, compute_line_starts};
#[derive(Debug, Clone)]
pub struct IgnoreWarning {
pub line: usize,
pub message: String,
pub fixes: Vec<Fix>,
}
#[derive(Debug, Clone)]
struct IgnoreDirective {
comment_line: usize,
target_line: usize,
rule_name: String,
used: bool,
fix_start_offset: usize,
fix_end_offset: usize,
fix_replacement: String,
}
#[derive(Debug, Default)]
pub struct IgnoreTracker {
ignored_lines: HashMap<usize, HashSet<String>>,
directives: Vec<IgnoreDirective>,
dormant_rules: HashSet<String>,
}
impl IgnoreTracker {
pub fn new() -> Self {
Self::default()
}
pub fn is_ignored(&self, rule: &str, line: usize) -> bool {
self.ignored_lines
.get(&line)
.map(|rules| rules.contains(rule))
.unwrap_or(false)
}
pub fn from_content(content: &str) -> (Self, Vec<IgnoreWarning>) {
Self::from_content_with_rules(content, None)
}
pub fn from_content_with_rules(
content: &str,
valid_rules: Option<&HashSet<String>>,
) -> (Self, Vec<IgnoreWarning>) {
let mut tracker = Self::new();
let mut warnings = Vec::new();
let line_starts = compute_line_starts(content);
let lines: Vec<&str> = content.lines().collect();
let mut parsed_comments: Vec<(usize, Result<ParsedIgnoreComment, IgnoreWarning>)> =
Vec::new();
for (line_idx, line) in lines.iter().enumerate() {
let line_number = line_idx + 1; if let Some(result) = parse_ignore_comment(line, line_number) {
parsed_comments.push((line_idx, result));
}
}
for i in 0..parsed_comments.len() {
let (line_idx, ref result) = parsed_comments[i];
match result {
Ok(parsed) if !parsed.is_inline => {
let mut target_idx = line_idx + 1;
while target_idx < lines.len() {
let is_ignore_comment = parsed_comments.iter().any(|(idx, r)| {
*idx == target_idx && matches!(r, Ok(p) if !p.is_inline)
});
if !is_ignore_comment {
break;
}
target_idx += 1;
}
let actual_target_line = target_idx + 1;
if let Some(valid) = valid_rules
&& !valid.contains(&parsed.rule_name)
{
warnings.push(IgnoreWarning {
line: parsed.comment_line,
message: format!(
"unknown rule '{}' in nginx-lint:ignore comment",
parsed.rule_name
),
fixes: Vec::new(),
});
}
tracker
.ignored_lines
.entry(actual_target_line)
.or_default()
.insert(parsed.rule_name.clone());
let comment_idx = parsed.comment_line - 1;
let num_lines = line_starts.len() - 1;
let (fix_start, fix_end) = if comment_idx + 1 < num_lines {
(line_starts[comment_idx], line_starts[comment_idx + 1])
} else if line_starts[comment_idx] > 0 {
(line_starts[comment_idx] - 1, line_starts[comment_idx + 1])
} else {
(line_starts[comment_idx], line_starts[comment_idx + 1])
};
tracker.directives.push(IgnoreDirective {
comment_line: parsed.comment_line,
target_line: actual_target_line,
rule_name: parsed.rule_name.clone(),
used: false,
fix_start_offset: fix_start,
fix_end_offset: fix_end,
fix_replacement: String::new(),
});
}
Ok(parsed) => {
if let Some(valid) = valid_rules
&& !valid.contains(&parsed.rule_name)
{
warnings.push(IgnoreWarning {
line: parsed.comment_line,
message: format!(
"unknown rule '{}' in nginx-lint:ignore comment",
parsed.rule_name
),
fixes: Vec::new(),
});
}
tracker
.ignored_lines
.entry(parsed.target_line)
.or_default()
.insert(parsed.rule_name.clone());
let comment_idx = parsed.comment_line - 1;
let line_start = line_starts[comment_idx];
let next_line_start = line_starts[comment_idx + 1];
let line_end = if next_line_start > line_start
&& content.as_bytes().get(next_line_start - 1) == Some(&b'\n')
{
next_line_start - 1
} else {
next_line_start
};
let replacement = parsed.content_before_comment.clone().unwrap_or_default();
tracker.directives.push(IgnoreDirective {
comment_line: parsed.comment_line,
target_line: parsed.target_line,
rule_name: parsed.rule_name.clone(),
used: false,
fix_start_offset: line_start,
fix_end_offset: line_end,
fix_replacement: replacement,
});
}
Err(warning) => {
warnings.push(warning.clone());
}
}
}
(tracker, warnings)
}
fn mark_used(&mut self, rule: &str, line: usize) {
for directive in &mut self.directives {
if directive.target_line == line && directive.rule_name == rule {
directive.used = true;
}
}
}
pub fn set_dormant_rules(&mut self, dormant: &HashSet<String>) {
self.dormant_rules = dormant.clone();
}
pub fn unused_warnings(&self) -> Vec<IgnoreWarning> {
self.directives
.iter()
.filter(|d| !d.used && !self.dormant_rules.contains(&d.rule_name))
.map(|d| {
let fix =
Fix::replace_range(d.fix_start_offset, d.fix_end_offset, &d.fix_replacement);
IgnoreWarning {
line: d.comment_line,
message: format!(
"unused nginx-lint:ignore comment for rule '{}'",
d.rule_name
),
fixes: vec![fix],
}
})
.collect()
}
#[cfg(test)]
pub fn add_ignore(&mut self, rule: &str, line: usize) {
self.ignored_lines
.entry(line)
.or_default()
.insert(rule.to_string());
self.directives.push(IgnoreDirective {
comment_line: line.saturating_sub(1).max(1),
target_line: line,
rule_name: rule.to_string(),
used: false,
fix_start_offset: 0,
fix_end_offset: 0,
fix_replacement: String::new(),
});
}
}
#[derive(Debug)]
struct ParsedIgnoreComment {
rule_name: String,
target_line: usize,
comment_line: usize,
is_inline: bool,
content_before_comment: Option<String>,
}
fn parse_ignore_comment(
line: &str,
line_number: usize,
) -> Option<Result<ParsedIgnoreComment, IgnoreWarning>> {
const IGNORE_PREFIX: &str = "nginx-lint:ignore";
let comment_start = line.find('#')?;
let comment_part = &line[comment_start..];
let comment = comment_part.trim_start_matches('#').trim();
let rest = comment.strip_prefix(IGNORE_PREFIX)?;
let rest = rest.trim();
let before_comment_trimmed = line[..comment_start].trim();
let is_inline = !before_comment_trimmed.is_empty();
let parts: Vec<&str> = rest.splitn(2, |c: char| c.is_whitespace()).collect();
if parts.is_empty() || parts[0].is_empty() {
return Some(Err(IgnoreWarning {
line: line_number,
message: "nginx-lint:ignore requires a rule name".to_string(),
fixes: Vec::new(),
}));
}
let rule_name = parts[0].to_string();
if parts.len() < 2 || parts[1].trim().is_empty() {
return Some(Err(IgnoreWarning {
line: line_number,
message: format!("nginx-lint:ignore {} requires a reason", rule_name),
fixes: Vec::new(),
}));
}
let target_line = if is_inline {
line_number
} else {
line_number + 1
};
let content_before = if is_inline {
Some(line[..comment_start].trim_end().to_string())
} else {
None
};
Some(Ok(ParsedIgnoreComment {
rule_name,
target_line,
comment_line: line_number,
is_inline,
content_before_comment: content_before,
}))
}
#[derive(Debug)]
pub struct FilterResult {
pub errors: Vec<LintError>,
pub ignored_count: usize,
pub unused_warnings: Vec<IgnoreWarning>,
}
pub fn filter_errors(errors: Vec<LintError>, tracker: &mut IgnoreTracker) -> FilterResult {
let mut remaining = Vec::new();
let mut ignored_count = 0;
for error in errors {
if let Some(line) = error.line
&& tracker.is_ignored(&error.rule, line)
{
tracker.mark_used(&error.rule, line);
ignored_count += 1;
continue;
}
remaining.push(error);
}
let unused_warnings = tracker.unused_warnings();
FilterResult {
errors: remaining,
ignored_count,
unused_warnings,
}
}
pub fn warnings_to_errors(warnings: Vec<IgnoreWarning>) -> Vec<LintError> {
warnings
.into_iter()
.map(|warning| {
let mut error = LintError::new(
"invalid-nginx-lint-ignore",
"ignore",
&warning.message,
Severity::Warning,
)
.with_location(warning.line, 1);
for fix in warning.fixes {
error = error.with_fix(fix);
}
error
})
.collect()
}
const CONTEXT_PREFIX: &str = "nginx-lint:context";
pub fn parse_context_comment(content: &str) -> Option<Vec<String>> {
for line in content.lines().take(10) {
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
if !trimmed.starts_with('#') {
break;
}
let comment = trimmed.trim_start_matches('#').trim();
if let Some(rest) = comment.strip_prefix(CONTEXT_PREFIX) {
let context_str = rest.trim();
if context_str.is_empty() {
return None;
}
let context: Vec<String> = context_str
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
if context.is_empty() {
return None;
}
return Some(context);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_valid_ignore_comment() {
let result = parse_ignore_comment(
"# nginx-lint:ignore server-tokens-enabled for dev environment",
5,
);
assert!(result.is_some());
let parsed = result.unwrap().unwrap();
assert_eq!(parsed.rule_name, "server-tokens-enabled");
assert_eq!(parsed.target_line, 6); assert_eq!(parsed.comment_line, 5);
assert!(!parsed.is_inline);
assert!(parsed.content_before_comment.is_none());
}
#[test]
fn test_parse_ignore_comment_with_japanese_reason() {
let result =
parse_ignore_comment("# nginx-lint:ignore server-tokens-enabled 開発環境用", 5);
assert!(result.is_some());
let parsed = result.unwrap().unwrap();
assert_eq!(parsed.rule_name, "server-tokens-enabled");
assert_eq!(parsed.target_line, 6);
assert_eq!(parsed.comment_line, 5);
assert!(!parsed.is_inline);
assert!(parsed.content_before_comment.is_none());
}
#[test]
fn test_parse_missing_rule_name() {
let result = parse_ignore_comment("# nginx-lint:ignore", 5);
assert!(result.is_some());
let warning = result.unwrap().unwrap_err();
assert_eq!(warning.line, 5);
assert!(
warning
.message
.contains("nginx-lint:ignore requires a rule name")
);
}
#[test]
fn test_parse_missing_reason() {
let result = parse_ignore_comment("# nginx-lint:ignore server-tokens-enabled", 5);
assert!(result.is_some());
let warning = result.unwrap().unwrap_err();
assert_eq!(warning.line, 5);
assert!(
warning
.message
.contains("nginx-lint:ignore server-tokens-enabled requires a reason")
);
}
#[test]
fn test_parse_not_a_comment() {
let result = parse_ignore_comment("server_tokens on;", 5);
assert!(result.is_none());
}
#[test]
fn test_parse_regular_comment() {
let result = parse_ignore_comment("# This is a regular comment", 5);
assert!(result.is_none());
}
#[test]
fn test_ignore_tracker_is_ignored() {
let mut tracker = IgnoreTracker::new();
tracker.add_ignore("server-tokens-enabled", 10);
assert!(tracker.is_ignored("server-tokens-enabled", 10));
assert!(!tracker.is_ignored("server-tokens-enabled", 11));
assert!(!tracker.is_ignored("other-rule", 10));
}
#[test]
fn test_ignore_tracker_from_content() {
let content = r#"
# nginx-lint:ignore server-tokens-enabled dev environment
server_tokens on;
"#;
let (tracker, warnings) = IgnoreTracker::from_content(content);
assert!(warnings.is_empty());
assert!(tracker.is_ignored("server-tokens-enabled", 3));
assert!(!tracker.is_ignored("server-tokens-enabled", 2));
}
#[test]
fn test_ignore_tracker_from_content_with_warnings() {
let content = r#"
# nginx-lint:ignore
server_tokens on;
"#;
let (_, warnings) = IgnoreTracker::from_content(content);
assert_eq!(warnings.len(), 1);
assert!(warnings[0].message.contains("requires a rule name"));
}
#[test]
fn test_filter_errors() {
let mut tracker = IgnoreTracker::new();
tracker.add_ignore("server-tokens-enabled", 5);
let errors = vec![
LintError::new(
"server-tokens-enabled",
"security",
"test error",
Severity::Warning,
)
.with_location(5, 1),
LintError::new(
"server-tokens-enabled",
"security",
"test error",
Severity::Warning,
)
.with_location(6, 1),
LintError::new("other-rule", "security", "test error", Severity::Warning)
.with_location(5, 1),
];
let result = filter_errors(errors, &mut tracker);
assert_eq!(result.errors.len(), 2);
assert_eq!(result.ignored_count, 1);
assert!(
result
.errors
.iter()
.all(|e| !(e.rule == "server-tokens-enabled" && e.line == Some(5)))
);
assert!(result.unused_warnings.is_empty());
}
#[test]
fn test_filter_errors_without_line_info() {
let mut tracker = IgnoreTracker::new();
tracker.add_ignore("some-rule", 5);
let errors = vec![LintError::new(
"some-rule",
"test",
"error without line",
Severity::Warning,
)];
let result = filter_errors(errors, &mut tracker);
assert_eq!(result.errors.len(), 1); assert_eq!(result.ignored_count, 0);
assert_eq!(result.unused_warnings.len(), 1);
}
#[test]
fn test_only_affects_next_line() {
let content = r#"
# nginx-lint:ignore server-tokens-enabled reason
server_tokens on;
server_tokens on;
"#;
let (tracker, warnings) = IgnoreTracker::from_content(content);
assert!(warnings.is_empty());
assert!(tracker.is_ignored("server-tokens-enabled", 3)); assert!(!tracker.is_ignored("server-tokens-enabled", 4)); }
#[test]
fn test_consecutive_ignore_comments() {
let content = r#"
# nginx-lint:ignore server-tokens-enabled reason1
# nginx-lint:ignore autoindex-enabled reason2
server_tokens on;
"#;
let (tracker, warnings) = IgnoreTracker::from_content(content);
assert!(warnings.is_empty());
assert!(tracker.is_ignored("server-tokens-enabled", 4));
assert!(tracker.is_ignored("autoindex-enabled", 4));
assert!(!tracker.is_ignored("server-tokens-enabled", 2));
assert!(!tracker.is_ignored("autoindex-enabled", 3));
}
#[test]
fn test_three_consecutive_ignore_comments() {
let content = r#"
# nginx-lint:ignore server-tokens-enabled reason1
# nginx-lint:ignore autoindex-enabled reason2
# nginx-lint:ignore gzip-not-enabled reason3
server_tokens on;
"#;
let (tracker, warnings) = IgnoreTracker::from_content(content);
assert!(warnings.is_empty());
assert!(tracker.is_ignored("server-tokens-enabled", 5));
assert!(tracker.is_ignored("autoindex-enabled", 5));
assert!(tracker.is_ignored("gzip-not-enabled", 5));
}
#[test]
fn test_warnings_to_errors() {
let warnings = vec![IgnoreWarning {
line: 5,
message: "test warning".to_string(),
fixes: Vec::new(),
}];
let errors = warnings_to_errors(warnings);
assert_eq!(errors.len(), 1);
assert_eq!(errors[0].rule, "invalid-nginx-lint-ignore");
assert_eq!(errors[0].category, "ignore");
assert_eq!(errors[0].message, "test warning");
assert_eq!(errors[0].severity, Severity::Warning);
assert_eq!(errors[0].line, Some(5));
assert!(errors[0].fixes.is_empty());
}
#[test]
fn test_warnings_to_errors_with_fix() {
let warnings = vec![IgnoreWarning {
line: 5,
message: "test warning".to_string(),
fixes: vec![Fix::replace_range(10, 50, "")],
}];
let errors = warnings_to_errors(warnings);
assert_eq!(errors.len(), 1);
assert!(!errors[0].fixes.is_empty());
let fix = &errors[0].fixes[0];
assert!(fix.is_range_based());
assert_eq!(fix.start_offset, Some(10));
assert_eq!(fix.end_offset, Some(50));
assert_eq!(fix.new_text, "");
}
#[test]
fn test_parse_inline_comment() {
let result = parse_ignore_comment(
"server_tokens on; # nginx-lint:ignore server-tokens-enabled dev environment",
5,
);
assert!(result.is_some());
let parsed = result.unwrap().unwrap();
assert_eq!(parsed.rule_name, "server-tokens-enabled");
assert_eq!(parsed.target_line, 5); assert_eq!(parsed.comment_line, 5);
assert!(parsed.is_inline);
assert_eq!(
parsed.content_before_comment,
Some("server_tokens on;".to_string())
);
}
#[test]
fn test_parse_inline_comment_with_japanese_reason() {
let result = parse_ignore_comment(
"server_tokens on; # nginx-lint:ignore server-tokens-enabled 開発環境用",
5,
);
assert!(result.is_some());
let parsed = result.unwrap().unwrap();
assert_eq!(parsed.rule_name, "server-tokens-enabled");
assert_eq!(parsed.target_line, 5); assert_eq!(parsed.comment_line, 5);
assert!(parsed.is_inline);
assert_eq!(
parsed.content_before_comment,
Some("server_tokens on;".to_string())
);
}
#[test]
fn test_inline_comment_missing_reason() {
let result = parse_ignore_comment(
"server_tokens on; # nginx-lint:ignore server-tokens-enabled",
5,
);
assert!(result.is_some());
let warning = result.unwrap().unwrap_err();
assert_eq!(warning.line, 5);
assert!(warning.message.contains("requires a reason"));
}
#[test]
fn test_ignore_tracker_inline_comment() {
let content = r#"
server_tokens on; # nginx-lint:ignore server-tokens-enabled dev environment
"#;
let (tracker, warnings) = IgnoreTracker::from_content(content);
assert!(warnings.is_empty());
assert!(tracker.is_ignored("server-tokens-enabled", 2)); assert!(!tracker.is_ignored("server-tokens-enabled", 3));
}
#[test]
fn test_both_comment_styles() {
let content = r#"
# nginx-lint:ignore server-tokens-enabled reason for next line
server_tokens on;
autoindex on; # nginx-lint:ignore autoindex-enabled reason for this line
"#;
let (tracker, warnings) = IgnoreTracker::from_content(content);
assert!(warnings.is_empty());
assert!(tracker.is_ignored("server-tokens-enabled", 3));
assert!(tracker.is_ignored("autoindex-enabled", 4));
}
#[test]
fn test_unknown_rule_name() {
let content = r#"
# nginx-lint:ignore unknown-rule-name some reason
server_tokens on;
"#;
let valid_rules: HashSet<String> = ["server-tokens-enabled", "autoindex-enabled"]
.iter()
.map(|s| s.to_string())
.collect();
let (_, warnings) = IgnoreTracker::from_content_with_rules(content, Some(&valid_rules));
assert_eq!(warnings.len(), 1);
assert!(
warnings[0]
.message
.contains("unknown rule 'unknown-rule-name'")
);
}
#[test]
fn test_unused_ignore_directive() {
let content = r#"
# nginx-lint:ignore server-tokens-enabled reason
server_tokens off;
"#;
let (mut tracker, _) = IgnoreTracker::from_content(content);
let errors: Vec<LintError> = vec![];
let result = filter_errors(errors, &mut tracker);
assert_eq!(result.unused_warnings.len(), 1);
assert!(
result.unused_warnings[0]
.message
.contains("unused nginx-lint:ignore")
);
assert!(
result.unused_warnings[0]
.message
.contains("server-tokens-enabled")
);
}
#[test]
fn test_dormant_rule_suppresses_unused_warning() {
let content = r#"
# nginx-lint:ignore server-tokens-enabled reason
server_tokens off;
"#;
let (mut tracker, _) = IgnoreTracker::from_content(content);
let mut dormant = HashSet::new();
dormant.insert("server-tokens-enabled".to_string());
tracker.set_dormant_rules(&dormant);
let result = filter_errors(Vec::<LintError>::new(), &mut tracker);
assert!(
result.unused_warnings.is_empty(),
"dormant rule should suppress unused-ignore warning, got: {:?}",
result
.unused_warnings
.iter()
.map(|w| &w.message)
.collect::<Vec<_>>()
);
}
#[test]
fn test_dormant_rule_does_not_affect_other_rules() {
let content = r#"
# nginx-lint:ignore server-tokens-enabled reason
# nginx-lint:ignore autoindex-enabled reason
server_tokens off;
"#;
let (mut tracker, _) = IgnoreTracker::from_content(content);
let mut dormant = HashSet::new();
dormant.insert("server-tokens-enabled".to_string());
tracker.set_dormant_rules(&dormant);
let result = filter_errors(Vec::<LintError>::new(), &mut tracker);
assert_eq!(result.unused_warnings.len(), 1);
assert!(
result.unused_warnings[0]
.message
.contains("autoindex-enabled")
);
}
#[test]
fn test_used_ignore_directive_no_warning() {
let content = r#"
# nginx-lint:ignore server-tokens-enabled reason
server_tokens on;
"#;
let (mut tracker, _) = IgnoreTracker::from_content(content);
let errors = vec![
LintError::new(
"server-tokens-enabled",
"security",
"test error",
Severity::Warning,
)
.with_location(3, 1),
];
let result = filter_errors(errors, &mut tracker);
assert!(result.unused_warnings.is_empty());
assert_eq!(result.ignored_count, 1);
}
#[test]
fn test_unused_comment_only_line_fix() {
let content = "\n# nginx-lint:ignore server-tokens-enabled reason\nserver_tokens off;\n";
let (mut tracker, _) = IgnoreTracker::from_content(content);
let errors: Vec<LintError> = vec![];
let result = filter_errors(errors, &mut tracker);
assert_eq!(result.unused_warnings.len(), 1);
let fix = &result.unused_warnings[0].fixes[0];
assert!(fix.is_range_based());
assert_eq!(fix.new_text, "");
let mut fixed = content.to_string();
fixed.replace_range(
fix.start_offset.unwrap()..fix.end_offset.unwrap(),
&fix.new_text,
);
assert_eq!(fixed, "\nserver_tokens off;\n");
}
#[test]
fn test_unused_inline_comment_fix() {
let content = "\nserver_tokens off; # nginx-lint:ignore server-tokens-enabled reason\n";
let (mut tracker, _) = IgnoreTracker::from_content(content);
let errors: Vec<LintError> = vec![];
let result = filter_errors(errors, &mut tracker);
assert_eq!(result.unused_warnings.len(), 1);
let fix = &result.unused_warnings[0].fixes[0];
assert!(fix.is_range_based());
assert_eq!(fix.new_text, "server_tokens off;");
let mut fixed = content.to_string();
fixed.replace_range(
fix.start_offset.unwrap()..fix.end_offset.unwrap(),
&fix.new_text,
);
assert_eq!(fixed, "\nserver_tokens off;\n");
}
#[test]
fn test_unused_comment_on_first_line_fix() {
let content = "# nginx-lint:ignore server-tokens-enabled reason\nserver_tokens off;\n";
let (mut tracker, _) = IgnoreTracker::from_content(content);
let errors: Vec<LintError> = vec![];
let result = filter_errors(errors, &mut tracker);
assert_eq!(result.unused_warnings.len(), 1);
let fix = &result.unused_warnings[0].fixes[0];
assert!(fix.is_range_based());
let mut fixed = content.to_string();
fixed.replace_range(
fix.start_offset.unwrap()..fix.end_offset.unwrap(),
&fix.new_text,
);
assert_eq!(fixed, "server_tokens off;\n");
}
#[test]
fn test_unused_comment_on_last_line_no_trailing_newline_fix() {
let content = "server_tokens off;\n# nginx-lint:ignore server-tokens-enabled reason";
let (mut tracker, _) = IgnoreTracker::from_content(content);
let errors: Vec<LintError> = vec![];
let result = filter_errors(errors, &mut tracker);
assert_eq!(result.unused_warnings.len(), 1);
let fix = &result.unused_warnings[0].fixes[0];
assert!(fix.is_range_based());
let mut fixed = content.to_string();
fixed.replace_range(
fix.start_offset.unwrap()..fix.end_offset.unwrap(),
&fix.new_text,
);
assert_eq!(fixed, "server_tokens off;");
}
#[test]
fn test_parse_context_comment_simple() {
let content = "# nginx-lint:context http\nserver { listen 80; }";
let context = parse_context_comment(content);
assert_eq!(context, Some(vec!["http".to_string()]));
}
#[test]
fn test_parse_context_comment_multiple() {
let content = "# nginx-lint:context http,server\nlocation / { }";
let context = parse_context_comment(content);
assert_eq!(
context,
Some(vec!["http".to_string(), "server".to_string()])
);
}
#[test]
fn test_parse_context_comment_with_spaces() {
let content = "# nginx-lint:context http, server\nlocation / { }";
let context = parse_context_comment(content);
assert_eq!(
context,
Some(vec!["http".to_string(), "server".to_string()])
);
}
#[test]
fn test_parse_context_comment_after_empty_lines() {
let content = "\n\n# nginx-lint:context http\nserver { }";
let context = parse_context_comment(content);
assert_eq!(context, Some(vec!["http".to_string()]));
}
#[test]
fn test_parse_context_comment_after_other_comments() {
let content = "# Some description\n# nginx-lint:context http\nserver { }";
let context = parse_context_comment(content);
assert_eq!(context, Some(vec!["http".to_string()]));
}
#[test]
fn test_parse_context_comment_not_found() {
let content = "server { listen 80; }";
let context = parse_context_comment(content);
assert_eq!(context, None);
}
#[test]
fn test_parse_context_comment_after_directive() {
let content = "server { }\n# nginx-lint:context http";
let context = parse_context_comment(content);
assert_eq!(context, None);
}
#[test]
fn test_parse_context_comment_empty_value() {
let content = "# nginx-lint:context\nserver { }";
let context = parse_context_comment(content);
assert_eq!(context, None);
}
}