nginx_lint_parser/
context.rs1use crate::ast::{ConfigItem, Directive};
10
11#[derive(Debug, Clone)]
16pub struct DirectiveWithContext<'a> {
17 pub directive: &'a Directive,
19 pub parent_stack: Vec<String>,
21 pub depth: usize,
23}
24
25impl<'a> DirectiveWithContext<'a> {
26 pub fn parent(&self) -> Option<&str> {
28 self.parent_stack.last().map(|s| s.as_str())
29 }
30
31 pub fn is_inside(&self, parent_name: &str) -> bool {
33 self.parent_stack.iter().any(|p| p == parent_name)
34 }
35
36 pub fn parent_is(&self, parent_name: &str) -> bool {
38 self.parent() == Some(parent_name)
39 }
40
41 pub fn is_at_root(&self) -> bool {
43 self.parent_stack.is_empty()
44 }
45}
46
47pub struct AllDirectivesWithContextIter<'a> {
52 stack: Vec<(std::slice::Iter<'a, ConfigItem>, Option<String>)>,
53 current_parents: Vec<String>,
54}
55
56impl<'a> AllDirectivesWithContextIter<'a> {
57 pub(crate) fn new(items: &'a [ConfigItem], initial_context: Vec<String>) -> Self {
58 Self {
59 stack: vec![(items.iter(), None)],
60 current_parents: initial_context,
61 }
62 }
63}
64
65impl<'a> Iterator for AllDirectivesWithContextIter<'a> {
66 type Item = DirectiveWithContext<'a>;
67
68 fn next(&mut self) -> Option<Self::Item> {
69 while let Some((iter, _)) = self.stack.last_mut() {
70 if let Some(item) = iter.next() {
71 if let ConfigItem::Directive(directive) = item {
72 let context = DirectiveWithContext {
73 directive: directive.as_ref(),
74 parent_stack: self.current_parents.clone(),
75 depth: self.current_parents.len(),
76 };
77
78 if let Some(block) = &directive.block {
79 self.current_parents.push(directive.name.clone());
80 self.stack
81 .push((block.items.iter(), Some(directive.name.clone())));
82 }
83
84 return Some(context);
85 }
86 } else {
87 let (_, parent_name) = self.stack.pop().unwrap();
88 if parent_name.is_some() {
89 self.current_parents.pop();
90 }
91 }
92 }
93 None
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use crate::ast::Config;
100
101 #[test]
102 fn test_all_directives_with_context() {
103 let config =
104 crate::parse_string("http {\n server {\n listen 80;\n }\n}").unwrap();
105
106 let contexts: Vec<_> = config.all_directives_with_context().collect();
107 assert_eq!(contexts.len(), 3);
108
109 assert_eq!(contexts[0].directive.name, "http");
111 assert!(contexts[0].is_at_root());
112 assert_eq!(contexts[0].depth, 0);
113
114 assert_eq!(contexts[1].directive.name, "server");
116 assert!(contexts[1].is_inside("http"));
117 assert!(contexts[1].parent_is("http"));
118 assert_eq!(contexts[1].depth, 1);
119
120 assert_eq!(contexts[2].directive.name, "listen");
122 assert!(contexts[2].is_inside("http"));
123 assert!(contexts[2].is_inside("server"));
124 assert!(contexts[2].parent_is("server"));
125 assert_eq!(contexts[2].depth, 2);
126 }
127
128 #[test]
129 fn test_all_directives_with_context_include_context() {
130 let mut config = crate::parse_string("server {\n listen 80;\n}").unwrap();
131 config.include_context = vec!["http".to_string()];
132
133 let contexts: Vec<_> = config.all_directives_with_context().collect();
134 assert_eq!(contexts.len(), 2);
135
136 assert_eq!(contexts[0].directive.name, "server");
138 assert!(contexts[0].is_inside("http"));
139 assert_eq!(contexts[0].depth, 1);
140
141 assert_eq!(contexts[1].directive.name, "listen");
143 assert!(contexts[1].is_inside("http"));
144 assert!(contexts[1].is_inside("server"));
145 assert_eq!(contexts[1].depth, 2);
146 }
147
148 #[test]
149 fn test_include_context_helpers() {
150 let mut config = Config::new();
151 assert!(!config.is_included_from_http());
152 assert!(!config.is_included_from_stream());
153
154 config.include_context = vec!["http".to_string()];
155 assert!(config.is_included_from_http());
156 assert!(config.is_included_from("http"));
157 assert!(!config.is_included_from_stream());
158 assert_eq!(config.immediate_parent_context(), Some("http"));
159
160 config.include_context = vec!["http".to_string(), "server".to_string()];
161 assert!(config.is_included_from_http());
162 assert!(config.is_included_from_http_server());
163 assert!(!config.is_included_from_http_location());
164
165 config.include_context = vec!["http".to_string(), "location".to_string()];
166 assert!(config.is_included_from_http_location());
167 }
168}