Skip to content

feat(lang): add Swift grammar support #648

Description

@clouatre

Summary

Add Swift language support to aptu-coder using the tree-sitter-swift grammar. This enables the four core MCP tools (analyze_directory, analyze_file, analyze_symbol, analyze_module) to parse and analyze Swift source files.

Context

Swift is the primary language for iOS, macOS, watchOS, and tvOS development, and has grown in adoption for server-side applications. Adding Swift support extends aptu-coder's reach into the Apple ecosystem and complements existing support for Go, Java, Rust, and other languages.

Acceptance Criteria

  • Cargo dependency: tree-sitter-swift = "0.7.2" added to [workspace.dependencies] in root Cargo.toml
  • Feature flag: lang-swift feature created in crates/aptu-coder-core/Cargo.toml and added to default feature set; dependency declared as optional under [features]
  • Query module: crates/aptu-coder-core/src/languages/swift.rs created with:
    • ELEMENT_QUERY (function declarations, class/struct/actor/enum/extension/protocol type declarations, extraction of both name field and declaration_kind field where relevant)
    • CALL_QUERY (call expressions)
    • REFERENCE_QUERY (simple identifier references)
    • IMPORT_QUERY (import declarations)
    • DEFUSE_QUERY (definition and use sites for variables and assignments)
    • extract_inheritance handler function (walks inheritance_specifier children via inherits_from field to collect superclass and protocol names)
    • SPDX header (Apache-2.0) on the file
  • Extension registry: .swift extension registered in EXTENSION_MAP in lang.rs behind #[cfg(feature = "lang-swift")]; "swift" added to supported_languages()
  • Language info: "swift" arm added in get_language_info() in languages/mod.rs (behind feature gate):
    • name, language (via tree_sitter_swift::LANGUAGE.into()), element_query, call_query, reference_query, import_query, defuse_query fields set
    • impl_query, impl_trait_query, extract_function_name, find_method_for_receiver, find_receiver_type set to None
    • extract_inheritance set to Some(swift::extract_inheritance)
  • Module registration: mod swift; added to languages/mod.rs behind #[cfg(feature = "lang-swift")]
  • Unit tests: Created in swift.rs under #[cfg(all(test, feature = "lang-swift"))] with parse_swift(source) helper:
    • Free function declaration and extraction
    • Method inside class and extraction
    • Struct declaration (same class_declaration node, declaration_kind = "struct")
    • Protocol declaration (separate protocol_declaration node)
    • Enum declaration (class_declaration with declaration_kind = "enum")
    • Extension declaration (class_declaration with declaration_kind = "extension")
    • Function call symbol extraction
    • Import statement parsing
    • Single superclass inheritance
    • Multiple protocol conformance
    • Mixed superclass and protocol inheritance
  • All tests pass: cargo test --features lang-swift
  • Linting passes: cargo clippy -- -D warnings with lang-swift feature

Implementation Notes

Grammar crate: tree-sitter-swift 0.7.2 (alex-pinkus/tree-sitter-swift). Its normal (non-dev) dependency is tree-sitter-language ^0.1.0 only; tree-sitter itself is a dev-only dependency. This means adding tree-sitter-swift introduces no version conflict with the workspace tree-sitter = "0.26.6".

Entry point: tree_sitter_swift::LANGUAGE.into() -- same pattern as all other languages in mod.rs.

There is no get_ts_language() function: The grammar entry point is the language field inside LanguageInfo, set inline in the get_language_info() match arm.

defuse_query is required: LanguageInfo.defuse_query is a mandatory struct field (typed Option<&'static str>). Every language in the registry sets it. Define a DEFUSE_QUERY constant in swift.rs covering local variable/constant declarations, assignments, and identifier reads. Model after java.rs.

Reference handler: Model after java.rs -- Swift shares JVM-inspired structural patterns (named method/function declarations, type inheritance, imports).

Key node kinds (from grammar.js and node-types.json):

  • function_declaration -- free functions and methods; name field holds the identifier
  • class_declaration -- covers class, struct, actor, enum, and extension via the declaration_kind field (literal string: "class", "struct", "actor", "enum", "extension"); name field holds a type_identifier
  • protocol_declaration -- separate node from class_declaration; name field holds a type_identifier
  • import_declaration -- module imports; no named fields; child identifier holds the module path
  • call_expression -- function and method calls
  • simple_identifier -- symbol references
  • inheritance_specifier -- one base type per node; has inherits_from field pointing to a user_type node; multiple specifiers are siblings under _inheritance_specifiers (comma-separated in source)

ELEMENT_QUERY guidance: Capture function_declaration with its name field as @function/@method_name. Capture class_declaration with its name field as @class/@class_name. Capture protocol_declaration similarly. A single pattern per node kind is sufficient; the declaration_kind field distinguishes struct/enum/extension at runtime if needed.

Inheritance walking: Walk inheritance_specifier descendants; for each, read the inherits_from field, then find the type_identifier or user_type text to get the name. There is no constructor-call vs. interface distinction in Swift inheritance syntax (superclass and protocols both appear the same way in _inheritance_specifiers).

Extension declarations: extension TypeName { ... } is represented as class_declaration with declaration_kind = "extension". The name field contains the extended type identifier, making it work automatically with the same ELEMENT_QUERY pattern used for class/struct.

No impl blocks: Swift has no separate impl block construct; set impl_query and impl_trait_query to None.

No struct_declaration or extension_declaration node types exist in this grammar -- both are represented via class_declaration with different declaration_kind values. The issue's earlier reference to struct_declaration and extension_declaration as separate node kinds was incorrect.

Not In Scope

  • Extraction of async/await or throws modifiers
  • Property wrappers (@State, @Published, etc.)
  • SwiftUI view body parsing or DSL-aware analysis
  • Macro expansion or custom operator definitions
  • actor isolation and concurrency annotation extraction

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions