improve namespace handling

This commit is contained in:
Robin Appelman 2024-07-23 19:27:34 +02:00
commit 724c738a50
6 changed files with 148 additions and 17 deletions

View file

@ -114,6 +114,7 @@ dependencies = [
"cc", "cc",
"databake", "databake",
"insta", "insta",
"maplit",
"memchr", "memchr",
"regex", "regex",
"regex-syntax", "regex-syntax",
@ -128,6 +129,12 @@ dependencies = [
"walkdir", "walkdir",
] ]
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.4" version = "2.7.4"

View file

@ -27,5 +27,6 @@ regex = "1.10.5"
cc = "1.1.6" cc = "1.1.6"
[dev-dependencies] [dev-dependencies]
maplit = "1.0.2"
test-case = "3.3.1" test-case = "3.3.1"
insta = { version = "1.39.0", features = ["json"] } insta = { version = "1.39.0", features = ["json"] }

View file

@ -1,4 +1,5 @@
use crate::messagebuilder::MessageBuilder; use crate::messagebuilder::MessageBuilder;
use crate::name_resolver::resolve_name;
use crate::{LogLevel, LoggingStatement}; use crate::{LogLevel, LoggingStatement};
use std::collections::HashMap; use std::collections::HashMap;
use tree_sitter::{Language, Node, Parser, Query, QueryCursor}; use tree_sitter::{Language, Node, Parser, Query, QueryCursor};
@ -8,6 +9,7 @@ pub struct LogExtractor {
method_query: Query, method_query: Query,
throw_query: Query, throw_query: Query,
use_query: Query, use_query: Query,
namespace_query: Query,
} }
impl LogExtractor { impl LogExtractor {
@ -39,11 +41,19 @@ impl LogExtractor {
)"#, )"#,
) )
.expect("invalid query"); .expect("invalid query");
let namespace_query = Query::new(
&language,
r#"(namespace_definition
name: (namespace_name) @name
)"#,
)
.expect("invalid query");
LogExtractor { LogExtractor {
language, language,
method_query, method_query,
throw_query, throw_query,
use_query, use_query,
namespace_query,
} }
} }
@ -64,10 +74,12 @@ impl LogExtractor {
let mut log_call_cursor = QueryCursor::new(); let mut log_call_cursor = QueryCursor::new();
let mut throw_call_cursor = QueryCursor::new(); let mut throw_call_cursor = QueryCursor::new();
let aliases = self.get_aliases(&mut log_call_cursor, code, tree.root_node()); let root = tree.root_node();
let aliases = self.get_aliases(&mut log_call_cursor, code, root);
let namespace = self.get_namespace(&mut log_call_cursor, code, root);
let log_calls = self.get_log_calls(&mut log_call_cursor, code, tree.root_node()); let log_calls = self.get_log_calls(&mut log_call_cursor, code, root);
let throw_calls = self.get_throw_calls(&mut throw_call_cursor, code, tree.root_node()); let throw_calls = self.get_throw_calls(&mut throw_call_cursor, code, root);
let mut all = log_calls let mut all = log_calls
.chain(throw_calls) .chain(throw_calls)
.filter_map(|call| { .filter_map(|call| {
@ -78,13 +90,9 @@ impl LogExtractor {
message_builder.push_node(argument, code); message_builder.push_node(argument, code);
} }
let exception = call.exception.map(|exception| { let exception = call
aliases .exception
.get(exception.as_str()) .map(|exception| resolve_name(namespace, &aliases, exception.as_str()));
.copied()
.map(String::from)
.unwrap_or(exception)
});
Some(LoggingStatement { Some(LoggingStatement {
level: call.level, level: call.level,
@ -184,6 +192,25 @@ impl LogExtractor {
}) })
.collect() .collect()
} }
fn get_namespace<'a>(
&'a self,
cursor: &mut QueryCursor,
code: &'a str,
node: Node<'a>,
) -> &'a str {
let mut namespace = cursor.matches(&self.namespace_query, node, code.as_bytes());
namespace
.next()
.map(|namespace| {
namespace.captures[0]
.node
.utf8_text(code.as_bytes())
.unwrap()
})
.unwrap_or("")
}
} }
impl Default for LogExtractor { impl Default for LogExtractor {
@ -204,14 +231,19 @@ fn test_extract_logging() {
use crate::MessagePart; use crate::MessagePart;
let code = r#"<?php let code = r#"<?php
namespace Test;
use Bar\BarException; use Bar\BarException;
use \Foo\Exception as FooException; use \Foo\Exception as FooException;
use Foo\Asd;
function test() { function test() {
$this->logger->warning("failed to find trash item for $rootTrashedItemName deleted at $rootTrashedItemDate in folder $groupFolderId", ['app' => 'groupfolders']); $this->logger->warning("failed to find trash item for $rootTrashedItemName deleted at $rootTrashedItemDate in folder $groupFolderId", ['app' => 'groupfolders']);
$logger->info('foobar'); $logger->info('foobar');
throw new FooException("foo \"bar\" \' {$this->blarg}"); throw new FooException("foo \"bar\" \' {$this->blarg}");
throw new BarException(); throw new BarException();
$this->logger->error('Share notification mail could not be sent to: ' . implode(', ', $failedRecipients)); $this->logger->error('Share notification mail could not be sent to: ' . implode(', ', $failedRecipients));
throw new Asd\Exception();
throw new SomeException();
throw new \SomeException();
} }
?> ?>
"#; "#;
@ -221,7 +253,7 @@ fn test_extract_logging() {
logs[0], logs[0],
LoggingStatement { LoggingStatement {
path: "foo.php", path: "foo.php",
line: 5, line: 7,
level: LogLevel::Warn, level: LogLevel::Warn,
has_meaningful_message: true, has_meaningful_message: true,
exception: None, exception: None,
@ -239,7 +271,7 @@ fn test_extract_logging() {
logs[1], logs[1],
LoggingStatement { LoggingStatement {
path: "foo.php", path: "foo.php",
line: 6, line: 8,
level: LogLevel::Info, level: LogLevel::Info,
has_meaningful_message: true, has_meaningful_message: true,
exception: None, exception: None,
@ -250,7 +282,7 @@ fn test_extract_logging() {
logs[2], logs[2],
LoggingStatement { LoggingStatement {
path: "foo.php", path: "foo.php",
line: 7, line: 9,
level: LogLevel::Exception, level: LogLevel::Exception,
has_meaningful_message: true, has_meaningful_message: true,
exception: Some("Foo\\Exception".into()), exception: Some("Foo\\Exception".into()),
@ -264,7 +296,7 @@ fn test_extract_logging() {
logs[3], logs[3],
LoggingStatement { LoggingStatement {
path: "foo.php", path: "foo.php",
line: 8, line: 10,
level: LogLevel::Exception, level: LogLevel::Exception,
has_meaningful_message: false, has_meaningful_message: false,
exception: Some("Bar\\BarException".into()), exception: Some("Bar\\BarException".into()),
@ -275,7 +307,7 @@ fn test_extract_logging() {
logs[4], logs[4],
LoggingStatement { LoggingStatement {
path: "foo.php", path: "foo.php",
line: 9, line: 11,
level: LogLevel::Error, level: LogLevel::Error,
has_meaningful_message: true, has_meaningful_message: true,
exception: None, exception: None,
@ -285,4 +317,37 @@ fn test_extract_logging() {
] ]
} }
); );
assert_eq!(
logs[5],
LoggingStatement {
path: "foo.php",
line: 12,
level: LogLevel::Exception,
has_meaningful_message: false,
exception: Some("Foo\\Asd\\Exception".into()),
message_parts: vec![]
}
);
assert_eq!(
logs[6],
LoggingStatement {
path: "foo.php",
line: 13,
level: LogLevel::Exception,
has_meaningful_message: false,
exception: Some("Test\\SomeException".into()),
message_parts: vec![]
}
);
assert_eq!(
logs[7],
LoggingStatement {
path: "foo.php",
line: 14,
level: LogLevel::Exception,
has_meaningful_message: false,
exception: Some("SomeException".into()),
message_parts: vec![]
}
);
} }

View file

@ -11,6 +11,7 @@ pub mod error;
pub mod extractor; pub mod extractor;
mod level; mod level;
mod messagebuilder; mod messagebuilder;
mod name_resolver;
pub mod string; pub mod string;
use crate::bake::bake_statement; use crate::bake::bake_statement;

View file

@ -0,0 +1,57 @@
use std::collections::HashMap;
pub fn resolve_name(namespace: &str, aliases: &HashMap<&str, &str>, class: &str) -> String {
if class.starts_with('\\') {
return class[1..].into();
}
let (first_part, rest) = class.split_once('\\').unwrap_or((class, ""));
if let Some(alias) = aliases.get(first_part) {
if rest.is_empty() {
format!("{alias}")
} else {
format!("{alias}\\{rest}")
}
} else if namespace.is_empty() {
if rest.is_empty() {
format!("{first_part}")
} else {
format!("{first_part}\\{rest}")
}
} else {
if rest.is_empty() {
format!("{namespace}\\{first_part}")
} else {
format!("{namespace}\\{first_part}\\{rest}")
}
}
}
#[test]
fn test_resolve_name() {
use maplit::hashmap;
assert_eq!(resolve_name("", &hashmap! {}, "Bar"), "Bar");
assert_eq!(resolve_name("Foo", &hashmap! {}, "Bar"), "Foo\\Bar");
assert_eq!(
resolve_name(
"Foo",
&hashmap! {
"Bar" => "Asd"
},
"Bar"
),
"Asd"
);
assert_eq!(resolve_name("Foo", &hashmap! {}, "\\Bar"), "Bar");
assert_eq!(resolve_name("Foo", &hashmap! {}, "\\Bar"), "Bar");
assert_eq!(
resolve_name(
"Foo",
&hashmap! {
"Bar" => "Asd"
},
"\\Bar"
),
"Bar"
);
}

View file

@ -8,7 +8,7 @@ expression: output
"path": "/DefaultShareProvider.php", "path": "/DefaultShareProvider.php",
"line": 129, "line": 129,
"has_meaningful_message": true, "has_meaningful_message": true,
"exception": "\\Exception", "exception": "Exception",
"message_parts": [ "message_parts": [
{ {
"literal": "invalid share type!" "literal": "invalid share type!"
@ -116,7 +116,7 @@ expression: output
"path": "/DefaultShareProvider.php", "path": "/DefaultShareProvider.php",
"line": 609, "line": 609,
"has_meaningful_message": true, "has_meaningful_message": true,
"exception": "\\Exception", "exception": "Exception",
"message_parts": [ "message_parts": [
{ {
"literal": "non-shallow getSharesInFolder is no longer supported" "literal": "non-shallow getSharesInFolder is no longer supported"