From 206e1feee993df189fa01d90e4c11ad59826dfea Mon Sep 17 00:00:00 2001 From: kelmith Date: Mon, 27 Jul 2026 11:17:17 +0530 Subject: [PATCH 1/3] chore: bump tree-sitter-swift to 0.7.3 for typed-throws parsing support --- Cargo.lock | 4 ++-- ast/Cargo.toml | 2 +- cli/Cargo.lock | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 250776cc7..7afae2037 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5438,9 +5438,9 @@ dependencies = [ [[package]] name = "tree-sitter-swift" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef216011c3e3df4fa864736f347cb8d509b1066cf0c8549fb1fd81ac9832e59" +checksum = "fe36052155b9dd69ca82b3b8f1b4ccfb2d867125ac1a4db1dd7331829242668c" dependencies = [ "cc", "tree-sitter-language", diff --git a/ast/Cargo.toml b/ast/Cargo.toml index 56367be4d..a038b78fd 100644 --- a/ast/Cargo.toml +++ b/ast/Cargo.toml @@ -46,7 +46,7 @@ streaming-iterator = "0.1.9" git-url-parse = "0.4.5" tree-sitter-kotlin-sg = "0.*" # tree-sitter-swift = "0.*" -tree-sitter-swift = "0.7.1" +tree-sitter-swift = "0.7.3" tree-sitter-java = "0.23.5" tree-sitter-svelte-ng = "1.*" # gitoxide-core = { version = "0.42.0", features = ["blocking-client"] } diff --git a/cli/Cargo.lock b/cli/Cargo.lock index 0a52147f3..15ff0c1c8 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -2582,9 +2582,9 @@ dependencies = [ [[package]] name = "tree-sitter-swift" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef216011c3e3df4fa864736f347cb8d509b1066cf0c8549fb1fd81ac9832e59" +checksum = "fe36052155b9dd69ca82b3b8f1b4ccfb2d867125ac1a4db1dd7331829242668c" dependencies = [ "cc", "tree-sitter-language", From ac8ec21a88eef6afadc5d27f56b5d23df59b1e6e Mon Sep 17 00:00:00 2001 From: kelmith Date: Mon, 27 Jul 2026 12:19:47 +0530 Subject: [PATCH 2/3] fix: overhaul Swift query stack for extensions, protocols, and class-body properties --- ast/src/lang/parse/collect.rs | 57 ++++++++++++++- ast/src/lang/queries/mod.rs | 8 +++ ast/src/lang/queries/swift.rs | 70 ++++++++++++++++--- ast/src/testing/coverage/swift.rs | 10 +-- .../APIIntegrationTests.swift | 1 + .../swift/LegacyApp/SphinxTestApp/API.swift | 3 + .../LegacyApp/SphinxTestApp/AppDelegate.swift | 1 + .../CoreData/Person+CoreDataProperties.swift | 4 ++ .../SphinxTestApp/PersonTableViewCell.swift | 1 + .../SphinxTestApp/SceneDelegate.swift | 1 + .../SphinxTestApp/ViewController.swift | 3 +- .../Core/Extensions/String+Extensions.swift | 1 + .../Sources/Core/Network/APIClient.swift | 1 + .../Sources/Features/Chat/ChatManager.swift | 15 +++- .../Sources/Features/Chat/ChatView.swift | 4 ++ .../Features/Profile/ProfileModel.swift | 5 ++ .../Features/Profile/ProfileService.swift | 2 + .../Features/Profile/ProfileView.swift | 4 ++ .../ModernApp/Sources/ModernApp/App.swift | 3 + .../ModernApp/DependencyInjection.swift | 3 + .../Tests/ModernAppTests/ProfileTests.swift | 2 + cli/tests/cli/swift.rs | 10 +-- 22 files changed, 187 insertions(+), 22 deletions(-) diff --git a/ast/src/lang/parse/collect.rs b/ast/src/lang/parse/collect.rs index a6bad8edb..5073f629c 100644 --- a/ast/src/lang/parse/collect.rs +++ b/ast/src/lang/parse/collect.rs @@ -1,12 +1,14 @@ -use super::utils::trim_quotes; +use super::super::queries::consts::{CLASS_DEFINITION, CLASS_NAME}; +use super::utils::{clean_class_name, trim_quotes}; use crate::builder::utils::log_stage_timing; use crate::lang::{graphs::Graph, *}; use lsp::{Cmd as LspCmd, Position, Res as LspRes}; use shared::error::{Error, Result}; +use std::collections::HashSet; use std::time::Instant; use streaming_iterator::StreamingIterator; use tracing::warn; -use tree_sitter::Node as TreeNode; +use tree_sitter::{Node as TreeNode, QueryMatch}; impl Lang { pub fn collect( &self, @@ -40,6 +42,28 @@ impl Lang { Ok(res) } + /// For a class_definition_query match, return (name, declaration_kind). + /// `declaration_kind` is `None` for grammars that don't distinguish + /// declaration forms at the node-kind level (see `class_declaration_kind`). + fn class_match_name_and_kind( + &self, + q: &Query, + m: &QueryMatch, + code: &str, + ) -> Result<(Option, Option)> { + let mut name: Option = None; + let mut kind: Option = None; + Self::loop_captures(q, m, code, |body, node, o| { + if o == CLASS_NAME { + name = Some(clean_class_name(&body)); + } else if o == CLASS_DEFINITION { + kind = self.lang.class_declaration_kind(node, code); + } + Ok(()) + })?; + Ok((name, kind)) + } + pub fn collect_classes( &self, q: &Query, @@ -48,10 +72,39 @@ impl Lang { graph: &G, ) -> Result)>> { let tree = self.parse(code, &NodeType::Class)?; + + // Some grammars reuse one node kind for several declaration forms + // (e.g. Swift's `class_declaration` covers class/struct/enum/extension/ + // actor). Pre-scan this file's matches so an `extension Foo { ... }` + // block doesn't create a redundant Class node when `Foo` is already + // declared (class/struct/enum) elsewhere in the same file — its + // methods still get attributed to the real node via find_function_parent. + let mut names_with_primary_decl: HashSet = HashSet::new(); + { + let mut cursor = QueryCursor::new(); + let mut matches = cursor.matches(q, tree.root_node(), code.as_bytes()); + while let Some(m) = matches.next() { + let (name, kind) = self.class_match_name_and_kind(q, m, code)?; + if let Some(name) = name { + if kind.as_deref() != Some("extension") { + names_with_primary_decl.insert(name); + } + } + } + } + let mut cursor = QueryCursor::new(); let mut matches = cursor.matches(q, tree.root_node(), code.as_bytes()); let mut res = Vec::new(); while let Some(m) = matches.next() { + let (name, kind) = Self::class_match_name_and_kind(self, q, m, code)?; + if kind.as_deref() == Some("extension") { + if let Some(name) = &name { + if names_with_primary_decl.contains(name) { + continue; + } + } + } if let Some((cls, edges)) = self.format_class_with_associations(m, code, file, q, graph)? { diff --git a/ast/src/lang/queries/mod.rs b/ast/src/lang/queries/mod.rs index af74e4832..c0f7f27f7 100644 --- a/ast/src/lang/queries/mod.rs +++ b/ast/src/lang/queries/mod.rs @@ -82,6 +82,14 @@ pub trait Stack { None } fn class_definition_query(&self) -> String; + // For grammars that reuse one node kind for several declaration forms (e.g. + // Swift's `class_declaration` covers class/struct/enum/extension/actor), + // return a discriminator string (e.g. "extension") for a CLASS_DEFINITION + // node so `collect_classes` can suppress a redundant node when another + // declaration of the same name already exists in the same file. + fn class_declaration_kind(&self, _node: TreeNode, _code: &str) -> Option { + None + } fn instance_definition_query(&self) -> Option { None } diff --git a/ast/src/lang/queries/swift.rs b/ast/src/lang/queries/swift.rs index 699079829..259f15a46 100644 --- a/ast/src/lang/queries/swift.rs +++ b/ast/src/lang/queries/swift.rs @@ -48,12 +48,22 @@ impl Stack for Swift { Some(format!( r#" (source_file - (property_declaration + (property_declaration (pattern (simple_identifier) @{VARIABLE_NAME} ) )@{VARIABLE_DECLARATION} ) + + (class_declaration + (class_body + (property_declaration + (pattern + (simple_identifier) @{VARIABLE_NAME} + ) + )@{VARIABLE_DECLARATION} + ) + ) "# )) } @@ -71,6 +81,41 @@ impl Stack for Swift { ) } + fn class_declaration_kind(&self, node: TreeNode, code: &str) -> Option { + node.child_by_field_name("declaration_kind") + .and_then(|n| n.utf8_text(code.as_bytes()).ok()) + .map(|s| s.to_string()) + } + + fn trait_query(&self) -> Option { + Some(format!( + r#" + (protocol_declaration + name: (type_identifier) @{TRAIT_NAME} + ) @{TRAIT} + "# + )) + } + + fn implements_query(&self) -> Option { + // `class Foo: Bar, Codable, ObservableObject` — Swift's grammar doesn't + // distinguish a superclass from a protocol conformance here (both are + // `inheritance_specifier`), so every entry is treated uniformly as a + // conformance/inheritance relationship. + Some(format!( + r#" + (class_declaration + name: [(type_identifier)(user_type)] @{CLASS_NAME} + (inheritance_specifier + (user_type + (type_identifier) @{TRAIT_NAME} + ) + ) + ) @{IMPLEMENTS} + "# + )) + } + fn function_definition_query(&self) -> String { format!( r#" @@ -118,23 +163,32 @@ impl Stack for Swift { code: &str, file: &str, func_name: &str, - _callback: &dyn Fn(&str) -> Option<(NodeData, NodeType)>, + find_class: &dyn Fn(&str) -> Option<(NodeData, NodeType)>, _parent_type: Option<&str>, ) -> Result> { let mut parent = node.parent(); while let Some(current) = parent { - if current.kind() == "class_declaration" { + if current.kind() == "class_declaration" || current.kind() == "protocol_declaration" { break; } parent = current.parent(); } let parent_of = match parent { Some(p) => { - let query = self.q("name: (type_identifier) @class-name", &NodeType::Class); - query_to_ident(query, p, code)?.map(|parent_name| Operand { - source: NodeKeys::new(&parent_name, file, p.start_position().row), - target: NodeKeys::new(func_name, file, node.start_position().row), - source_type: NodeType::Class, + let query = + self.q("name: [(type_identifier)(user_type)] @class-name", &NodeType::Class); + let parent_name = query_to_ident(query, p, code)?; + // `class_declaration` covers class/struct/enum/extension/actor in this + // grammar — an `extension Foo { ... }` block re-opening an existing type + // is itself a distinct node, but its methods still belong to the one real + // `Foo`. Resolve by name so every block's methods land on the same node, + // instead of keying the edge to whichever block happens to contain them. + parent_name.and_then(|name| { + find_class(&name).map(|(class, source_type)| Operand { + source: NodeKeys::new(&class.name, &class.file, class.start), + target: NodeKeys::new(func_name, file, node.start_position().row), + source_type, + }) }) } None => None, diff --git a/ast/src/testing/coverage/swift.rs b/ast/src/testing/coverage/swift.rs index a6fee1fce..9b7a4632c 100644 --- a/ast/src/testing/coverage/swift.rs +++ b/ast/src/testing/coverage/swift.rs @@ -71,7 +71,7 @@ async fn test_btreemap_graph_structure() -> Result<()> { assert_eq!(endpoints.len(), 0); let functions = graph.find_nodes_by_type(NodeType::Function); - assert_eq!(functions.len(), 36); + assert_eq!(functions.len(), 37); let unit_tests = graph.find_nodes_by_type(NodeType::UnitTest); assert_eq!(unit_tests.len(), 5); @@ -89,7 +89,7 @@ async fn test_btreemap_graph_structure() -> Result<()> { assert_eq!(data_models.len(), 1); let traits = graph.find_nodes_by_type(NodeType::Trait); - assert_eq!(traits.len(), 0); + assert_eq!(traits.len(), 1); Ok(()) } @@ -108,16 +108,16 @@ async fn test_btreemap_test_to_function_edges() -> Result<()> { let graph = repo.build_graph_inner::().await?; let calls_edges = graph.count_edges_of_type(EdgeType::Calls); - assert_eq!(calls_edges, 24); + assert_eq!(calls_edges, 25); let contains_edges = graph.count_edges_of_type(EdgeType::Contains); - assert_eq!(contains_edges, 168); + assert_eq!(contains_edges, 210); let handler_edges = graph.count_edges_of_type(EdgeType::Handler); assert_eq!(handler_edges, 0); let implements_edges = graph.count_edges_of_type(EdgeType::Implements); - assert_eq!(implements_edges, 0); + assert_eq!(implements_edges, 1); let unit_test_to_function_edges = graph.find_nodes_with_edge_type(NodeType::UnitTest, NodeType::Function, EdgeType::Calls); diff --git a/ast/src/testing/swift/LegacyApp/IntegrationTests/APIIntegrationTests.swift b/ast/src/testing/swift/LegacyApp/IntegrationTests/APIIntegrationTests.swift index b15b913b5..7ef6d0a2d 100644 --- a/ast/src/testing/swift/LegacyApp/IntegrationTests/APIIntegrationTests.swift +++ b/ast/src/testing/swift/LegacyApp/IntegrationTests/APIIntegrationTests.swift @@ -12,6 +12,7 @@ import XCTest // @ast edge: Calls -> Function "updatePeopleProfileWith" "API.swift" // @ast edge: Calls -> Function "createRequest" "API.swift" // @ast node: Import "import-imports-srctestingswiftlegacyappintegrationtestsapiintegrationtestsswift-0" +// @ast node: Var "mockAPI" final class APIIntegrationTests: XCTestCase { diff --git a/ast/src/testing/swift/LegacyApp/SphinxTestApp/API.swift b/ast/src/testing/swift/LegacyApp/SphinxTestApp/API.swift index aa8f9b7ea..91dc86391 100644 --- a/ast/src/testing/swift/LegacyApp/SphinxTestApp/API.swift +++ b/ast/src/testing/swift/LegacyApp/SphinxTestApp/API.swift @@ -22,6 +22,9 @@ import SwiftyJSON // @ast node: Request "/people" // @ast node: Request "/person" // @ast node: Import "import-imports-srctestingswiftlegacyappsphinxtestappapiswift-7" +// @ast node: Var "sharedInstance" +// @ast node: Var "instance" +// @ast node: Var "host" class API { class var sharedInstance : API { diff --git a/ast/src/testing/swift/LegacyApp/SphinxTestApp/AppDelegate.swift b/ast/src/testing/swift/LegacyApp/SphinxTestApp/AppDelegate.swift index 0c58c9f7e..ce23a1eb8 100644 --- a/ast/src/testing/swift/LegacyApp/SphinxTestApp/AppDelegate.swift +++ b/ast/src/testing/swift/LegacyApp/SphinxTestApp/AppDelegate.swift @@ -18,6 +18,7 @@ import CoreData // @ast node: Function "saveContext" // @ast node: Var "Name" // @ast node: Var "Version" +// @ast node: Var "persistentContainer" // @ast node: Import "import-imports-srctestingswiftlegacyappsphinxtestappappdelegateswift-7" let Name = "StakGraph" diff --git a/ast/src/testing/swift/LegacyApp/SphinxTestApp/CoreData/Person+CoreDataProperties.swift b/ast/src/testing/swift/LegacyApp/SphinxTestApp/CoreData/Person+CoreDataProperties.swift index 94e4fdf69..30a2536ef 100644 --- a/ast/src/testing/swift/LegacyApp/SphinxTestApp/CoreData/Person+CoreDataProperties.swift +++ b/ast/src/testing/swift/LegacyApp/SphinxTestApp/CoreData/Person+CoreDataProperties.swift @@ -11,6 +11,10 @@ import CoreData // @ast node: Class "Person" // @ast node: Function "fetchRequest" // @ast node: Import "import-imports-srctestingswiftlegacyappsphinxtestappcoredatapersoncoredatapropertiesswift-8" +// @ast node: Var "alias" +// @ast node: Var "imageUrl" +// @ast node: Var "publicKey" +// @ast node: Var "routeHint" extension Person { diff --git a/ast/src/testing/swift/LegacyApp/SphinxTestApp/PersonTableViewCell.swift b/ast/src/testing/swift/LegacyApp/SphinxTestApp/PersonTableViewCell.swift index 6c487c479..c71afe9d8 100644 --- a/ast/src/testing/swift/LegacyApp/SphinxTestApp/PersonTableViewCell.swift +++ b/ast/src/testing/swift/LegacyApp/SphinxTestApp/PersonTableViewCell.swift @@ -11,6 +11,7 @@ import UIKit // @ast edge: Operand -> Function "setSelected" "PersonTableViewCell.swift" // @ast node: Function "awakeFromNib" // @ast node: Function "setSelected" +// @ast node: Var "nameLabel" // @ast node: Import "import-imports-srctestingswiftlegacyappsphinxtestapppersontableviewcellswift-7" class PersonTableViewCell: UITableViewCell { diff --git a/ast/src/testing/swift/LegacyApp/SphinxTestApp/SceneDelegate.swift b/ast/src/testing/swift/LegacyApp/SphinxTestApp/SceneDelegate.swift index af66bdc99..4c8462e90 100644 --- a/ast/src/testing/swift/LegacyApp/SphinxTestApp/SceneDelegate.swift +++ b/ast/src/testing/swift/LegacyApp/SphinxTestApp/SceneDelegate.swift @@ -19,6 +19,7 @@ import UIKit // @ast node: Function "sceneWillResignActive" // @ast node: Function "sceneWillEnterForeground" // @ast node: Function "sceneDidEnterBackground" +// @ast node: Var "window" // @ast node: Import "import-imports-srctestingswiftlegacyappsphinxtestappscenedelegateswift-7" class SceneDelegate: UIResponder, UIWindowSceneDelegate { diff --git a/ast/src/testing/swift/LegacyApp/SphinxTestApp/ViewController.swift b/ast/src/testing/swift/LegacyApp/SphinxTestApp/ViewController.swift index 45b2d2eed..07409479a 100644 --- a/ast/src/testing/swift/LegacyApp/SphinxTestApp/ViewController.swift +++ b/ast/src/testing/swift/LegacyApp/SphinxTestApp/ViewController.swift @@ -8,7 +8,6 @@ import UIKit import SwiftyJSON // @ast node: Class "ViewController" -// @ast node: Class "ViewController" // @ast edge: Operand -> Function "viewDidLoad" "ViewController.swift" // @ast edge: Operand -> Function "configureTableView" "ViewController.swift" // @ast edge: Operand -> Function "getPeopleAndSave" "ViewController.swift" @@ -28,6 +27,8 @@ import SwiftyJSON // @ast node: Function "tableView" // @ast node: Function "tableView" // @ast edge: Calls -> Function "updateProfile" "ViewController.swift" +// @ast node: Var "peopleTableView" +// @ast node: Var "persons" // @ast node: Import "import-imports-srctestingswiftlegacyappsphinxtestappviewcontrollerswift-7" class ViewController: UIViewController { diff --git a/ast/src/testing/swift/ModernApp/Sources/Core/Extensions/String+Extensions.swift b/ast/src/testing/swift/ModernApp/Sources/Core/Extensions/String+Extensions.swift index a350122a2..28cf7fa0c 100644 --- a/ast/src/testing/swift/ModernApp/Sources/Core/Extensions/String+Extensions.swift +++ b/ast/src/testing/swift/ModernApp/Sources/Core/Extensions/String+Extensions.swift @@ -1,6 +1,7 @@ import Foundation // @ast node: Class "String" // @ast node: Function "isValidEmail" +// @ast node: Var "localized" // @ast node: Import "import-imports-srctestingswiftmodernappsourcescoreextensionsstringextensionsswift-0" extension String { diff --git a/ast/src/testing/swift/ModernApp/Sources/Core/Network/APIClient.swift b/ast/src/testing/swift/ModernApp/Sources/Core/Network/APIClient.swift index 63c1d6118..eb8e3be2d 100644 --- a/ast/src/testing/swift/ModernApp/Sources/Core/Network/APIClient.swift +++ b/ast/src/testing/swift/ModernApp/Sources/Core/Network/APIClient.swift @@ -5,6 +5,7 @@ import Foundation // @ast edge: Operand -> Function "post" "APIClient.swift" // @ast node: Function "fetch" // @ast node: Function "post" +// @ast node: Var "session" // @ast node: Import "import-imports-srctestingswiftmodernappsourcescorenetworkapiclientswift-0" enum APIError: Error { diff --git a/ast/src/testing/swift/ModernApp/Sources/Features/Chat/ChatManager.swift b/ast/src/testing/swift/ModernApp/Sources/Features/Chat/ChatManager.swift index dc1a69e9e..894067831 100644 --- a/ast/src/testing/swift/ModernApp/Sources/Features/Chat/ChatManager.swift +++ b/ast/src/testing/swift/ModernApp/Sources/Features/Chat/ChatManager.swift @@ -2,6 +2,13 @@ import Foundation // @ast node: Class "ChatManager" // @ast edge: Operand -> Function "send" "ChatManager.swift" // @ast node: Function "send" +// @ast node: Trait "ChatManagerDelegate" +// @ast node: Class "ConsoleChatObserver" +// @ast edge: Implements -> Trait "ChatManagerDelegate" "ChatManager.swift" +// @ast edge: Operand -> Function "didReceiveMessage" "ChatManager.swift" +// @ast node: Function "didReceiveMessage" +// @ast node: Var "delegate" +// @ast node: Var "onStatusChange" // @ast node: Import "import-imports-srctestingswiftmodernappsourcesfeatureschatchatmanagerswift-0" protocol ChatManagerDelegate: AnyObject { @@ -11,7 +18,7 @@ protocol ChatManagerDelegate: AnyObject { class ChatManager { weak var delegate: ChatManagerDelegate? var onStatusChange: ((Bool) -> Void)? - + func send(message: String) { // Simulate network delay DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) { [weak self] in @@ -20,3 +27,9 @@ class ChatManager { } } } + +class ConsoleChatObserver: ChatManagerDelegate { + func didReceiveMessage(_ message: String) { + print(message) + } +} diff --git a/ast/src/testing/swift/ModernApp/Sources/Features/Chat/ChatView.swift b/ast/src/testing/swift/ModernApp/Sources/Features/Chat/ChatView.swift index 8f6c464fd..51cb8e73e 100644 --- a/ast/src/testing/swift/ModernApp/Sources/Features/Chat/ChatView.swift +++ b/ast/src/testing/swift/ModernApp/Sources/Features/Chat/ChatView.swift @@ -1,6 +1,10 @@ import SwiftUI // @ast node: Class "ChatView" // @ast node: Class "ChatViewModel" +// @ast node: Var "viewModel" +// @ast node: Var "body" +// @ast node: Var "messages" +// @ast node: Var "searchText" // @ast node: Import "import-imports-srctestingswiftmodernappsourcesfeatureschatchatviewswift-0" struct ChatView: View { diff --git a/ast/src/testing/swift/ModernApp/Sources/Features/Profile/ProfileModel.swift b/ast/src/testing/swift/ModernApp/Sources/Features/Profile/ProfileModel.swift index d87fedb10..0c150c46c 100644 --- a/ast/src/testing/swift/ModernApp/Sources/Features/Profile/ProfileModel.swift +++ b/ast/src/testing/swift/ModernApp/Sources/Features/Profile/ProfileModel.swift @@ -1,6 +1,11 @@ import Foundation // @ast node: Class "Profile" // @ast node: Class "Status" +// @ast node: Var "id" +// @ast node: Var "username" +// @ast node: Var "bio" +// @ast node: Var "avatarURL" +// @ast node: Var "status" // @ast node: Import "import-imports-srctestingswiftmodernappsourcesfeaturesprofileprofilemodelswift-0" struct Profile: Codable, Identifiable { diff --git a/ast/src/testing/swift/ModernApp/Sources/Features/Profile/ProfileService.swift b/ast/src/testing/swift/ModernApp/Sources/Features/Profile/ProfileService.swift index 6820b0dfc..9b85b2ebc 100644 --- a/ast/src/testing/swift/ModernApp/Sources/Features/Profile/ProfileService.swift +++ b/ast/src/testing/swift/ModernApp/Sources/Features/Profile/ProfileService.swift @@ -5,6 +5,8 @@ import Foundation // @ast node: Function "fetchProfile" // @ast edge: Calls -> Function "fetch" "APIClient.swift" // @ast node: Function "updateStatus" +// @ast node: Var "apiClient" +// @ast node: Var "cache" // @ast node: Import "import-imports-srctestingswiftmodernappsourcesfeaturesprofileprofileserviceswift-0" actor ProfileService { diff --git a/ast/src/testing/swift/ModernApp/Sources/Features/Profile/ProfileView.swift b/ast/src/testing/swift/ModernApp/Sources/Features/Profile/ProfileView.swift index 81faf4bce..fb22321a2 100644 --- a/ast/src/testing/swift/ModernApp/Sources/Features/Profile/ProfileView.swift +++ b/ast/src/testing/swift/ModernApp/Sources/Features/Profile/ProfileView.swift @@ -3,6 +3,10 @@ import SwiftUI // @ast edge: Operand -> Function "loadProfile" "ProfileView.swift" // @ast node: Function "loadProfile" // @ast edge: Calls -> Function "fetchProfile" "ProfileService.swift" +// @ast node: Var "profile" +// @ast node: Var "isLoading" +// @ast node: Var "container" +// @ast node: Var "body" // @ast node: Import "import-imports-srctestingswiftmodernappsourcesfeaturesprofileprofileviewswift-0" struct ProfileView: View { diff --git a/ast/src/testing/swift/ModernApp/Sources/ModernApp/App.swift b/ast/src/testing/swift/ModernApp/Sources/ModernApp/App.swift index 83f6ee60c..a88ee61e7 100644 --- a/ast/src/testing/swift/ModernApp/Sources/ModernApp/App.swift +++ b/ast/src/testing/swift/ModernApp/Sources/ModernApp/App.swift @@ -1,6 +1,9 @@ import SwiftUI // @ast node: Class "ModernApp" // @ast node: Class "ContentView" +// @ast node: Var "dependencyContainer" +// @ast node: Var "body" +// @ast node: Var "body" // @ast node: Import "import-imports-srctestingswiftmodernappsourcesmodernappappswift-0" @main diff --git a/ast/src/testing/swift/ModernApp/Sources/ModernApp/DependencyInjection.swift b/ast/src/testing/swift/ModernApp/Sources/ModernApp/DependencyInjection.swift index 071619c43..8e0b1ff03 100644 --- a/ast/src/testing/swift/ModernApp/Sources/ModernApp/DependencyInjection.swift +++ b/ast/src/testing/swift/ModernApp/Sources/ModernApp/DependencyInjection.swift @@ -1,6 +1,9 @@ import Foundation import Combine // @ast node: Class "DependencyContainer" +// @ast node: Var "profileService" +// @ast node: Var "chatManager" +// @ast node: Var "apiClient" // @ast node: Import "import-imports-srctestingswiftmodernappsourcesmodernappdependencyinjectionswift-0" class DependencyContainer: ObservableObject { diff --git a/ast/src/testing/swift/ModernApp/Tests/ModernAppTests/ProfileTests.swift b/ast/src/testing/swift/ModernApp/Tests/ModernAppTests/ProfileTests.swift index f7940f7db..615f2f870 100644 --- a/ast/src/testing/swift/ModernApp/Tests/ModernAppTests/ProfileTests.swift +++ b/ast/src/testing/swift/ModernApp/Tests/ModernAppTests/ProfileTests.swift @@ -8,6 +8,8 @@ import XCTest // @ast node: UnitTest "testStatusUpdate" // @ast edge: Calls -> Function "updateStatus" "ProfileService.swift" // @ast node: UnitTest "testEmailValidation" +// @ast node: Var "service" +// @ast node: Var "mockClient" // @ast node: Import "import-imports-srctestingswiftmodernapptestsmodernapptestsprofiletestsswift-0" final class ProfileTests: XCTestCase { diff --git a/cli/tests/cli/swift.rs b/cli/tests/cli/swift.rs index 55fb53238..29034024a 100644 --- a/cli/tests/cli/swift.rs +++ b/cli/tests/cli/swift.rs @@ -22,13 +22,13 @@ fn api_swift_contains_exact_named_nodes() { let out = run_stakgraph(&[&file]); assert_eq!(out.exit_code, 0); - assert_eq!(out.stdout.contains("Class: API (26-141)"), true); + assert_eq!(out.stdout.contains("Class: API (29-144)"), true); assert_eq!( - out.stdout.contains("Function: API.getPeopleList (77-100)"), + out.stdout.contains("Function: API.getPeopleList (80-103)"), true ); - assert_eq!(out.stdout.contains("Request: GET /people (83-87)"), true); - assert_eq!(out.stdout.contains("Request: POST /person (118-122)"), true); + assert_eq!(out.stdout.contains("Request: GET /people (86-90)"), true); + assert_eq!(out.stdout.contains("Request: POST /person (121-125)"), true); } // ── parse ───────────────────────────────────────────────────────────────────── @@ -40,7 +40,7 @@ fn parse_stats_swift_dir() { assert_eq!(out.exit_code, 0, "stderr: {}", out.stderr); assert!(out.stdout.contains("Class 26"), "stdout: {}", out.stdout); - assert!(out.stdout.contains("Function 36"), "stdout: {}", out.stdout); + assert!(out.stdout.contains("Function 37"), "stdout: {}", out.stdout); assert!(out.stdout.contains("UnitTest 5"), "stdout: {}", out.stdout); } From cfbd8fac8950675078ef300bad2494b1beec69d9 Mon Sep 17 00:00:00 2001 From: kelmith Date: Mon, 27 Jul 2026 13:14:19 +0530 Subject: [PATCH 3/3] style: trim comments in Swift query stack overhaul --- ast/src/lang/parse/collect.rs | 11 ++--------- ast/src/lang/queries/mod.rs | 7 ++----- ast/src/lang/queries/swift.rs | 15 ++++++--------- 3 files changed, 10 insertions(+), 23 deletions(-) diff --git a/ast/src/lang/parse/collect.rs b/ast/src/lang/parse/collect.rs index 5073f629c..9be6f89db 100644 --- a/ast/src/lang/parse/collect.rs +++ b/ast/src/lang/parse/collect.rs @@ -42,9 +42,6 @@ impl Lang { Ok(res) } - /// For a class_definition_query match, return (name, declaration_kind). - /// `declaration_kind` is `None` for grammars that don't distinguish - /// declaration forms at the node-kind level (see `class_declaration_kind`). fn class_match_name_and_kind( &self, q: &Query, @@ -73,12 +70,8 @@ impl Lang { ) -> Result)>> { let tree = self.parse(code, &NodeType::Class)?; - // Some grammars reuse one node kind for several declaration forms - // (e.g. Swift's `class_declaration` covers class/struct/enum/extension/ - // actor). Pre-scan this file's matches so an `extension Foo { ... }` - // block doesn't create a redundant Class node when `Foo` is already - // declared (class/struct/enum) elsewhere in the same file — its - // methods still get attributed to the real node via find_function_parent. + // Avoid a redundant node for e.g. Swift's `extension Foo { ... }` when `Foo` is + // already declared elsewhere in the same file. let mut names_with_primary_decl: HashSet = HashSet::new(); { let mut cursor = QueryCursor::new(); diff --git a/ast/src/lang/queries/mod.rs b/ast/src/lang/queries/mod.rs index c0f7f27f7..df275fdc9 100644 --- a/ast/src/lang/queries/mod.rs +++ b/ast/src/lang/queries/mod.rs @@ -82,11 +82,8 @@ pub trait Stack { None } fn class_definition_query(&self) -> String; - // For grammars that reuse one node kind for several declaration forms (e.g. - // Swift's `class_declaration` covers class/struct/enum/extension/actor), - // return a discriminator string (e.g. "extension") for a CLASS_DEFINITION - // node so `collect_classes` can suppress a redundant node when another - // declaration of the same name already exists in the same file. + // Discriminator (e.g. "extension") for a CLASS_DEFINITION node, for grammars + // that reuse one node kind for several declaration forms. fn class_declaration_kind(&self, _node: TreeNode, _code: &str) -> Option { None } diff --git a/ast/src/lang/queries/swift.rs b/ast/src/lang/queries/swift.rs index 259f15a46..1e4e32eb9 100644 --- a/ast/src/lang/queries/swift.rs +++ b/ast/src/lang/queries/swift.rs @@ -98,10 +98,9 @@ impl Stack for Swift { } fn implements_query(&self) -> Option { - // `class Foo: Bar, Codable, ObservableObject` — Swift's grammar doesn't - // distinguish a superclass from a protocol conformance here (both are - // `inheritance_specifier`), so every entry is treated uniformly as a - // conformance/inheritance relationship. + // Swift's grammar doesn't distinguish a superclass from a protocol + // conformance (both are `inheritance_specifier`), so treat every entry + // as a conformance/inheritance relationship. Some(format!( r#" (class_declaration @@ -178,11 +177,9 @@ impl Stack for Swift { let query = self.q("name: [(type_identifier)(user_type)] @class-name", &NodeType::Class); let parent_name = query_to_ident(query, p, code)?; - // `class_declaration` covers class/struct/enum/extension/actor in this - // grammar — an `extension Foo { ... }` block re-opening an existing type - // is itself a distinct node, but its methods still belong to the one real - // `Foo`. Resolve by name so every block's methods land on the same node, - // instead of keying the edge to whichever block happens to contain them. + // Resolve by name (not this block's own position) so an + // `extension Foo { ... }` block's methods land on the same + // node as `Foo`'s own declaration. parent_name.and_then(|name| { find_class(&name).map(|(class, source_type)| Operand { source: NodeKeys::new(&class.name, &class.file, class.start),