Skip to content

Commit 866ae0a

Browse files
Add AST traversal utilities for Fluent syntax analysis
Adds helper functions for traversing the Fluent AST to extract information: - extract_variable_references: Get all variables used in a pattern - collect_vars_from_pattern: Recursively collect variables from patterns - collect_vars_from_expression: Extract variables from expressions - collect_references: Collect message/term references from patterns - collect_expression_references: Extract message/term refs from expressions These utilities enable validation features like detecting unused variables, checking that all referenced messages exist, and finding cyclic references.
1 parent aad1399 commit 866ae0a

File tree

1 file changed

+106
-0
lines changed

1 file changed

+106
-0
lines changed

src/lib.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use miette::{LabeledSpan, miette};
66
use pyo3::exceptions::{PyFileNotFoundError, PyTypeError, PyValueError};
77
use pyo3::prelude::*;
88
use pyo3::types::{PyDate, PyDict, PyInt, PyList, PyString};
9+
use std::collections::{HashMap, HashSet};
910
use std::fs;
1011
use std::path::PathBuf;
1112
use unic_langid::LanguageIdentifier;
@@ -210,6 +211,111 @@ impl FormatError {
210211
}
211212
}
212213

214+
/// Extract all variable references from a pattern
215+
fn extract_variable_references(pattern: &fluent_syntax::ast::Pattern<&str>) -> HashSet<String> {
216+
let mut vars = HashSet::new();
217+
collect_vars_from_pattern(pattern, &mut vars);
218+
vars
219+
}
220+
221+
fn collect_vars_from_pattern(
222+
pattern: &fluent_syntax::ast::Pattern<&str>,
223+
vars: &mut HashSet<String>,
224+
) {
225+
use fluent_syntax::ast;
226+
for element in &pattern.elements {
227+
if let ast::PatternElement::Placeable { expression } = element {
228+
collect_vars_from_expression(expression, vars);
229+
}
230+
}
231+
}
232+
233+
fn collect_vars_from_expression(
234+
expr: &fluent_syntax::ast::Expression<&str>,
235+
vars: &mut HashSet<String>,
236+
) {
237+
use fluent_syntax::ast;
238+
match expr {
239+
ast::Expression::Inline(inline) => match inline {
240+
ast::InlineExpression::VariableReference { id } => {
241+
vars.insert(id.name.to_string());
242+
}
243+
ast::InlineExpression::FunctionReference { arguments, .. } => {
244+
// Check positional args
245+
for arg in &arguments.positional {
246+
collect_vars_from_expression(&ast::Expression::Inline(arg.clone()), vars);
247+
}
248+
// Check named args
249+
for arg in &arguments.named {
250+
collect_vars_from_expression(&ast::Expression::Inline(arg.value.clone()), vars);
251+
}
252+
}
253+
ast::InlineExpression::TermReference { arguments, .. } => {
254+
if let Some(args) = arguments {
255+
// Check positional args
256+
for arg in &args.positional {
257+
collect_vars_from_expression(&ast::Expression::Inline(arg.clone()), vars);
258+
}
259+
// Check named args
260+
for arg in &args.named {
261+
collect_vars_from_expression(
262+
&ast::Expression::Inline(arg.value.clone()),
263+
vars,
264+
);
265+
}
266+
}
267+
}
268+
_ => {}
269+
},
270+
ast::Expression::Select { selector, variants } => {
271+
// Check selector expression
272+
collect_vars_from_expression(&ast::Expression::Inline((*selector).clone()), vars);
273+
274+
// Check all variant values
275+
for variant in variants {
276+
collect_vars_from_pattern(&variant.value, vars);
277+
}
278+
}
279+
}
280+
}
281+
282+
fn collect_references(pattern: &Option<fluent_syntax::ast::Pattern<&str>>, refs: &mut Vec<String>) {
283+
use fluent_syntax::ast;
284+
285+
if let Some(pattern) = pattern {
286+
for element in &pattern.elements {
287+
if let ast::PatternElement::Placeable { expression } = element {
288+
collect_expression_references(expression, refs);
289+
}
290+
}
291+
}
292+
}
293+
294+
fn collect_expression_references(
295+
expression: &fluent_syntax::ast::Expression<&str>,
296+
refs: &mut Vec<String>,
297+
) {
298+
use fluent_syntax::ast;
299+
300+
match expression {
301+
ast::Expression::Inline(inline) => match inline {
302+
ast::InlineExpression::MessageReference { id, .. } => {
303+
refs.push(id.name.to_string());
304+
}
305+
ast::InlineExpression::TermReference { id, .. } => {
306+
refs.push(format!("-{}", id.name));
307+
}
308+
_ => {}
309+
},
310+
ast::Expression::Select { selector, variants } => {
311+
collect_expression_references(&ast::Expression::Inline((*selector).clone()), refs);
312+
for variant in variants {
313+
collect_references(&Some(variant.value.clone()), refs);
314+
}
315+
}
316+
}
317+
}
318+
213319
#[pymodule]
214320
mod rustfluent {
215321
use super::*;

0 commit comments

Comments
 (0)