do some variable flattening when extracting log messages

This commit is contained in:
Robin Appelman 2024-09-17 16:47:08 +02:00
commit a463903d24
4 changed files with 186 additions and 131 deletions

View file

@ -86,8 +86,9 @@ impl LogExtractor {
let mut message_builder = MessageBuilder::with_capacity(16);
if let Some(argument) = call.arguments {
let mut context = self.get_context_assignments(code, argument);
let argument = argument.child(0)?;
message_builder.push_node(argument, code);
message_builder.push_node(argument, code, &mut context);
}
let exception = call
@ -211,6 +212,24 @@ impl LogExtractor {
})
.unwrap_or("")
}
fn get_context_assignments<'a>(&'a self, code: &'a str, mut node: Node<'a>) -> HashMap<&'a str, Node<'a>> {
let mut assignments = HashMap::new();
let mut cursor = node.walk();
while let Some(parent) = node.parent() {
node = parent;
if ["method_declaration", "function_definition"].contains(&parent.grammar_name()) {
break;
}
let child_assignments = node.children(&mut cursor)
.filter_map(|child| (child.grammar_name() == "expression_statement").then(|| child.child(0).unwrap()))
.filter(|child| child.grammar_name() == "assignment_expression")
.filter_map(|child| Some((child.child_by_field_name("left")?.child(1).unwrap(), child.child_by_field_name("right")?)))
.map(|(left, right)| (left.utf8_text(code.as_bytes()).unwrap(), right));
assignments.extend(child_assignments);
}
assignments
}
}
impl Default for LogExtractor {
@ -247,6 +266,8 @@ fn test_extract_logging() {
$this->logger->error("foo {bar} {asd}");
$this->logger->error($this->l10n->t("translated %s", $foo));
throw new InvalidArgumentException(sprintf('Argument "%s" not found.', $key));
$baseMsg = 'Could not resolve ' . $name . '!';
throw new QueryNotFoundException($baseMsg . ' ' . $e->getMessage());
}
?>
"#;
@ -398,4 +419,20 @@ fn test_extract_logging() {
]
}
);
assert_eq!(
logs[11],
LoggingStatement {
path: "foo.php",
line: 19,
level: LogLevel::Exception,
has_meaningful_message: true,
exception: Some("Test\\QueryNotFoundException".into()),
message_parts: vec![
MessagePart::Literal(r#"Could not resolve "#.into()),
MessagePart::PlaceHolder("$name".into()),
MessagePart::Literal(r#"! "#.into()),
MessagePart::PlaceHolder(r#"$e->getMessage()"#.into()),
]
}
);
}

View file

@ -1,3 +1,4 @@
use std::collections::HashMap;
use crate::string::{unescape, DoubleQuoteString, SingleQuoteString};
use crate::MessagePart;
use regex::Regex;
@ -81,7 +82,7 @@ impl MessageBuilder {
}
}
pub fn push_node(&mut self, node: Node, code: &str) {
pub fn push_node(&mut self, node: Node, code: &str, context: &mut HashMap<&str, Node>) {
let mut cursor = node.walk();
match node.grammar_name() {
"string" | "encapsed_string" => {
@ -93,10 +94,21 @@ impl MessageBuilder {
let operator = &code[start..end];
if operator.trim() == "." {
for part in node.named_children(&mut cursor) {
self.push_node(part, code);
self.push_node(part, code, context);
}
}
}
"variable_name" => {
let name = node.child(1).map(|c| c.utf8_text(code.as_bytes()).unwrap()).unwrap_or_default();
if let Some(replacement) = context.remove(name) {
if has_literal(replacement, code, context) {
self.push_node(replacement, code, context);
return;
}
}
let placeholder = node.utf8_text(code.as_bytes()).unwrap();
self.push_placeholder(placeholder);
}
"member_call_expression" | "function_call_expression" => {
match node
.child_by_field_name("name")
@ -144,6 +156,12 @@ impl MessageBuilder {
}
}
fn has_literal(node: Node, code: &str, context: &HashMap<&str, Node>) -> bool {
let mut replacement_builder = MessageBuilder::with_capacity(4);
replacement_builder.push_node(node.clone(), code, &mut context.clone());
replacement_builder.parts.iter().any(|part| matches!(part, MessagePart::Literal(_)))
}
impl From<MessageBuilder> for Vec<MessagePart> {
fn from(value: MessageBuilder) -> Self {
value.parts