Swift ran through the C-style lexical patterns, which never recognize
`func`. On a small file (`func realTarget()`, `func ghost()`, a class
method `caller` that calls `realTarget()` below a `/* ghost() */`
comment) the preview reported:
a.swift -> ghost (a.swift:2 declaration, a.swift:5 in a comment)
a.swift -> caller (its declaration line)
a.swift -> realTarget (its declaration line)
and missed the real call. Now:
caller -> realTarget (a.swift:7)
- Swift gets its own patterns: `func` declarations (with attributes and
modifiers), class/struct/enum/protocol/actor/extension declarations
(`class func` / `class var` are members, not classes), and imports.
- Block comments are masked across lines, nested as Swift allows, and a
`/*` inside a string literal does not open one. The masker is salvaged
from the unmerged language-parity branch (543bafc).
- Calls are attributed to their enclosing function through the existing
brace-scoped scopes, now that functions are recognized.
Test: tests/swift_preview.rs (functions and a class recognized, the call
attributed to `caller`, nothing from comments, nothing from the file
node); it failed before this change.
98 lines
2.7 KiB
Rust
98 lines
2.7 KiB
Rust
use brick::model::{Building, EdgeKind, NodeKind};
|
|
use brick::scan;
|
|
use std::fs;
|
|
|
|
fn name_of<'a>(model: &'a Building, id: &str) -> &'a str {
|
|
model
|
|
.nodes
|
|
.iter()
|
|
.find(|node| node.id == id)
|
|
.map_or("", |node| node.name.as_str())
|
|
}
|
|
|
|
fn calls(model: &Building) -> Vec<(String, String, NodeKind)> {
|
|
model
|
|
.edges
|
|
.iter()
|
|
.filter(|edge| edge.kind == EdgeKind::Calls)
|
|
.map(|edge| {
|
|
let source_kind = model
|
|
.nodes
|
|
.iter()
|
|
.find(|node| node.id == edge.source)
|
|
.map(|node| node.kind)
|
|
.unwrap();
|
|
(
|
|
name_of(model, &edge.source).to_owned(),
|
|
name_of(model, &edge.target).to_owned(),
|
|
source_kind,
|
|
)
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
#[test]
|
|
fn swift_preview_declares_functions_and_ignores_comments() {
|
|
// Swift ran through the C-style lexical patterns: `func` declarations were
|
|
// never functions, each declaration line became a call from the file, calls
|
|
// inside (nested) block comments became edges, and real calls were attributed
|
|
// to the file instead of their function.
|
|
let dir = tempfile::tempdir().unwrap();
|
|
fs::write(
|
|
dir.path().join("a.swift"),
|
|
r#"import Foundation
|
|
|
|
func realTarget() -> Int { return 1 }
|
|
func ghost() -> Int { return 2 }
|
|
|
|
final class Worker: NSObject {
|
|
let url = "http://example.com/*not-a-comment"
|
|
func caller() -> Int {
|
|
/* disabled:
|
|
ghost()
|
|
/* nested: ghost() */
|
|
still disabled: ghost()
|
|
*/
|
|
return realTarget()
|
|
}
|
|
}
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
let model = scan::scan(dir.path()).unwrap();
|
|
|
|
let functions: Vec<&str> = model
|
|
.nodes
|
|
.iter()
|
|
.filter(|node| node.kind == NodeKind::Function)
|
|
.map(|node| node.name.as_str())
|
|
.collect();
|
|
for name in ["realTarget", "ghost", "caller"] {
|
|
assert!(
|
|
functions.contains(&name),
|
|
"{name} not a function: {functions:?}"
|
|
);
|
|
}
|
|
assert!(
|
|
model
|
|
.nodes
|
|
.iter()
|
|
.any(|node| node.kind == NodeKind::Class && node.name == "Worker")
|
|
);
|
|
|
|
let calls = calls(&model);
|
|
assert!(
|
|
calls.iter().any(|(source, target, kind)| source == "caller"
|
|
&& target == "realTarget"
|
|
&& *kind == NodeKind::Function),
|
|
"{calls:?}"
|
|
);
|
|
assert!(
|
|
!calls.iter().any(|(_, target, _)| target == "ghost"),
|
|
"a call inside a block comment became an edge: {calls:?}"
|
|
);
|
|
assert!(
|
|
!calls.iter().any(|(_, _, kind)| *kind == NodeKind::File),
|
|
"a declaration or statement was attributed to the file: {calls:?}"
|
|
);
|
|
}
|