Skip to content

Commit 49f1961

Browse files
committed
feat(flow): tighten entry scoping and SwiftUI trace fallback
1 parent 90cd150 commit 49f1961

6 files changed

Lines changed: 732 additions & 83 deletions

File tree

grapha/src/query.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,36 @@ pub(crate) fn normalize_symbol_name(name: &str) -> &str {
7070
.unwrap_or(without_accessor)
7171
}
7272

73+
pub(crate) fn file_matches_query_path(node_file: &std::path::Path, file_query: &str) -> bool {
74+
let normalize = |value: &str| value.replace('\\', "/");
75+
76+
let file = normalize(&node_file.to_string_lossy());
77+
let query = normalize(file_query);
78+
79+
if file == query || file.ends_with(&query) || file.contains(&query) || query.ends_with(&file) {
80+
return true;
81+
}
82+
83+
let file_name = file.rsplit('/').next().unwrap_or(file.as_str());
84+
let query_name = query.rsplit('/').next().unwrap_or(query.as_str());
85+
file_name == query_name
86+
}
87+
88+
pub(crate) fn file_matches_path_or_suffix(node_file: &std::path::Path, file_query: &str) -> bool {
89+
let normalize = |value: &str| value.replace('\\', "/");
90+
91+
let file = normalize(&node_file.to_string_lossy());
92+
let query = normalize(file_query);
93+
94+
if file == query || file.ends_with(&query) || query.ends_with(&file) {
95+
return true;
96+
}
97+
98+
let file_name = file.rsplit('/').next().unwrap_or(file.as_str());
99+
let query_name = query.rsplit('/').next().unwrap_or(query.as_str());
100+
file_name == query_name
101+
}
102+
73103
pub(crate) fn is_swiftui_invalidation_source(node: &Node) -> bool {
74104
node.metadata
75105
.get("swiftui.invalidation_source")
@@ -598,4 +628,16 @@ mod tests {
598628
.expect("locator should resolve");
599629
assert_eq!(resolved.id, "method-id");
600630
}
631+
632+
#[test]
633+
fn strict_file_match_requires_full_path_or_suffix() {
634+
let node_file = PathBuf::from("Modules/Room/Sources/Room/View/RoomPage.swift");
635+
636+
assert!(file_matches_path_or_suffix(&node_file, "RoomPage.swift"));
637+
assert!(file_matches_path_or_suffix(
638+
&node_file,
639+
"Modules/Room/Sources/Room/View/RoomPage.swift"
640+
));
641+
assert!(!file_matches_path_or_suffix(&node_file, "Page"));
642+
}
601643
}

grapha/src/query/entries.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use serde::Serialize;
22

33
use grapha_core::graph::{Graph, NodeRole};
44

5-
use super::{SymbolRef, file_matches_query_path};
5+
use super::{SymbolRef, file_matches_path_or_suffix};
66

77
#[derive(Debug, Clone, Default)]
88
pub struct EntriesQueryOptions {
@@ -39,13 +39,13 @@ pub fn query_entries_with_options(graph: &Graph, options: &EntriesQueryOptions)
3939
options
4040
.module
4141
.as_deref()
42-
.map_or(true, |module| node.module.as_deref() == Some(module))
42+
.is_none_or(|module| node.module.as_deref() == Some(module))
4343
})
4444
.filter(|node| {
4545
options
4646
.file
4747
.as_deref()
48-
.map_or(true, |file_query| file_matches_query_path(&node.file, file_query))
48+
.is_none_or(|file_query| file_matches_path_or_suffix(&node.file, file_query))
4949
})
5050
.map(SymbolRef::from_node)
5151
.collect();
@@ -192,11 +192,7 @@ mod tests {
192192
(
193193
entry.id.as_str(),
194194
entry.name.as_str(),
195-
entry
196-
.file
197-
.rsplit('/')
198-
.next()
199-
.unwrap_or(entry.file.as_str()),
195+
entry.file.rsplit('/').next().unwrap_or(entry.file.as_str()),
200196
entry.module.as_deref(),
201197
)
202198
})
@@ -207,4 +203,30 @@ mod tests {
207203
assert_eq!(result.total, 2);
208204
assert_eq!(result.shown, 1);
209205
}
206+
207+
#[test]
208+
fn file_filter_does_not_match_partial_fragments() {
209+
let graph = Graph {
210+
version: "0.1.0".to_string(),
211+
nodes: vec![entry_node(
212+
"room_body",
213+
"body",
214+
"Modules/Room/Sources/Room/View/RoomPage.swift",
215+
Some("Room"),
216+
)],
217+
edges: vec![],
218+
};
219+
220+
let result = query_entries_with_options(
221+
&graph,
222+
&EntriesQueryOptions {
223+
file: Some("Page".to_string()),
224+
..EntriesQueryOptions::default()
225+
},
226+
);
227+
228+
assert_eq!(result.total, 0);
229+
assert_eq!(result.shown, 0);
230+
assert!(result.entries.is_empty());
231+
}
210232
}

0 commit comments

Comments
 (0)