Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/architecture/language-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ From each supported language:
- Call relationships
- Type relationships
- Documentation comments
- External type references (for compiled languages like C# and Java)

## Performance

Expand Down
8 changes: 8 additions & 0 deletions docs/integrations/agent-guidance.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ Workflow:
3. find_symbol, get_calls, find_callers - Get specific details

Start with semantic search, then narrow with specific queries.

### Understanding ExternalType Symbols

For compiled languages (C#, Java), Codanna tracks references to external types from libraries/assemblies even though their definitions aren't in your source code.

- **ExternalType** symbols represent types from external dependencies (NuGet packages, DLLs, JARs)
- When you find an ExternalType, use `search_symbols` to find all usages across your codebase
- These are references only - the actual type definition is in an external library
```

## Claude Sub Agent
Expand Down
4 changes: 2 additions & 2 deletions docs/user-guide/mcp-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ codanna mcp find_symbol main
codanna mcp find_symbol Parser --json
```

**Returns:** Symbol information including file path, line number, kind, and signature.
**Returns:** Symbol information including file path, line number, kind, and signature. For ExternalType symbols (types from external assemblies/libraries), includes a helpful note explaining they're not defined in source code.

### `search_symbols`

Expand All @@ -42,7 +42,7 @@ Search symbols with full-text fuzzy matching.
**Parameters:**
- `query` (required) - Search query (supports fuzzy matching)
- `limit` - Maximum number of results (default: 10)
- `kind` - Filter by symbol kind (e.g., "Function", "Struct", "Trait")
- `kind` - Filter by symbol kind (e.g., "Function", "Struct", "Trait", "ExternalType")
- `module` - Filter by module path

**Example:**
Expand Down
9 changes: 9 additions & 0 deletions src/mcp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,15 @@ impl CodeIntelligenceServer {
result.push_str(&format!("Signature: {sig}\n"));
}

// Add note for external types
if symbol.kind == crate::SymbolKind::ExternalType {
result.push_str("\nNote: This type is not defined in your source code. ");
result.push_str("It's from an external assembly/library.\n");
result.push_str(
"Use 'search_symbols' to find all usages of this type in your codebase.\n",
);
}

// Add documentation preview
if let Some(doc) = symbol.as_doc_comment() {
let doc_preview: Vec<&str> = doc.lines().take(3).collect();
Expand Down
162 changes: 161 additions & 1 deletion src/parsing/csharp/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use crate::parsing::{
use crate::types::SymbolCounter;
use crate::{FileId, Range, Symbol, SymbolKind, Visibility};
use std::any::Any;
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use tree_sitter::{Language, Node, Parser};

/// C# language parser using tree-sitter
Expand Down Expand Up @@ -61,6 +61,12 @@ pub struct CSharpParser {
parser: Parser,
context: ParserContext,
node_tracker: NodeTrackingState,
/// Track locally defined types (classes, interfaces, structs, enums, records)
/// to distinguish them from external types
local_types: HashSet<String>,
/// Track external type references we've seen
/// Maps type name -> namespace (if known)
external_type_refs: HashMap<String, Option<String>>,
}

impl CSharpParser {
Expand Down Expand Up @@ -105,6 +111,8 @@ impl CSharpParser {
) -> Vec<Symbol> {
// Reset context for each file
self.context = ParserContext::new();
self.local_types.clear();
self.external_type_refs.clear();
let mut symbols = Vec::new();

match self.parser.parse(code, None) {
Expand Down Expand Up @@ -140,6 +148,8 @@ impl CSharpParser {
parser,
context: ParserContext::new(),
node_tracker: NodeTrackingState::new(),
local_types: HashSet::new(),
external_type_refs: HashMap::new(),
})
}

Expand Down Expand Up @@ -447,6 +457,10 @@ impl CSharpParser {
module_path: &str,
) -> Option<Symbol> {
let name = self.extract_type_name(node, code)?;

// Record as locally-defined type
self.record_local_type(&name);

let signature = self.extract_class_signature(node, code);
let doc_comment = self.extract_doc_comment(&node, code);
let visibility = self.determine_visibility(node, code);
Expand Down Expand Up @@ -479,6 +493,10 @@ impl CSharpParser {
module_path: &str,
) -> Option<Symbol> {
let name = self.extract_type_name(node, code)?;

// Record as locally-defined type
self.record_local_type(&name);

let signature = self.extract_interface_signature(node, code);
let doc_comment = self.extract_doc_comment(&node, code);
let visibility = self.determine_visibility(node, code);
Expand Down Expand Up @@ -511,6 +529,10 @@ impl CSharpParser {
module_path: &str,
) -> Option<Symbol> {
let name = self.extract_type_name(node, code)?;

// Record as locally-defined type
self.record_local_type(&name);

let signature = self.extract_struct_signature(node, code);
let doc_comment = self.extract_doc_comment(&node, code);
let visibility = self.determine_visibility(node, code);
Expand Down Expand Up @@ -543,6 +565,10 @@ impl CSharpParser {
module_path: &str,
) -> Option<Symbol> {
let name = self.extract_type_name(node, code)?;

// Record as locally-defined type
self.record_local_type(&name);

let signature = self.extract_enum_signature(node, code);
let doc_comment = self.extract_doc_comment(&node, code);
let visibility = self.determine_visibility(node, code);
Expand Down Expand Up @@ -575,6 +601,10 @@ impl CSharpParser {
module_path: &str,
) -> Option<Symbol> {
let name = self.extract_type_name(node, code)?;

// Record as locally-defined type
self.record_local_type(&name);

let signature = self.extract_record_signature(node, code);
let doc_comment = self.extract_doc_comment(&node, code);
let visibility = self.determine_visibility(node, code);
Expand Down Expand Up @@ -1586,6 +1616,22 @@ impl CSharpParser {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "variable_declaration" {
// Check if the variable declaration has a type node
if let Some(type_node) = child.child_by_field_name("type") {
// Extract and track external type if applicable
if let Some(type_name) = self.extract_type_from_node(type_node, code) {
self.track_external_type_reference(
type_name,
type_node,
code,
file_id,
counter,
symbols,
module_path,
);
}
}

// Extract each variable declarator
let mut var_cursor = child.walk();
for var_child in child.children(&mut var_cursor) {
Expand Down Expand Up @@ -1947,6 +1993,120 @@ impl LanguageParser for CSharpParser {
}
}

impl CSharpParser {
/// Record a locally-defined type (class, interface, struct, enum, record)
fn record_local_type(&mut self, type_name: &str) {
self.local_types.insert(type_name.to_string());
}

/// Check if a type is a C# primitive or well-known framework type
fn is_known_type(type_name: &str) -> bool {
matches!(
type_name,
"bool"
| "byte"
| "sbyte"
| "char"
| "decimal"
| "double"
| "float"
| "int"
| "uint"
| "long"
| "ulong"
| "short"
| "ushort"
| "object"
| "string"
| "void"
| "var"
| "dynamic"
| "String"
| "Object"
| "Int32"
| "Int64"
| "Boolean"
)
}

/// Extract type name from a type node (handles identifiers, generic names, qualified names)
fn extract_type_from_node<'a>(&self, type_node: Node, code: &'a str) -> Option<&'a str> {
match type_node.kind() {
"identifier" => Some(&code[type_node.byte_range()]),
"generic_name" => {
// For generic types like List<T>, extract just "List"
if let Some(ident) = type_node.child_by_field_name("name") {
Some(&code[ident.byte_range()])
} else {
None
}
}
"qualified_name" => {
// For qualified names like System.String, extract the last part
let mut cursor = type_node.walk();
let mut last_ident = None;
for child in type_node.children(&mut cursor) {
if child.kind() == "identifier" {
last_ident = Some(&code[child.byte_range()]);
}
}
last_ident
}
_ => None,
}
}

/// Track and potentially create an external type symbol
fn track_external_type_reference(
&mut self,
type_name: &str,
type_node: Node,
_code: &str,
file_id: FileId,
counter: &mut SymbolCounter,
symbols: &mut Vec<Symbol>,
module_path: &str,
) {
// Skip if it's a primitive or well-known type
if Self::is_known_type(type_name) {
return;
}

// Skip if it's a locally-defined type
if self.local_types.contains(type_name) {
return;
}

// Skip if we've already tracked this external type
if self.external_type_refs.contains_key(type_name) {
return;
}

// Mark as tracked
self.external_type_refs.insert(type_name.to_string(), None);

// Create an ExternalType symbol
let symbol = self.create_symbol(
counter.next_id(),
type_name.to_string(),
SymbolKind::ExternalType,
file_id,
Range::new(
type_node.start_position().row as u32 + 1,
type_node.start_position().column as u16,
type_node.end_position().row as u32 + 1,
type_node.end_position().column as u16,
),
Some(format!("external type: {type_name}")),
None,
module_path,
Visibility::Public, // External types are assumed public
);

symbols.push(symbol);
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
8 changes: 7 additions & 1 deletion src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ pub enum SymbolKind {
Parameter,
TypeAlias,
Macro,
/// External type reference (not defined in source code)
/// Used primarily for tracking references to types from external assemblies/libraries
/// in compiled languages like C# and Java
ExternalType,
}

impl SymbolId {
Expand Down Expand Up @@ -135,6 +139,7 @@ impl FromStr for SymbolKind {
"Parameter" => Ok(SymbolKind::Parameter),
"TypeAlias" => Ok(SymbolKind::TypeAlias),
"Macro" => Ok(SymbolKind::Macro),
"ExternalType" => Ok(SymbolKind::ExternalType),
_ => Err("Unknown symbol kind"),
}
}
Expand Down Expand Up @@ -216,9 +221,10 @@ mod tests {
SymbolKind::Parameter,
SymbolKind::TypeAlias,
SymbolKind::Macro,
SymbolKind::ExternalType,
];

assert_eq!(kinds.len(), 14);
assert_eq!(kinds.len(), 15);
}

#[test]
Expand Down
1 change: 1 addition & 0 deletions src/vector/embedding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,7 @@ pub fn create_symbol_text(
crate::types::SymbolKind::Class => "class",
crate::types::SymbolKind::Field => "field",
crate::types::SymbolKind::Parameter => "parameter",
crate::types::SymbolKind::ExternalType => "external_type",
};

if let Some(sig) = signature {
Expand Down
Loading