use crate::ast::{Argument, ArgumentValue, Comment, Config, ConfigItem, Directive};
wit_bindgen::generate!({
path: "../../wit/nginx-lint-plugin.wit",
world: "parser",
pub_export_macro: true,
});
struct ParserComponent;
impl Guest for ParserComponent {
fn parse_config(
source: String,
include_context: Vec<String>,
) -> Result<nginx_lint::plugin::parser_types::ParseOutput, String> {
let mut config = crate::parse_string(&source).map_err(|e| e.to_string())?;
config.include_context = include_context;
Ok(build_parse_output(&config))
}
}
export!(ParserComponent);
use nginx_lint::plugin::data_types as dt;
use nginx_lint::plugin::parser_types as pt;
fn convert_argument(arg: &Argument) -> dt::ArgumentInfo {
let arg_type = match &arg.value {
ArgumentValue::Literal(_) => dt::ArgumentType::Literal,
ArgumentValue::QuotedString(_) => dt::ArgumentType::QuotedString,
ArgumentValue::SingleQuotedString(_) => dt::ArgumentType::SingleQuotedString,
ArgumentValue::Variable(_) => dt::ArgumentType::Variable,
};
dt::ArgumentInfo {
value: arg.as_str().to_string(),
raw: arg.raw.clone(),
arg_type,
line: arg.span.start.line as u32,
column: arg.span.start.column as u32,
start_offset: arg.span.start.offset as u32,
end_offset: arg.span.end.offset as u32,
}
}
fn convert_directive(d: &Directive) -> dt::DirectiveData {
dt::DirectiveData {
name: d.name.clone(),
args: d.args.iter().map(convert_argument).collect(),
line: d.span.start.line as u32,
column: d.span.start.column as u32,
start_offset: d.span.start.offset as u32,
end_offset: d.span.end.offset as u32,
end_line: d.span.end.line as u32,
end_column: d.span.end.column as u32,
leading_whitespace: d.leading_whitespace.clone(),
trailing_whitespace: d.trailing_whitespace.clone(),
space_before_terminator: d.space_before_terminator.clone(),
has_block: d.block.is_some(),
block_is_raw: d.block.as_ref().is_some_and(|b| b.is_raw()),
block_raw_content: d.block.as_ref().and_then(|b| b.raw_content.clone()),
closing_brace_leading_whitespace: d
.block
.as_ref()
.map(|b| b.closing_brace_leading_whitespace.clone()),
block_trailing_whitespace: d.block.as_ref().map(|b| b.trailing_whitespace.clone()),
trailing_comment_text: d.trailing_comment.as_ref().map(|c| c.text.clone()),
name_end_column: d.name_span.end.column as u32,
name_end_offset: d.name_span.end.offset as u32,
block_start_line: d.block.as_ref().map(|b| b.span.start.line as u32),
block_start_column: d.block.as_ref().map(|b| b.span.start.column as u32),
block_start_offset: d.block.as_ref().map(|b| b.span.start.offset as u32),
}
}
fn convert_comment(c: &Comment) -> dt::CommentInfo {
dt::CommentInfo {
text: c.text.clone(),
line: c.span.start.line as u32,
column: c.span.start.column as u32,
leading_whitespace: c.leading_whitespace.clone(),
trailing_whitespace: c.trailing_whitespace.clone(),
start_offset: c.span.start.offset as u32,
end_offset: c.span.end.offset as u32,
}
}
fn flatten_config_items(items: &[ConfigItem]) -> (Vec<pt::ConfigItem>, Vec<u32>) {
let mut all_items: Vec<pt::ConfigItem> = Vec::new();
let mut top_level_indices: Vec<u32> = Vec::new();
for item in items {
let idx = flatten_item(item, &mut all_items);
top_level_indices.push(idx);
}
(all_items, top_level_indices)
}
fn flatten_item(item: &ConfigItem, all_items: &mut Vec<pt::ConfigItem>) -> u32 {
match item {
ConfigItem::Directive(d) => {
let idx = all_items.len() as u32;
all_items.push(pt::ConfigItem {
value: pt::ConfigItemValue::DirectiveItem(convert_directive(d)),
child_indices: Vec::new(),
});
let child_indices: Vec<u32> = if let Some(block) = &d.block {
block
.items
.iter()
.map(|child| flatten_item(child, all_items))
.collect()
} else {
Vec::new()
};
all_items[idx as usize].child_indices = child_indices;
idx
}
ConfigItem::Comment(c) => {
let idx = all_items.len() as u32;
all_items.push(pt::ConfigItem {
value: pt::ConfigItemValue::CommentItem(convert_comment(c)),
child_indices: Vec::new(),
});
idx
}
ConfigItem::BlankLine(b) => {
let idx = all_items.len() as u32;
all_items.push(pt::ConfigItem {
value: pt::ConfigItemValue::BlankLineItem(dt::BlankLineInfo {
line: b.span.start.line as u32,
content: b.content.clone(),
start_offset: b.span.start.offset as u32,
}),
child_indices: Vec::new(),
});
idx
}
}
}
fn build_parse_output(config: &Config) -> pt::ParseOutput {
let (all_items, top_level_indices) = flatten_config_items(&config.items);
let directives_with_context = build_directive_contexts(config, &all_items, &top_level_indices);
pt::ParseOutput {
directives_with_context,
include_context: config.include_context.clone(),
all_items,
top_level_indices,
}
}
fn build_directive_contexts(
config: &Config,
all_items: &[pt::ConfigItem],
top_level_indices: &[u32],
) -> Vec<pt::DirectiveContext> {
let mut results = Vec::new();
collect_directive_contexts(
all_items,
top_level_indices,
&config.include_context,
&mut results,
);
results
}
fn collect_directive_contexts(
all_items: &[pt::ConfigItem],
indices: &[u32],
parent_stack: &[String],
results: &mut Vec<pt::DirectiveContext>,
) {
for &idx in indices {
let item = &all_items[idx as usize];
if let pt::ConfigItemValue::DirectiveItem(ref data) = item.value {
results.push(pt::DirectiveContext {
data: data.clone(),
block_item_indices: item.child_indices.clone(),
parent_stack: parent_stack.to_vec(),
depth: parent_stack.len() as u32,
});
if !item.child_indices.is_empty() {
let mut child_stack = parent_stack.to_vec();
child_stack.push(data.name.clone());
collect_directive_contexts(all_items, &item.child_indices, &child_stack, results);
}
}
}
}