brick/tests/swift_preview.rs
trvon c3c7c6c3f4
fix(swift): give the lexical preview Swift's forms and mask block comments
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.
2026-09-24 21:00:59 -06:00

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:?}"
);
}