Skip to content
Merged
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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ uses [Semantic Versioning](https://semver.org/).
providers (including `@riverpod` codegen), and GoRouter routes link to their
screens. Generated `*.g.dart`/`*.freezed.dart` files and `.dart_tool/` are
ignored, and Dart modules get Flutter-specific documentation guidance.
- **Go analyzer.** Functions, methods, structs, interfaces (and their method
signatures), and other defined types (`type Kind int`) become components.
Methods are keyed by their receiver type (`Buffer.Push`, pointer or value
receiver alike), whichever file of the package declares them. Edges cover
calls (plain, `pkg.F()`, and method calls on receivers, parameters and
locals whose type can be inferred), struct field and embedded types,
composite literals, types named in signatures and local `var`s, and types
that implement an interface of their own package. Names resolve inside the
package (directory) they name; imports of the repository's own packages map
to directories through the module path in `go.mod` (and local `replace`
directives), and standard-library or third-party calls are dropped.
`_test.go` files and `vendor/` are skipped.

### Changed

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ Full list in the [CHANGELOG](./CHANGELOG.md).
## Features

**Languages.** Python, Java, JavaScript, TypeScript, C, C++, C#, Kotlin,
PHP, Ruby, Scala, Rust, Dart (Flutter-aware).
PHP, Ruby, Scala, Rust, Dart (Flutter-aware), Go.

**Hierarchical decomposition.** The dependency graph is clustered into a
module tree, recursively, so a 1.4M-line repository gets the same treatment
Expand Down
2 changes: 1 addition & 1 deletion codewiki/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def cli(ctx):

AI agents write the documentation from your dependency graph. Supports
Python, Java, JavaScript, TypeScript, C, C++, C#, Kotlin, PHP, Ruby,
Scala, Rust, and Dart.
Scala, Rust, Dart, and Go.
"""
# Ensure context object exists
ctx.ensure_object(dict)
Expand Down
3 changes: 2 additions & 1 deletion codewiki/cli/utils/repo_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
".sc", # Scala Scripts
".rs", # Rust
".dart", # Dart
".go", # Go
}


Expand Down Expand Up @@ -66,7 +67,7 @@ def validate_repository(repo_path: Path) -> tuple[Path, list[tuple[str, int]]]:
raise RepositoryError(
f"No supported code files found in {repo_path}\n\n"
"CodeWiki supports: Python, Java, JavaScript, TypeScript, C, C++, C#, Kotlin, PHP, "
"Ruby, Scala, Rust, Dart\n\n"
"Ruby, Scala, Rust, Dart, Go\n\n"
"Please navigate to a code repository or specify a custom directory:\n"
" cd /path/to/your/project\n"
" codewiki generate"
Expand Down
1 change: 1 addition & 0 deletions codewiki/cli/utils/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ def detect_supported_languages(directory: Path) -> list[tuple[str, int]]:
"Scala": [".scala", ".sc"],
"Rust": [".rs"],
"Dart": [".dart"],
"Go": [".go"],
}

# Directories to exclude from counting
Expand Down
1 change: 1 addition & 0 deletions codewiki/mcp/tools/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ def _detect_via_mtime(
".sc",
".rs",
".dart",
".go",
}

changed: list[str] = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ def _filter_supported_languages(self, code_files: list[dict]) -> list[dict]:
Filter code files to only include supported languages.

Supports Python, JavaScript, TypeScript, Java, C#, C, C++, PHP, Ruby, Kotlin,
Scala, Rust, and Dart.
Scala, Rust, Dart, and Go.
"""
supported_languages = {
"python",
Expand Down Expand Up @@ -381,6 +381,7 @@ def _get_supported_languages(self) -> list[str]:
"scala",
"rust",
"dart",
"go",
]

def _cleanup_repository(self, temp_dir: str):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ def __init__(self):
self._dart_visible: dict[str, set[str]] = {}
self._dart_library_members: dict[str, set[str]] = {}
self._dart_unscoped = False
self._go_visible: dict[str, set[str]] = {}
self._go_index = None
self.call_relationships: list[CallRelationship] = []
self._python_project_modules: set = set()
self._python_external_import_roots: set = set()
Expand Down Expand Up @@ -100,6 +102,8 @@ def analyze_code_files(self, code_files: list[dict], base_dir: str) -> dict:
self._dart_visible = {}
self._dart_library_members = {}
self._dart_unscoped = False
self._go_visible = {}
self._go_index = None

files_analyzed = 0
files_failed = 0
Expand Down Expand Up @@ -281,6 +285,8 @@ def _analyze_code_file(self, repo_dir: str, file_info: dict):
self._analyze_rust_file(file_path, content, repo_dir)
elif language == "dart":
self._analyze_dart_file(file_path, content, repo_dir)
elif language == "go":
self._analyze_go_file(file_path, content, repo_dir)
# else:
# logger.warning(
# f"Unsupported language for call graph analysis: {language} for file {file_path}"
Expand Down Expand Up @@ -606,6 +612,47 @@ def _analyze_dart_file(self, file_path: str, content: str, repo_dir: str):
except Exception:
logger.exception(f"Failed to analyze Dart file {file_path}")

def _analyze_go_file(self, file_path: str, content: str, repo_dir: str):
"""
Analyze Go file using tree-sitter based analyzer.

Besides components and relationships, records the package directories
the file can see (its own and the repository packages it imports) for
Go package-scoped resolution.

Args:
file_path: Path to the Go file
content: File content string
repo_dir: Repository base directory
"""
from codewiki.src.be.dependency_analyzer.analyzers.go import TreeSitterGoAnalyzer

try:
analyzer = TreeSitterGoAnalyzer(str(file_path), content, repo_path=repo_dir)

for func in analyzer.nodes:
func_id = func.id if func.id else f"{file_path}:{func.name}"
self.functions[func_id] = func

self.call_relationships.extend(analyzer.call_relationships)
self._go_visible[analyzer.relative_path] = analyzer.visible_dirs
except Exception:
logger.exception(f"Failed to analyze Go file {file_path}")

def _resolve_go_callee(self, callee: str, caller: Node) -> str | None:
"""Go names resolve inside the package they name (a directory), or,
for a method on a value of unknown type, among the packages the
caller's file can see."""
from codewiki.src.be.dependency_analyzer.analyzers.go import (
GoPackageIndex,
resolve_go_callee,
)

if self._go_index is None:
self._go_index = GoPackageIndex(self.functions)
caller_file = Path(caller.relative_path).as_posix()
return resolve_go_callee(callee, caller, self._go_index, self._go_visible.get(caller_file))

def _build_dart_scopes(self, base_dir: str) -> None:
"""Resolve recorded Dart directives into per-file visibility scopes."""
from codewiki.src.be.dependency_analyzer.analyzers.dart_imports import (
Expand Down Expand Up @@ -723,6 +770,7 @@ def _resolve_call_relationships(self):
func_info.language = CODE_EXTENSIONS.get(file_ext)

indexes = self._build_resolution_indexes()
self._go_index = None

resolved_count = 0
for relationship in self.call_relationships:
Expand All @@ -740,20 +788,27 @@ def _resolve_call_relationships(self):
relationship
for relationship in self.call_relationships
if relationship.is_resolved
or not self._is_unresolvable_dart(relationship)
or not self._has_authoritative_resolution(relationship)
and not self._is_external_callee(
self._caller_language(relationship.caller),
relationship.callee,
dotted_packages,
)
]

def _is_unresolvable_dart(self, relationship: CallRelationship) -> bool:
"""A Dart caller's callee that Dart resolution rejected is external or
invisible; drop it so ast_parser's name-only fallback cannot bind it
to an unrelated component."""
if any(func.language == "go" for func in self.functions.values()):
from codewiki.src.be.dependency_analyzer.analyzers.go import link_go_interfaces

# Go types implement interfaces implicitly: link them once every
# method of the package is known.
self.call_relationships.extend(link_go_interfaces(self.functions))

def _has_authoritative_resolution(self, relationship: CallRelationship) -> bool:
"""A Dart or Go caller's callee that the language's scoped resolution
rejected is external or invisible; drop it so ast_parser's name-only
fallback cannot bind it to an unrelated component."""
caller = self.functions.get(relationship.caller)
return caller is not None and caller.language == "dart"
return caller is not None and caller.language in ("dart", "go")

def _dotted_project_packages(self) -> dict[str, set]:
"""Project packages/namespaces, partitioned by language. Java and C#
Expand Down Expand Up @@ -893,6 +948,9 @@ def _resolve_callee(
# Dart resolution is authoritative: it never falls back to name
# guessing in other languages or invisible libraries.
return self._resolve_dart_callee(relationship.callee, caller, lang_indexes)
if caller_language == "go" and caller is not None:
# So is Go's: a name resolves only in the package it names.
return self._resolve_go_callee(relationship.callee, caller)
if lang_indexes:
match = self._resolve_callee_in(
relationship, lang_indexes["exact"], lang_indexes["simple"]
Expand Down Expand Up @@ -1034,6 +1092,8 @@ def _generate_visualization_data(self) -> dict:
node_classes.append("lang-rust")
elif file_ext == ".dart":
node_classes.append("lang-dart")
elif file_ext == ".go":
node_classes.append("lang-go")

cytoscape_elements.append(
{
Expand Down
Loading
Loading