This is page 12 of 23. Use http://codebase.md/shashankss1205/codegraphcontext?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .cgcignore
├── .dockerignore
├── .env.example
├── .github
│ ├── FUNDING.yml
│ └── workflows
│ ├── docker-publish.yml
│ ├── e2e-tests.yml
│ ├── post_discord_invite.yml
│ ├── test.yml
│ └── update-contributors.yml
├── .gitignore
├── CLI_Commands.md
├── CONTRIBUTING.md
├── contributors.md
├── deploy-production.sh
├── DEPLOYMENT_CHECKLIST.md
├── DOCKER_COMPLETE_GUIDE.md
├── DOCKER_DEPLOYMENT.md
├── DOCKER_README.md
├── docker-compose.template.yml
├── docker-quickstart.sh
├── Dockerfile
├── docs
│ ├── docs
│ │ ├── architecture.md
│ │ ├── cli.md
│ │ ├── contributing_languages.md
│ │ ├── contributing.md
│ │ ├── cookbook.md
│ │ ├── core.md
│ │ ├── future_work.md
│ │ ├── images
│ │ │ ├── 1.png
│ │ │ ├── 11.png
│ │ │ ├── 12.png
│ │ │ ├── 13.png
│ │ │ ├── 14.png
│ │ │ ├── 16.png
│ │ │ ├── 19.png
│ │ │ ├── 2.png
│ │ │ ├── 20.png
│ │ │ ├── 21.png
│ │ │ ├── 22.png
│ │ │ ├── 23.png
│ │ │ ├── 24.png
│ │ │ ├── 26.png
│ │ │ ├── 28.png
│ │ │ ├── 29.png
│ │ │ ├── 3.png
│ │ │ ├── 30.png
│ │ │ ├── 31.png
│ │ │ ├── 32.png
│ │ │ ├── 33.png
│ │ │ ├── 34.png
│ │ │ ├── 35.png
│ │ │ ├── 36.png
│ │ │ ├── 38.png
│ │ │ ├── 39.png
│ │ │ ├── 4.png
│ │ │ ├── 40.png
│ │ │ ├── 41.png
│ │ │ ├── 42.png
│ │ │ ├── 43.png
│ │ │ ├── 44.png
│ │ │ ├── 5.png
│ │ │ ├── 6.png
│ │ │ ├── 7.png
│ │ │ ├── 8.png
│ │ │ ├── 9.png
│ │ │ ├── Indexing.gif
│ │ │ ├── tool_images
│ │ │ │ ├── 1.png
│ │ │ │ ├── 2.png
│ │ │ │ └── 3.png
│ │ │ └── Usecase.gif
│ │ ├── index.md
│ │ ├── installation.md
│ │ ├── license.md
│ │ ├── server.md
│ │ ├── tools.md
│ │ ├── troubleshooting.md
│ │ ├── use_cases.md
│ │ └── watching.md
│ ├── mkdocs.yml
│ └── site
│ ├── 404.html
│ ├── architecture
│ │ └── index.html
│ ├── assets
│ │ ├── images
│ │ │ └── favicon.png
│ │ ├── javascripts
│ │ │ ├── bundle.79ae519e.min.js
│ │ │ ├── bundle.79ae519e.min.js.map
│ │ │ ├── lunr
│ │ │ │ ├── min
│ │ │ │ │ ├── lunr.ar.min.js
│ │ │ │ │ ├── lunr.da.min.js
│ │ │ │ │ ├── lunr.de.min.js
│ │ │ │ │ ├── lunr.du.min.js
│ │ │ │ │ ├── lunr.el.min.js
│ │ │ │ │ ├── lunr.es.min.js
│ │ │ │ │ ├── lunr.fi.min.js
│ │ │ │ │ ├── lunr.fr.min.js
│ │ │ │ │ ├── lunr.he.min.js
│ │ │ │ │ ├── lunr.hi.min.js
│ │ │ │ │ ├── lunr.hu.min.js
│ │ │ │ │ ├── lunr.hy.min.js
│ │ │ │ │ ├── lunr.it.min.js
│ │ │ │ │ ├── lunr.ja.min.js
│ │ │ │ │ ├── lunr.jp.min.js
│ │ │ │ │ ├── lunr.kn.min.js
│ │ │ │ │ ├── lunr.ko.min.js
│ │ │ │ │ ├── lunr.multi.min.js
│ │ │ │ │ ├── lunr.nl.min.js
│ │ │ │ │ ├── lunr.no.min.js
│ │ │ │ │ ├── lunr.pt.min.js
│ │ │ │ │ ├── lunr.ro.min.js
│ │ │ │ │ ├── lunr.ru.min.js
│ │ │ │ │ ├── lunr.sa.min.js
│ │ │ │ │ ├── lunr.stemmer.support.min.js
│ │ │ │ │ ├── lunr.sv.min.js
│ │ │ │ │ ├── lunr.ta.min.js
│ │ │ │ │ ├── lunr.te.min.js
│ │ │ │ │ ├── lunr.th.min.js
│ │ │ │ │ ├── lunr.tr.min.js
│ │ │ │ │ ├── lunr.vi.min.js
│ │ │ │ │ └── lunr.zh.min.js
│ │ │ │ ├── tinyseg.js
│ │ │ │ └── wordcut.js
│ │ │ └── workers
│ │ │ ├── search.2c215733.min.js
│ │ │ └── search.2c215733.min.js.map
│ │ └── stylesheets
│ │ ├── main.484c7ddc.min.css
│ │ ├── main.484c7ddc.min.css.map
│ │ ├── palette.ab4e12ef.min.css
│ │ └── palette.ab4e12ef.min.css.map
│ ├── cli
│ │ └── index.html
│ ├── contributing
│ │ └── index.html
│ ├── contributing_languages
│ │ └── index.html
│ ├── cookbook
│ │ └── index.html
│ ├── core
│ │ └── index.html
│ ├── future_work
│ │ └── index.html
│ ├── images
│ │ ├── 1.png
│ │ ├── 11.png
│ │ ├── 12.png
│ │ ├── 13.png
│ │ ├── 14.png
│ │ ├── 16.png
│ │ ├── 19.png
│ │ ├── 2.png
│ │ ├── 20.png
│ │ ├── 21.png
│ │ ├── 22.png
│ │ ├── 23.png
│ │ ├── 24.png
│ │ ├── 26.png
│ │ ├── 28.png
│ │ ├── 29.png
│ │ ├── 3.png
│ │ ├── 30.png
│ │ ├── 31.png
│ │ ├── 32.png
│ │ ├── 33.png
│ │ ├── 34.png
│ │ ├── 35.png
│ │ ├── 36.png
│ │ ├── 38.png
│ │ ├── 39.png
│ │ ├── 4.png
│ │ ├── 40.png
│ │ ├── 41.png
│ │ ├── 42.png
│ │ ├── 43.png
│ │ ├── 44.png
│ │ ├── 5.png
│ │ ├── 6.png
│ │ ├── 7.png
│ │ ├── 8.png
│ │ ├── 9.png
│ │ ├── Indexing.gif
│ │ ├── tool_images
│ │ │ ├── 1.png
│ │ │ ├── 2.png
│ │ │ └── 3.png
│ │ └── Usecase.gif
│ ├── index.html
│ ├── installation
│ │ └── index.html
│ ├── license
│ │ └── index.html
│ ├── search
│ │ └── search_index.json
│ ├── server
│ │ └── index.html
│ ├── sitemap.xml
│ ├── sitemap.xml.gz
│ ├── tools
│ │ └── index.html
│ ├── troubleshooting
│ │ └── index.html
│ ├── use_cases
│ │ └── index.html
│ └── watching
│ └── index.html
├── funding.json
├── HOSTING_COMPARISON.md
├── images
│ ├── 1.png
│ ├── 11.png
│ ├── 12.png
│ ├── 13.png
│ ├── 14.png
│ ├── 16.png
│ ├── 19.png
│ ├── 2.png
│ ├── 20.png
│ ├── 21.png
│ ├── 22.png
│ ├── 23.png
│ ├── 24.png
│ ├── 26.png
│ ├── 28.png
│ ├── 29.png
│ ├── 3.png
│ ├── 30.png
│ ├── 31.png
│ ├── 32.png
│ ├── 33.png
│ ├── 34.png
│ ├── 35.png
│ ├── 36.png
│ ├── 38.png
│ ├── 39.png
│ ├── 4.png
│ ├── 40.png
│ ├── 41.png
│ ├── 42.png
│ ├── 43.png
│ ├── 44.png
│ ├── 5.png
│ ├── 6.png
│ ├── 7.png
│ ├── 8.png
│ ├── 9.png
│ ├── Indexing.gif
│ ├── tool_images
│ │ ├── 1.png
│ │ ├── 2.png
│ │ └── 3.png
│ └── Usecase.gif
├── k8s
│ ├── configmap.yaml
│ ├── deployment.yaml
│ ├── neo4j-deployment.yaml
│ ├── pvc.yaml
│ ├── README.md
│ └── service.yaml
├── LICENSE
├── MANIFEST.in
├── organizer
│ ├── CONTRIBUTING_LANGUAGES.md
│ ├── cookbook.md
│ ├── docs.md
│ ├── language_specific_nodes.md
│ ├── Tools_Exploration.md
│ └── troubleshoot.md
├── pyproject.toml
├── README.md
├── scripts
│ ├── generate_lang_contributors.py
│ ├── post_install_fix.sh
│ ├── test_all_parsers.py
│ └── update_language_parsers.py
├── SECURITY.md
├── src
│ └── codegraphcontext
│ ├── __init__.py
│ ├── __main__.py
│ ├── cli
│ │ ├── __init__.py
│ │ ├── cli_helpers.py
│ │ ├── config_manager.py
│ │ ├── main.py
│ │ ├── setup_macos.py
│ │ └── setup_wizard.py
│ ├── core
│ │ ├── __init__.py
│ │ ├── database_falkordb.py
│ │ ├── database.py
│ │ ├── falkor_worker.py
│ │ ├── jobs.py
│ │ └── watcher.py
│ ├── prompts.py
│ ├── server.py
│ ├── tools
│ │ ├── __init__.py
│ │ ├── advanced_language_query_tool.py
│ │ ├── code_finder.py
│ │ ├── graph_builder.py
│ │ ├── languages
│ │ │ ├── c.py
│ │ │ ├── cpp.py
│ │ │ ├── csharp.py
│ │ │ ├── go.py
│ │ │ ├── java.py
│ │ │ ├── javascript.py
│ │ │ ├── kotlin.py
│ │ │ ├── php.py
│ │ │ ├── python.py
│ │ │ ├── ruby.py
│ │ │ ├── rust.py
│ │ │ ├── scala.py
│ │ │ ├── swift.py
│ │ │ ├── typescript.py
│ │ │ └── typescriptjsx.py
│ │ ├── package_resolver.py
│ │ ├── query_tool_languages
│ │ │ ├── c_toolkit.py
│ │ │ ├── cpp_toolkit.py
│ │ │ ├── csharp_toolkit.py
│ │ │ ├── go_toolkit.py
│ │ │ ├── java_toolkit.py
│ │ │ ├── javascript_toolkit.py
│ │ │ ├── python_toolkit.py
│ │ │ ├── ruby_toolkit.py
│ │ │ ├── rust_toolkit.py
│ │ │ ├── scala_toolkit.py
│ │ │ ├── swift_toolkit.py
│ │ │ └── typescript_toolkit.py
│ │ └── system.py
│ └── utils
│ ├── debug_log.py
│ └── tree_sitter_manager.py
├── tests
│ ├── __init__.py
│ ├── conftest.py
│ ├── sample_project
│ │ ├── advanced_calls.py
│ │ ├── advanced_classes.py
│ │ ├── advanced_classes2.py
│ │ ├── advanced_functions.py
│ │ ├── advanced_imports.py
│ │ ├── async_features.py
│ │ ├── callbacks_decorators.py
│ │ ├── circular1.py
│ │ ├── circular2.py
│ │ ├── class_instantiation.py
│ │ ├── cli_and_dunder.py
│ │ ├── complex_classes.py
│ │ ├── comprehensions_generators.py
│ │ ├── context_managers.py
│ │ ├── control_flow.py
│ │ ├── datatypes.py
│ │ ├── dynamic_dispatch.py
│ │ ├── dynamic_imports.py
│ │ ├── edge_cases
│ │ │ ├── comments_only.py
│ │ │ ├── docstring_only.py
│ │ │ ├── empty.py
│ │ │ ├── hardcoded_secrets.py
│ │ │ ├── long_functions.py
│ │ │ └── syntax_error.py
│ │ ├── function_chains.py
│ │ ├── generators.py
│ │ ├── import_reexports.py
│ │ ├── mapping_calls.py
│ │ ├── module_a.py
│ │ ├── module_b.py
│ │ ├── module_c
│ │ │ ├── __init__.py
│ │ │ ├── submodule1.py
│ │ │ └── submodule2.py
│ │ ├── namespace_pkg
│ │ │ └── ns_module.py
│ │ ├── pattern_matching.py
│ │ └── typing_examples.py
│ ├── sample_project_c
│ │ ├── cgc_sample
│ │ ├── include
│ │ │ ├── config.h
│ │ │ ├── math
│ │ │ │ └── vec.h
│ │ │ ├── module.h
│ │ │ ├── platform.h
│ │ │ └── util.h
│ │ ├── Makefile
│ │ ├── README.md
│ │ └── src
│ │ ├── main.c
│ │ ├── math
│ │ │ └── vec.c
│ │ ├── module.c
│ │ └── util.c
│ ├── sample_project_cpp
│ │ ├── class_features.cpp
│ │ ├── classes.cpp
│ │ ├── control_flow.cpp
│ │ ├── edge_cases.cpp
│ │ ├── enum_struct_union.cpp
│ │ ├── exceptions.cpp
│ │ ├── file_io.cpp
│ │ ├── function_chain.cpp
│ │ ├── function_chain.h
│ │ ├── function_types.cpp
│ │ ├── main.cpp
│ │ ├── main.exe
│ │ ├── namespaces.cpp
│ │ ├── raii_example.cpp
│ │ ├── README.md
│ │ ├── sample_project.exe
│ │ ├── stl_usage.cpp
│ │ ├── templates.cpp
│ │ └── types_variable_assignments.cpp
│ ├── sample_project_csharp
│ │ ├── README.md
│ │ └── src
│ │ └── Example.App
│ │ ├── Attributes
│ │ │ └── CustomAttributes.cs
│ │ ├── Example.App.csproj
│ │ ├── Models
│ │ │ ├── Person.cs
│ │ │ ├── Point.cs
│ │ │ ├── Role.cs
│ │ │ └── User.cs
│ │ ├── OuterClass.cs
│ │ ├── Program.cs
│ │ ├── Services
│ │ │ ├── GreetingService.cs
│ │ │ ├── IGreetingService.cs
│ │ │ └── LegacyService.cs
│ │ └── Utils
│ │ ├── CollectionHelper.cs
│ │ └── FileHelper.cs
│ ├── sample_project_go
│ │ ├── advanced_types.go
│ │ ├── basic_functions.go
│ │ ├── embedded_composition.go
│ │ ├── error_handling.go
│ │ ├── generics.go
│ │ ├── go.mod
│ │ ├── goroutines_channels.go
│ │ ├── interfaces.go
│ │ ├── packages_imports.go
│ │ ├── README.md
│ │ ├── structs_methods.go
│ │ └── util
│ │ └── helpers.go
│ ├── sample_project_java
│ │ ├── out
│ │ │ └── com
│ │ │ └── example
│ │ │ └── app
│ │ │ ├── annotations
│ │ │ │ └── Logged.class
│ │ │ ├── Main.class
│ │ │ ├── misc
│ │ │ │ ├── Outer.class
│ │ │ │ └── Outer$Inner.class
│ │ │ ├── model
│ │ │ │ ├── Role.class
│ │ │ │ └── User.class
│ │ │ ├── service
│ │ │ │ ├── AbstractGreeter.class
│ │ │ │ ├── GreetingService.class
│ │ │ │ └── impl
│ │ │ │ └── GreetingServiceImpl.class
│ │ │ └── util
│ │ │ ├── CollectionUtils.class
│ │ │ └── IOHelper.class
│ │ ├── README.md
│ │ ├── sources.txt
│ │ └── src
│ │ └── com
│ │ └── example
│ │ └── app
│ │ ├── annotations
│ │ │ └── Logged.java
│ │ ├── Main.java
│ │ ├── misc
│ │ │ └── Outer.java
│ │ ├── model
│ │ │ ├── Role.java
│ │ │ └── User.java
│ │ ├── service
│ │ │ ├── AbstractGreeter.java
│ │ │ ├── GreetingService.java
│ │ │ └── impl
│ │ │ └── GreetingServiceImpl.java
│ │ └── util
│ │ ├── CollectionUtils.java
│ │ └── IOHelper.java
│ ├── sample_project_javascript
│ │ ├── arrays.js
│ │ ├── asyncAwait.js
│ │ ├── classes.js
│ │ ├── dom.js
│ │ ├── errorHandling.js
│ │ ├── events.js
│ │ ├── exporter.js
│ │ ├── fetchAPI.js
│ │ ├── fixtures
│ │ │ └── js
│ │ │ └── accessors.js
│ │ ├── functions.js
│ │ ├── importer.js
│ │ ├── objects.js
│ │ ├── promises.js
│ │ ├── README.md
│ │ └── variables.js
│ ├── sample_project_kotlin
│ │ ├── AdvancedClasses.kt
│ │ ├── Annotations.kt
│ │ ├── Coroutines.kt
│ │ ├── EdgeCases.kt
│ │ ├── Functions.kt
│ │ ├── Main.kt
│ │ ├── Properties.kt
│ │ └── User.kt
│ ├── sample_project_misc
│ │ ├── index.html
│ │ ├── README.md
│ │ ├── styles.css
│ │ ├── tables.css
│ │ └── tables.html
│ ├── sample_project_php
│ │ ├── classes_objects.php
│ │ ├── database.php
│ │ ├── edgecases.php
│ │ ├── error_handling.php
│ │ ├── file_handling.php
│ │ ├── functions.php
│ │ ├── generators_iterators.php
│ │ ├── globals_superglobals.php
│ │ ├── Inheritance.php
│ │ ├── interface_traits.php
│ │ └── README.md
│ ├── sample_project_ruby
│ │ ├── class_example.rb
│ │ ├── enumerables.rb
│ │ ├── error_handling.rb
│ │ ├── file_io.rb
│ │ ├── inheritance_example.rb
│ │ ├── main.rb
│ │ ├── metaprogramming.rb
│ │ ├── mixins_example.rb
│ │ ├── module_example.rb
│ │ └── tests
│ │ ├── test_mixins.py
│ │ └── test_sample.rb
│ ├── sample_project_rust
│ │ ├── Cargo.toml
│ │ ├── README.md
│ │ └── src
│ │ ├── basic_functions.rs
│ │ ├── concurrency.rs
│ │ ├── error_handling.rs
│ │ ├── generics.rs
│ │ ├── iterators_closures.rs
│ │ ├── lib.rs
│ │ ├── lifetimes_references.rs
│ │ ├── modules.rs
│ │ ├── smart_pointers.rs
│ │ ├── structs_enums.rs
│ │ └── traits.rs
│ ├── sample_project_scala
│ │ ├── Animals.scala
│ │ ├── Complex.scala
│ │ ├── Functional.scala
│ │ ├── Geometry.scala
│ │ ├── Main.scala
│ │ ├── PackageObject.scala
│ │ ├── Script.sc
│ │ ├── Services.scala
│ │ ├── Shapes.scala
│ │ ├── Utils.scala
│ │ └── Variables.scala
│ ├── sample_project_swift
│ │ ├── Generics.swift
│ │ ├── Main.swift
│ │ ├── README.md
│ │ ├── Shapes.swift
│ │ ├── User.swift
│ │ └── Vehicles.swift
│ ├── sample_project_typescript
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── sample_tsx.tsx
│ │ ├── src
│ │ │ ├── advanced-types.ts
│ │ │ ├── async-promises.ts
│ │ │ ├── classes-inheritance.ts
│ │ │ ├── decorators-metadata.ts
│ │ │ ├── error-validation.ts
│ │ │ ├── functions-generics.ts
│ │ │ ├── index.ts
│ │ │ ├── modules-namespaces.ts
│ │ │ ├── types-interfaces.ts
│ │ │ └── utilities-helpers.ts
│ │ └── tsconfig.json
│ ├── test_cpp_parser.py
│ ├── test_database_validation.py
│ ├── test_end_to_end.py
│ ├── test_graph_indexing_js.py
│ ├── test_graph_indexing.py
│ ├── test_kotlin_parser.py
│ ├── test_swift_parser.py
│ ├── test_tree_sitter
│ │ ├── __init__.py
│ │ ├── class_instantiation.py
│ │ ├── complex_classes.py
│ │ └── test_file.py
│ ├── test_tree_sitter_manager.py
│ └── test_typescript_parser.py
├── visualize_graph.py
├── website
│ ├── .example.env
│ ├── .gitignore
│ ├── api
│ │ └── pypi.ts
│ ├── bun.lockb
│ ├── components.json
│ ├── eslint.config.js
│ ├── index.html
│ ├── package-lock.json
│ ├── package.json
│ ├── postcss.config.js
│ ├── public
│ │ ├── favicon.ico
│ │ ├── placeholder.svg
│ │ └── robots.txt
│ ├── README.md
│ ├── src
│ │ ├── App.css
│ │ ├── App.tsx
│ │ ├── assets
│ │ │ ├── function-calls.png
│ │ │ ├── graph-total.png
│ │ │ ├── hero-graph.jpg
│ │ │ └── hierarchy.png
│ │ ├── components
│ │ │ ├── ComparisonTable.tsx
│ │ │ ├── CookbookSection.tsx
│ │ │ ├── DemoSection.tsx
│ │ │ ├── ExamplesSection.tsx
│ │ │ ├── FeaturesSection.tsx
│ │ │ ├── Footer.tsx
│ │ │ ├── HeroSection.tsx
│ │ │ ├── InstallationSection.tsx
│ │ │ ├── MoveToTop.tsx
│ │ │ ├── ShowDownloads.tsx
│ │ │ ├── ShowStarGraph.tsx
│ │ │ ├── SocialMentionsTimeline.tsx
│ │ │ ├── TestimonialSection.tsx
│ │ │ ├── ThemeProvider.tsx
│ │ │ ├── ThemeToggle.tsx
│ │ │ └── ui
│ │ │ ├── accordion.tsx
│ │ │ ├── alert-dialog.tsx
│ │ │ ├── alert.tsx
│ │ │ ├── aspect-ratio.tsx
│ │ │ ├── avatar.tsx
│ │ │ ├── badge.tsx
│ │ │ ├── breadcrumb.tsx
│ │ │ ├── button.tsx
│ │ │ ├── calendar.tsx
│ │ │ ├── card.tsx
│ │ │ ├── carousel.tsx
│ │ │ ├── chart.tsx
│ │ │ ├── checkbox.tsx
│ │ │ ├── collapsible.tsx
│ │ │ ├── command.tsx
│ │ │ ├── context-menu.tsx
│ │ │ ├── dialog.tsx
│ │ │ ├── drawer.tsx
│ │ │ ├── dropdown-menu.tsx
│ │ │ ├── form.tsx
│ │ │ ├── hover-card.tsx
│ │ │ ├── input-otp.tsx
│ │ │ ├── input.tsx
│ │ │ ├── label.tsx
│ │ │ ├── menubar.tsx
│ │ │ ├── navigation-menu.tsx
│ │ │ ├── orbiting-circles.tsx
│ │ │ ├── pagination.tsx
│ │ │ ├── popover.tsx
│ │ │ ├── progress.tsx
│ │ │ ├── radio-group.tsx
│ │ │ ├── resizable.tsx
│ │ │ ├── scroll-area.tsx
│ │ │ ├── select.tsx
│ │ │ ├── separator.tsx
│ │ │ ├── sheet.tsx
│ │ │ ├── sidebar.tsx
│ │ │ ├── skeleton.tsx
│ │ │ ├── slider.tsx
│ │ │ ├── sonner.tsx
│ │ │ ├── switch.tsx
│ │ │ ├── table.tsx
│ │ │ ├── tabs.tsx
│ │ │ ├── textarea.tsx
│ │ │ ├── toast.tsx
│ │ │ ├── toaster.tsx
│ │ │ ├── toggle-group.tsx
│ │ │ ├── toggle.tsx
│ │ │ ├── tooltip.tsx
│ │ │ └── use-toast.ts
│ │ ├── hooks
│ │ │ ├── use-mobile.tsx
│ │ │ └── use-toast.ts
│ │ ├── index.css
│ │ ├── lib
│ │ │ └── utils.ts
│ │ ├── main.tsx
│ │ ├── pages
│ │ │ ├── Index.tsx
│ │ │ └── NotFound.tsx
│ │ └── vite-env.d.ts
│ ├── tailwind.config.ts
│ ├── tsconfig.app.json
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ ├── vercel.json
│ └── vite.config.ts
└── windows_setup_guide.md
```
# Files
--------------------------------------------------------------------------------
/src/codegraphcontext/tools/languages/ruby.py:
--------------------------------------------------------------------------------
```python
1 | from pathlib import Path
2 | from typing import Any, Dict, Optional, Tuple
3 | from codegraphcontext.utils.debug_log import debug_log, info_logger, error_logger, warning_logger, debug_logger
4 | from codegraphcontext.utils.tree_sitter_manager import execute_query
5 |
6 | RUBY_QUERIES = {
7 | "functions": """
8 | (method
9 | name: (identifier) @name
10 | ) @function_node
11 | """,
12 | "classes": """
13 | (class
14 | name: (constant) @name
15 | ) @class
16 | """,
17 | "modules": """
18 | (module
19 | name: (constant) @name
20 | ) @module_node
21 | """,
22 | "imports": """
23 | (call
24 | method: (identifier) @method_name
25 | arguments: (argument_list
26 | (string) @path
27 | )
28 | ) @import
29 | """,
30 | "calls": """
31 | (call
32 | receiver: (_)? @receiver
33 | method: (identifier) @name
34 | arguments: (argument_list)? @args
35 | ) @call_node
36 | """,
37 | "variables": """
38 | (assignment
39 | left: (identifier) @name
40 | right: (_) @value
41 | )
42 | (assignment
43 | left: (instance_variable) @name
44 | right: (_) @value
45 | )
46 | """,
47 | "comments": """
48 | (comment) @comment
49 | """,
50 | "module_includes": """
51 | (call
52 | method: (identifier) @method
53 | arguments: (argument_list (constant) @module)
54 | ) @include_call
55 | """,
56 | }
57 |
58 |
59 | class RubyTreeSitterParser:
60 | """A Ruby-specific parser using tree-sitter."""
61 |
62 | def __init__(self, generic_parser_wrapper: Any):
63 | self.generic_parser_wrapper = generic_parser_wrapper
64 | self.language_name = "ruby"
65 | self.language = generic_parser_wrapper.language
66 | self.parser = generic_parser_wrapper.parser
67 |
68 | def _get_node_text(self, node: Any) -> str:
69 | return node.text.decode("utf-8")
70 |
71 | def _enclosing_class_name(self, node: Any) -> Optional[str]:
72 | name, typ, _ = self._get_parent_context(node, ('class',))
73 | return name
74 |
75 | def _find_modules(self, root_node: Any) -> list[Dict[str, Any]]:
76 | modules = []
77 | query_str = RUBY_QUERIES["modules"]
78 | # name via captures
79 | captures = list(execute_query(self.language, query_str, root_node))
80 | for node, cap in captures:
81 | if cap == "module_node":
82 | name = None
83 | for n, c in captures:
84 | if c == "name":
85 | if n.start_byte >= node.start_byte and n.end_byte <= node.end_byte:
86 | name = self._get_node_text(n)
87 | break
88 | if name:
89 | modules.append({
90 | "name": name,
91 | "line_number": node.start_point[0] + 1,
92 | "end_line": node.end_point[0] + 1,
93 | "source": self._get_node_text(node),
94 |
95 | "lang": self.language_name,
96 | "is_dependency": False,
97 | })
98 | return modules
99 |
100 | def _find_module_inclusions(self, root_node: Any) -> list[Dict[str, Any]]:
101 | includes = []
102 | query_str = RUBY_QUERIES["module_includes"]
103 | for node, cap in execute_query(self.language, query_str, root_node):
104 | if cap == "method":
105 | method_name = self._get_node_text(node)
106 | if method_name != "include":
107 | continue
108 | if cap == "include_call":
109 | method = None
110 | module = None
111 | for n, c in execute_query(self.language, query_str, node):
112 | if c == "method":
113 | method = self._get_node_text(n)
114 | elif c == "module":
115 | module = self._get_node_text(n)
116 | if method == "include" and module:
117 | cls = self._enclosing_class_name(node)
118 | if cls:
119 | includes.append({
120 | "class": cls,
121 | "module": module,
122 | "line_number": node.start_point[0] + 1,
123 | "lang": self.language_name,
124 | "is_dependency": False,
125 | })
126 | return includes
127 |
128 |
129 | def _get_parent_context(self, node: Any, types: Tuple[str, ...] = ('class', 'module', 'method')):
130 | """Find parent context for Ruby constructs."""
131 | curr = node.parent
132 | while curr:
133 | if curr.type in types:
134 | name_node = curr.child_by_field_name('name')
135 | if name_node:
136 | return self._get_node_text(name_node), curr.type, curr.start_point[0] + 1
137 | curr = curr.parent
138 | return None, None, None
139 |
140 | def _calculate_complexity(self, node: Any) -> int:
141 | """Calculate cyclomatic complexity for Ruby constructs."""
142 | complexity_nodes = {
143 | "if", "unless", "case", "when", "while", "until", "for", "rescue", "ensure",
144 | "and", "or", "&&", "||", "?", "ternary"
145 | }
146 | count = 1
147 |
148 | def traverse(n):
149 | nonlocal count
150 | if n.type in complexity_nodes:
151 | count += 1
152 | for child in n.children:
153 | traverse(child)
154 |
155 | traverse(node)
156 | return count
157 |
158 | def _get_docstring(self, node: Any) -> Optional[str]:
159 | """Extract comments as docstrings for Ruby constructs."""
160 | # Look for comments before the node
161 | prev_sibling = node.prev_sibling
162 | while prev_sibling and prev_sibling.type in ('comment', '\n', ' '):
163 | if prev_sibling.type == 'comment':
164 | comment_text = self._get_node_text(prev_sibling)
165 | if comment_text.startswith('#') and not comment_text.startswith('#!'):
166 | return comment_text.strip()
167 | prev_sibling = prev_sibling.prev_sibling
168 | return None
169 |
170 | def _parse_method_parameters(self, method_node: Any) -> list[str]:
171 | """Parse method parameters from a method node."""
172 | params = []
173 | # Look for parameters in the method node
174 | for child in method_node.children:
175 | if child.type == 'identifier' and child != method_node.child_by_field_name('name'):
176 | # This is likely a parameter
177 | params.append(self._get_node_text(child))
178 | return params
179 |
180 | def parse(self, file_path: Path, is_dependency: bool = False) -> Dict[str, Any]:
181 | """Parses a Ruby file and returns its structure."""
182 | with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
183 | source_code = f.read()
184 |
185 | tree = self.parser.parse(bytes(source_code, "utf8"))
186 | root_node = tree.root_node
187 |
188 | functions = self._find_functions(root_node)
189 | classes = self._find_classes(root_node)
190 | imports = self._find_imports(root_node)
191 | function_calls = self._find_calls(root_node)
192 | variables = self._find_variables(root_node)
193 | modules = self._find_modules(root_node)
194 | module_inclusions = self._find_module_inclusions(root_node)
195 |
196 | return {
197 | "file_path": str(file_path),
198 | "functions": functions,
199 | "classes": classes,
200 | "variables": variables,
201 | "imports": imports,
202 | "function_calls": function_calls,
203 | "is_dependency": is_dependency,
204 | "lang": self.language_name,
205 | "modules": modules,
206 | "module_inclusions": module_inclusions,
207 | }
208 |
209 | def _find_functions(self, root_node: Any) -> list[Dict[str, Any]]:
210 | """Find all function/method definitions."""
211 | functions = []
212 | query_str = RUBY_QUERIES["functions"]
213 |
214 | # Collect all captures first
215 | all_captures = list(execute_query(self.language, query_str, root_node))
216 |
217 | # Group captures by function node using a different approach
218 | captures_by_function = {}
219 | for node, capture_name in all_captures:
220 | if capture_name == 'function_node':
221 | captures_by_function[id(node)] = {'node': node, 'name': None}
222 |
223 | # Now find names for each function
224 | for node, capture_name in all_captures:
225 | if capture_name == 'name':
226 | # Find which function this name belongs to
227 | for func_id, func_data in captures_by_function.items():
228 | func_node = func_data['node']
229 | # Check if this name node is within the function node
230 | if (node.start_byte >= func_node.start_byte and
231 | node.end_byte <= func_node.end_byte):
232 | captures_by_function[func_id]['name'] = self._get_node_text(node)
233 | break
234 |
235 | # Build function entries
236 | for func_data in captures_by_function.values():
237 | func_node = func_data['node']
238 | name = func_data['name']
239 |
240 | if name:
241 | args = self._parse_method_parameters(func_node)
242 |
243 | # Get context and docstring
244 | context, context_type, _ = self._get_parent_context(func_node)
245 | class_context = context if context_type in ('class', 'module') else None
246 | docstring = self._get_docstring(func_node)
247 |
248 | functions.append({
249 | "name": name,
250 | "line_number": func_node.start_point[0] + 1,
251 | "end_line": func_node.end_point[0] + 1,
252 | "args": args,
253 | "source": self._get_node_text(func_node),
254 |
255 | "docstring": docstring,
256 | "cyclomatic_complexity": self._calculate_complexity(func_node),
257 | "context": context,
258 | "context_type": context_type,
259 | "class_context": class_context,
260 | "decorators": [],
261 | "lang": self.language_name,
262 | "is_dependency": False,
263 | })
264 |
265 | return functions
266 |
267 | def _find_classes(self, root_node: Any) -> list[Dict[str, Any]]:
268 | """Find all class and module definitions."""
269 | classes = []
270 | query_str = RUBY_QUERIES["classes"]
271 |
272 | # Collect all captures first
273 | all_captures = list(execute_query(self.language, query_str, root_node))
274 |
275 | # Group captures by class node using a different approach
276 | captures_by_class = {}
277 | for node, capture_name in all_captures:
278 | if capture_name == 'class':
279 | captures_by_class[id(node)] = {'node': node, 'name': None}
280 |
281 | # Now find names for each class
282 | for node, capture_name in all_captures:
283 | if capture_name == 'name':
284 | # Find which class this name belongs to
285 | for class_id, class_data in captures_by_class.items():
286 | class_node = class_data['node']
287 | # Check if this name node is within the class node
288 | if (node.start_byte >= class_node.start_byte and
289 | node.end_byte <= class_node.end_byte):
290 | captures_by_class[class_id]['name'] = self._get_node_text(node)
291 | break
292 |
293 | # Build class entries
294 | for class_data in captures_by_class.values():
295 | class_node = class_data['node']
296 | name = class_data['name']
297 |
298 | if name:
299 | # Get superclass for inheritance (simplified)
300 | bases = []
301 |
302 | # Get docstring
303 | docstring = self._get_docstring(class_node)
304 |
305 | classes.append({
306 | "name": name,
307 | "line_number": class_node.start_point[0] + 1,
308 | "end_line": class_node.end_point[0] + 1,
309 | "bases": bases,
310 | "source": self._get_node_text(class_node),
311 |
312 | "docstring": docstring,
313 | "context": None,
314 | "decorators": [],
315 | "lang": self.language_name,
316 | "is_dependency": False,
317 | })
318 |
319 | return classes
320 |
321 | def _find_imports(self, root_node: Any) -> list[Dict[str, Any]]:
322 | """Find all require/load statements."""
323 | imports = []
324 | query_str = RUBY_QUERIES["imports"]
325 |
326 | # Collect all captures first
327 | all_captures = list(execute_query(self.language, query_str, root_node))
328 |
329 | # Group captures by import node using a different approach
330 | captures_by_import = {}
331 | for node, capture_name in all_captures:
332 | if capture_name == 'import':
333 | captures_by_import[id(node)] = {'node': node, 'method_name': None, 'path': None}
334 |
335 | # Now find method names and paths for each import
336 | for node, capture_name in all_captures:
337 | if capture_name == 'method_name':
338 | # Find which import this method name belongs to
339 | for import_id, import_data in captures_by_import.items():
340 | import_node = import_data['node']
341 | # Check if this method name node is within the import node
342 | if (node.start_byte >= import_node.start_byte and
343 | node.end_byte <= import_node.end_byte):
344 | captures_by_import[import_id]['method_name'] = self._get_node_text(node)
345 | break
346 | elif capture_name == 'path':
347 | # Find which import this path belongs to
348 | for import_id, import_data in captures_by_import.items():
349 | import_node = import_data['node']
350 | # Check if this path node is within the import node
351 | if (node.start_byte >= import_node.start_byte and
352 | node.end_byte <= import_node.end_byte):
353 | captures_by_import[import_id]['path'] = self._get_node_text(node)
354 | break
355 |
356 | # Build import entries
357 | for import_data in captures_by_import.values():
358 | import_node = import_data['node']
359 | method_name = import_data['method_name']
360 | path = import_data['path']
361 |
362 | if method_name and path:
363 | path = path.strip('\'"')
364 |
365 | # Only process require/load statements
366 | if method_name in ('require', 'require_relative', 'load'):
367 | imports.append({
368 | "name": path,
369 | "full_import_name": f"{method_name} '{path}'",
370 | "line_number": import_node.start_point[0] + 1,
371 | "alias": None,
372 | "lang": self.language_name,
373 | "is_dependency": False,
374 | })
375 |
376 | return imports
377 |
378 | def _find_calls(self, root_node: Any) -> list[Dict[str, Any]]:
379 | """Find all function and method calls."""
380 | calls = []
381 | query_str = RUBY_QUERIES["calls"]
382 |
383 | # Collect all captures
384 | all_captures = list(execute_query(self.language, query_str, root_node))
385 |
386 | # Group by call node
387 | captures_by_call = {}
388 | for node, capture_name in all_captures:
389 | if capture_name == 'call_node':
390 | captures_by_call[id(node)] = {'node': node, 'name': None, 'receiver': None, 'args': []}
391 |
392 | for node, capture_name in all_captures:
393 | for call_id, call_data in captures_by_call.items():
394 | call_node = call_data['node']
395 | if not (node.start_byte >= call_node.start_byte and node.end_byte <= call_node.end_byte):
396 | continue
397 |
398 | if capture_name == 'name':
399 | # The identifier could be part of receiver or arguments too, be careful
400 | # But tree-sitter structure ensures method name is distinct
401 | # Check if node is child 'method' of call_node
402 | if node == call_node.child_by_field_name('method'):
403 | captures_by_call[call_id]['name'] = self._get_node_text(node)
404 |
405 | elif capture_name == 'receiver':
406 | captures_by_call[call_id]['receiver'] = self._get_node_text(node)
407 |
408 | elif capture_name == 'args':
409 | # Capture arguments
410 | args_text = self._get_node_text(node)
411 | # Simple heuristic: split by comma
412 | captures_by_call[call_id]['args'] = [a.strip() for a in args_text.strip("()").split(',') if a.strip()]
413 |
414 | for call_data in captures_by_call.values():
415 | call_node = call_data['node']
416 | name = call_data['name']
417 |
418 | if name:
419 | receiver = call_data['receiver']
420 | full_name = f"{receiver}.{name}" if receiver else name
421 |
422 | context_name, context_type, context_line = self._get_parent_context(call_node)
423 | class_context = context_name if context_type in ('class', 'module') else None
424 | if context_type == 'method':
425 | # If inside a method, try to find enclosing class too
426 | enclosing_class, _, _ = self._get_parent_context(call_node.parent, ('class', 'module'))
427 | class_context = enclosing_class
428 |
429 |
430 | calls.append({
431 | "name": name,
432 | "full_name": full_name,
433 | "line_number": call_node.start_point[0] + 1,
434 | "args": call_data['args'],
435 | "inferred_obj_type": None,
436 | "context": (context_name, context_type, context_line),
437 | "class_context": class_context,
438 | "lang": self.language_name,
439 | "is_dependency": False,
440 | })
441 |
442 | return calls
443 |
444 | def _find_variables(self, root_node: Any) -> list[Dict[str, Any]]:
445 | """Find all variable assignments."""
446 | variables = []
447 | query_str = RUBY_QUERIES["variables"]
448 |
449 | # Group captures by assignment node
450 | captures_by_assignment = {}
451 | for node, capture_name in execute_query(self.language, query_str, root_node):
452 | if capture_name == 'name':
453 | # Find the parent assignment node
454 | current = node.parent
455 | while current and current.type != 'assignment':
456 | current = current.parent
457 | if current:
458 | assignment_id = id(current)
459 | if assignment_id not in captures_by_assignment:
460 | captures_by_assignment[assignment_id] = {'node': current, 'name': None, 'value': None}
461 | captures_by_assignment[assignment_id]['name'] = self._get_node_text(node)
462 | elif capture_name == 'value':
463 | # Find the parent assignment node
464 | current = node.parent
465 | while current and current.type != 'assignment':
466 | current = current.parent
467 | if current:
468 | assignment_id = id(current)
469 | if assignment_id not in captures_by_assignment:
470 | captures_by_assignment[assignment_id] = {'node': current, 'name': None, 'value': None}
471 | captures_by_assignment[assignment_id]['value'] = self._get_node_text(node)
472 |
473 | # Build variable entries
474 | for var_data in captures_by_assignment.values():
475 | name = var_data['name']
476 | value = var_data['value']
477 |
478 | if name:
479 | # Determine variable type based on name prefix
480 | var_type = "local"
481 | if name.startswith("@"):
482 | var_type = "instance"
483 | elif name.startswith("@@"):
484 | var_type = "class"
485 | elif name.startswith("$"):
486 | var_type = "global"
487 |
488 | variables.append({
489 | "name": name,
490 | "line_number": var_data['node'].start_point[0] + 1,
491 | "value": value,
492 | "type": var_type,
493 | "context": None, # Placeholder
494 | "class_context": None, # Placeholder
495 | "lang": self.language_name,
496 | "is_dependency": False,
497 | })
498 |
499 | return variables
500 |
501 |
502 | def pre_scan_ruby(files: list[Path], parser_wrapper) -> dict:
503 | """Scans Ruby files to create a map of class/method names to their file paths."""
504 | imports_map = {}
505 | query_str = """
506 | (class
507 | name: (constant) @name
508 | )
509 | (module
510 | name: (constant) @name
511 | )
512 | (method
513 | name: (identifier) @name
514 | )
515 | """
516 |
517 |
518 | for file_path in files:
519 | try:
520 | with open(file_path, "r", encoding="utf-8") as f:
521 | tree = parser_wrapper.parser.parse(bytes(f.read(), "utf8"))
522 |
523 | for capture, _ in execute_query(parser_wrapper.language, query_str, tree.root_node):
524 | name = capture.text.decode('utf-8')
525 | if name not in imports_map:
526 | imports_map[name] = []
527 | imports_map[name].append(str(file_path.resolve()))
528 | except Exception as e:
529 | warning_logger(f"Tree-sitter pre-scan failed for {file_path}: {e}")
530 |
531 | return imports_map
532 |
```
--------------------------------------------------------------------------------
/src/codegraphcontext/tools/languages/c.py:
--------------------------------------------------------------------------------
```python
1 | from pathlib import Path
2 | from typing import Any, Dict, Optional, Tuple
3 | from codegraphcontext.utils.debug_log import debug_log, info_logger, error_logger, warning_logger
4 | from codegraphcontext.utils.tree_sitter_manager import execute_query
5 |
6 | C_QUERIES = {
7 | "functions": """
8 | (function_definition
9 | declarator: (function_declarator
10 | declarator: (identifier) @name
11 | )
12 | ) @function_node
13 |
14 | (function_definition
15 | declarator: (function_declarator
16 | declarator: (pointer_declarator
17 | declarator: (identifier) @name
18 | )
19 | )
20 | ) @function_node
21 | """,
22 | "structs": """
23 | (struct_specifier
24 | name: (type_identifier) @name
25 | ) @struct
26 | """,
27 | "unions": """
28 | (union_specifier
29 | name: (type_identifier) @name
30 | ) @union
31 | """,
32 | "enums": """
33 | (enum_specifier
34 | name: (type_identifier) @name
35 | ) @enum
36 | """,
37 | "typedefs": """
38 | (type_definition
39 | declarator: (type_identifier) @name
40 | ) @typedef
41 | """,
42 | "imports": """
43 | (preproc_include
44 | path: [
45 | (string_literal) @path
46 | (system_lib_string) @path
47 | ]
48 | ) @import
49 | """,
50 | "calls": """
51 | (call_expression
52 | function: (identifier) @name
53 | )
54 | """,
55 | "variables": """
56 | (declaration
57 | declarator: (init_declarator
58 | declarator: (identifier) @name
59 | )
60 | )
61 |
62 | (declaration
63 | declarator: (init_declarator
64 | declarator: (pointer_declarator
65 | declarator: (identifier) @name
66 | )
67 | )
68 | )
69 |
70 | (declaration
71 | declarator: (identifier) @name
72 | )
73 |
74 | (declaration
75 | declarator: (pointer_declarator
76 | declarator: (identifier) @name
77 | )
78 | )
79 | """,
80 | "macros": """
81 | (preproc_def
82 | name: (identifier) @name
83 | ) @macro
84 | """,
85 | }
86 |
87 | class CTreeSitterParser:
88 | """A C-specific parser using tree-sitter."""
89 |
90 | def __init__(self, generic_parser_wrapper: Any):
91 | self.generic_parser_wrapper = generic_parser_wrapper
92 | self.language_name = "c"
93 | self.language = generic_parser_wrapper.language
94 | self.parser = generic_parser_wrapper.parser
95 |
96 | def _get_node_text(self, node: Any) -> str:
97 | return node.text.decode("utf-8")
98 |
99 | def parse(self, file_path: Path, is_dependency: bool = False) -> Dict[str, Any]:
100 | """Parses a C file and returns its structure."""
101 | with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
102 | source_code = f.read()
103 |
104 | tree = self.parser.parse(bytes(source_code, "utf8"))
105 | root_node = tree.root_node
106 |
107 | functions = self._find_functions(root_node)
108 | classes = self._find_structs_unions_enums(root_node)
109 | imports = self._find_imports(root_node)
110 | function_calls = self._find_calls(root_node)
111 | variables = self._find_variables(root_node)
112 | macros = self._find_macros(root_node)
113 |
114 | return {
115 | "file_path": str(file_path),
116 | "functions": functions,
117 | "classes": classes,
118 | "variables": variables,
119 | "imports": imports,
120 | "function_calls": function_calls,
121 | "macros": macros,
122 | "is_dependency": is_dependency,
123 | "lang": self.language_name,
124 | }
125 |
126 | def _get_parent_context(self, node: Any, types: tuple = ('function_definition', 'struct_specifier', 'union_specifier', 'enum_specifier')) -> tuple:
127 | """Get parent context for nested constructs."""
128 | curr = node.parent
129 | while curr:
130 | if curr.type in types:
131 | if curr.type == 'function_definition':
132 | # Traverse declarator to find name and use its line number
133 | decl = curr.child_by_field_name('declarator')
134 | while decl:
135 | if decl.type == 'identifier':
136 | return self._get_node_text(decl), curr.type, decl.start_point[0] + 1
137 |
138 | # Handle recursive declarators (function, pointer, array, parenthesized)
139 | child = decl.child_by_field_name('declarator')
140 | if child:
141 | decl = child
142 | else:
143 | # Fallback if structure is different
144 | break
145 | else:
146 | name_node = curr.child_by_field_name('name')
147 | if name_node:
148 | return self._get_node_text(name_node), curr.type, name_node.start_point[0] + 1
149 | curr = curr.parent
150 | return None, None, None
151 |
152 | def _calculate_complexity(self, node: Any) -> int:
153 | """Calculate cyclomatic complexity for C functions."""
154 | complexity_nodes = {
155 | "if_statement", "for_statement", "while_statement", "do_statement",
156 | "switch_statement", "case_statement", "conditional_expression",
157 | "logical_expression", "binary_expression", "goto_statement"
158 | }
159 | count = 1
160 |
161 | def traverse(n):
162 | nonlocal count
163 | if n.type in complexity_nodes:
164 | count += 1
165 | for child in n.children:
166 | traverse(child)
167 |
168 | traverse(node)
169 | return count
170 |
171 | def _get_docstring(self, node: Any) -> Optional[str]:
172 | """Extract comments as documentation."""
173 | # Look for comments before the node
174 | if node.parent:
175 | for child in node.parent.children:
176 | if child.type == 'comment' and child.start_point[0] < node.start_point[0]:
177 | return self._get_node_text(child)
178 | return None
179 |
180 | def _parse_function_args(self, params_node: Any) -> list[Dict[str, Any]]:
181 | """Enhanced helper to parse function arguments from a (parameter_list) node."""
182 | args = []
183 | if not params_node:
184 | return args
185 |
186 | for param in params_node.named_children:
187 | if param.type == "parameter_declaration":
188 | arg_info: Dict[str, Any] = {"name": "", "type": None, "is_pointer": False, "is_array": False}
189 |
190 | # Find the declarator (variable name)
191 | declarator = param.child_by_field_name("declarator")
192 | if declarator:
193 | if declarator.type == "identifier":
194 | arg_info["name"] = self._get_node_text(declarator)
195 | elif declarator.type == "pointer_declarator":
196 | arg_info["is_pointer"] = True
197 | inner_declarator = declarator.child_by_field_name("declarator")
198 | if inner_declarator and inner_declarator.type == "identifier":
199 | arg_info["name"] = self._get_node_text(inner_declarator)
200 | elif declarator.type == "array_declarator":
201 | arg_info["is_array"] = True
202 | inner_declarator = declarator.child_by_field_name("declarator")
203 | if inner_declarator and inner_declarator.type == "identifier":
204 | arg_info["name"] = self._get_node_text(inner_declarator)
205 |
206 | # Find the type
207 | type_node = param.child_by_field_name("type")
208 | if type_node:
209 | arg_info["type"] = self._get_node_text(type_node)
210 |
211 | # Handle variadic arguments
212 | if param.type == "variadic_parameter":
213 | arg_info["name"] = "..."
214 | arg_info["type"] = "variadic"
215 |
216 | args.append(arg_info)
217 | return args
218 |
219 | def _find_functions(self, root_node: Any) -> list[Dict[str, Any]]:
220 | functions = []
221 | query_str = C_QUERIES["functions"]
222 | for match in execute_query(self.language, query_str, root_node):
223 | capture_name = match[1]
224 | node = match[0]
225 | if capture_name == 'name':
226 | func_node = node.parent.parent.parent
227 | name = self._get_node_text(node)
228 |
229 | # Find parameters
230 | params_node = None
231 | body_node = None
232 | for child in func_node.children:
233 | if child.type == "function_declarator":
234 | params_node = child.child_by_field_name("parameters")
235 | elif child.type == "compound_statement":
236 | body_node = child
237 |
238 | args = self._parse_function_args(params_node) if params_node else []
239 | context, context_type, _ = self._get_parent_context(func_node)
240 |
241 | functions.append({
242 | "name": name,
243 | "line_number": node.start_point[0] + 1,
244 | "end_line": func_node.end_point[0] + 1,
245 | "args": [arg["name"] for arg in args if arg["name"]], # Simplified args for compatibility
246 | "source": self._get_node_text(func_node),
247 |
248 | "docstring": self._get_docstring(func_node),
249 | "cyclomatic_complexity": self._calculate_complexity(func_node),
250 | "context": context,
251 | "context_type": context_type,
252 | "class_context": None,
253 | "decorators": [],
254 | "lang": self.language_name,
255 | "is_dependency": False,
256 | "detailed_args": args, # Keep detailed args for future use
257 | })
258 | return functions
259 |
260 | def _find_structs_unions_enums(self, root_node: Any) -> list[Dict[str, Any]]:
261 | """Find structs, unions, and enums (treated as classes in C)."""
262 | classes = []
263 |
264 | # Find structs
265 | query_str = C_QUERIES["structs"]
266 | for match in execute_query(self.language, query_str, root_node):
267 | capture_name = match[1]
268 | node = match[0]
269 | if capture_name == 'name':
270 | struct_node = node.parent
271 | name = self._get_node_text(node)
272 | context, context_type, _ = self._get_parent_context(struct_node)
273 |
274 | classes.append({
275 | "name": name,
276 | "line_number": node.start_point[0] + 1,
277 | "end_line": struct_node.end_point[0] + 1,
278 | "bases": [], # C doesn't have inheritance
279 | "source": self._get_node_text(struct_node),
280 | "docstring": self._get_docstring(struct_node),
281 | "context": context,
282 | "decorators": [],
283 | "lang": self.language_name,
284 | "is_dependency": False,
285 | "type": "struct",
286 | })
287 |
288 | # Find unions
289 | query_str = C_QUERIES["unions"]
290 | for match in execute_query(self.language, query_str, root_node):
291 | capture_name = match[1]
292 | node = match[0]
293 | if capture_name == 'name':
294 | union_node = node.parent
295 | name = self._get_node_text(node)
296 | context, context_type, _ = self._get_parent_context(union_node)
297 |
298 | classes.append({
299 | "name": name,
300 | "line_number": node.start_point[0] + 1,
301 | "end_line": union_node.end_point[0] + 1,
302 | "bases": [],
303 | "source": self._get_node_text(union_node),
304 | "docstring": self._get_docstring(union_node),
305 | "context": context,
306 | "decorators": [],
307 | "lang": self.language_name,
308 | "is_dependency": False,
309 | "type": "union",
310 | })
311 |
312 | # Find enums
313 | query_str = C_QUERIES["enums"]
314 | for match in execute_query(self.language, query_str, root_node):
315 | capture_name = match[1]
316 | node = match[0]
317 | if capture_name == 'name':
318 | enum_node = node.parent
319 | name = self._get_node_text(node)
320 | context, context_type, _ = self._get_parent_context(enum_node)
321 |
322 | classes.append({
323 | "name": name,
324 | "line_number": node.start_point[0] + 1,
325 | "end_line": enum_node.end_point[0] + 1,
326 | "bases": [],
327 | "source": self._get_node_text(enum_node),
328 | "docstring": self._get_docstring(enum_node),
329 | "context": context,
330 | "decorators": [],
331 | "lang": self.language_name,
332 | "is_dependency": False,
333 | "type": "enum",
334 | })
335 |
336 | return classes
337 |
338 | def _find_imports(self, root_node: Any) -> list[Dict[str, Any]]:
339 | imports = []
340 | query_str = C_QUERIES["imports"]
341 | for match in execute_query(self.language, query_str, root_node):
342 | capture_name = match[1]
343 | node = match[0]
344 | if capture_name == 'path':
345 | path = self._get_node_text(node).strip('"<>')
346 | context, context_type, _ = self._get_parent_context(node)
347 |
348 | imports.append({
349 | "name": path,
350 | "full_import_name": path,
351 | "line_number": node.start_point[0] + 1,
352 | "alias": None,
353 | "context": context,
354 | "lang": self.language_name,
355 | "is_dependency": False,
356 | })
357 | return imports
358 |
359 | def _find_calls(self, root_node: Any) -> list[Dict[str, Any]]:
360 | """Enhanced function call detection."""
361 | calls = []
362 | query_str = C_QUERIES["calls"]
363 | for match in execute_query(self.language, query_str, root_node):
364 | capture_name = match[1]
365 | node = match[0]
366 | if capture_name == "name":
367 | call_node = node.parent if node.parent.type == "call_expression" else node.parent.parent
368 | call_name = self._get_node_text(node)
369 |
370 | # Extract arguments
371 | args = []
372 | args_node = call_node.child_by_field_name("arguments")
373 | if args_node:
374 | for child in args_node.children:
375 | if child.type not in ['(', ')', ',']:
376 | args.append(self._get_node_text(child))
377 |
378 | context_name, context_type, context_line = self._get_parent_context(call_node)
379 |
380 | # print(f"DEBUG_C_PARSER: Call {call_name} context: {context_name}, {context_type}, {context_line}")
381 |
382 | calls.append({
383 | "name": call_name,
384 | "full_name": call_name, # For C, function name is the same as full name
385 | "line_number": node.start_point[0] + 1,
386 | "args": args,
387 | "inferred_obj_type": None,
388 | "context": (context_name, context_type, context_line),
389 | "class_context": None,
390 | "lang": self.language_name,
391 | "is_dependency": False,
392 | })
393 | return calls
394 |
395 | def _find_variables(self, root_node: Any) -> list[Dict[str, Any]]:
396 | """Enhanced variable declaration detection."""
397 | variables = []
398 | query_str = C_QUERIES["variables"]
399 | for match in execute_query(self.language, query_str, root_node):
400 | capture_name = match[1]
401 | node = match[0]
402 | if capture_name == "name":
403 | var_name = self._get_node_text(node)
404 |
405 | # Find the declaration node
406 | decl_node = node.parent
407 | while decl_node and decl_node.type != "declaration":
408 | decl_node = decl_node.parent
409 |
410 | # Extract type information
411 | var_type = None
412 | is_pointer = False
413 | is_array = False
414 | value = None
415 |
416 | if decl_node:
417 | # Find type
418 | for child in decl_node.children:
419 | if child.type in ["primitive_type", "type_identifier", "sized_type_specifier"]:
420 | var_type = self._get_node_text(child)
421 | elif child.type == "init_declarator":
422 | # Check for pointer/array
423 | if child.child_by_field_name("declarator"):
424 | declarator = child.child_by_field_name("declarator")
425 | if declarator.type == "pointer_declarator":
426 | is_pointer = True
427 | elif declarator.type == "array_declarator":
428 | is_array = True
429 |
430 | # Check for initial value
431 | if child.child_by_field_name("value"):
432 | value = self._get_node_text(child.child_by_field_name("value"))
433 |
434 | context, context_type, _ = self._get_parent_context(node)
435 | class_context, _, _ = self._get_parent_context(node, types=('struct_specifier', 'union_specifier', 'enum_specifier'))
436 |
437 | variables.append({
438 | "name": var_name,
439 | "line_number": node.start_point[0] + 1,
440 | "value": value,
441 | "type": var_type,
442 | "context": context,
443 | "class_context": class_context,
444 | "lang": self.language_name,
445 | "is_dependency": False,
446 | "is_pointer": is_pointer,
447 | "is_array": is_array,
448 | })
449 | return variables
450 |
451 | def _find_macros(self, root_node: Any) -> list[Dict[str, Any]]:
452 | """Enhanced preprocessor macro detection."""
453 | macros = []
454 | query_str = C_QUERIES["macros"]
455 | for match in execute_query(self.language, query_str, root_node):
456 | capture_name = match[1]
457 | node = match[0]
458 | if capture_name == 'name':
459 | macro_node = node.parent
460 | name = self._get_node_text(node)
461 |
462 | # Extract macro value
463 | value = None
464 | if macro_node.child_by_field_name("value"):
465 | value = self._get_node_text(macro_node.child_by_field_name("value"))
466 |
467 | # Extract parameters for function-like macros
468 | params = []
469 | if macro_node.child_by_field_name("parameters"):
470 | params_node = macro_node.child_by_field_name("parameters")
471 | for child in params_node.children:
472 | if child.type == "identifier":
473 | params.append(self._get_node_text(child))
474 |
475 | context, context_type, _ = self._get_parent_context(macro_node)
476 |
477 | macros.append({
478 | "name": name,
479 | "line_number": node.start_point[0] + 1,
480 | "end_line": macro_node.end_point[0] + 1,
481 | "source": self._get_node_text(macro_node),
482 | "value": value,
483 | "params": params,
484 | "context": context,
485 | "lang": self.language_name,
486 | "is_dependency": False,
487 | })
488 | return macros
489 |
490 |
491 | def pre_scan_c(files: list[Path], parser_wrapper) -> dict:
492 | """Scans C files to create a map of function/struct/union/enum names to their file paths."""
493 | imports_map = {}
494 | query_str = """
495 | (function_definition
496 | declarator: (function_declarator
497 | declarator: (identifier) @name
498 | )
499 | )
500 |
501 | (function_definition
502 | declarator: (function_declarator
503 | declarator: (pointer_declarator
504 | declarator: (identifier) @name
505 | )
506 | )
507 | )
508 |
509 | (struct_specifier
510 | name: (type_identifier) @name
511 | )
512 |
513 | (union_specifier
514 | name: (type_identifier) @name
515 | )
516 |
517 | (enum_specifier
518 | name: (type_identifier) @name
519 | )
520 |
521 | (type_definition
522 | declarator: (type_identifier) @name
523 | )
524 |
525 | (preproc_def
526 | name: (identifier) @name
527 | )
528 | """
529 |
530 |
531 | for file_path in files:
532 | try:
533 | with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
534 | tree = parser_wrapper.parser.parse(bytes(f.read(), "utf8"))
535 |
536 | for capture, _ in execute_query(parser_wrapper.language, query_str, tree.root_node):
537 | name = capture.text.decode('utf-8')
538 | if name not in imports_map:
539 | imports_map[name] = []
540 | imports_map[name].append(str(file_path.resolve()))
541 | except Exception as e:
542 | warning_logger(f"Tree-sitter pre-scan failed for {file_path}: {e}")
543 | return imports_map
544 |
```
--------------------------------------------------------------------------------
/src/codegraphcontext/tools/languages/php.py:
--------------------------------------------------------------------------------
```python
1 | from pathlib import Path
2 | from typing import Any, Dict, Optional, Tuple
3 | import re
4 | from codegraphcontext.utils.debug_log import debug_log, info_logger, error_logger, warning_logger
5 | from codegraphcontext.utils.tree_sitter_manager import execute_query
6 |
7 | # Reference: https://github.com/tree-sitter/tree-sitter-php/blob/master/queries/tags.scm
8 | PHP_QUERIES = {
9 | "functions": """
10 | (function_definition
11 | name: (name) @name
12 | parameters: (formal_parameters) @params
13 | ) @function_node
14 |
15 | (method_declaration
16 | name: (name) @name
17 | parameters: (formal_parameters) @params
18 | ) @function_node
19 | """,
20 | "classes": """
21 | (class_declaration
22 | name: (name) @name
23 | ) @class
24 |
25 | (interface_declaration
26 | name: (name) @name
27 | ) @interface
28 |
29 | (trait_declaration
30 | name: (name) @name
31 | ) @trait
32 | """,
33 | "imports": """
34 | (use_declaration) @import
35 | """,
36 | "calls": """
37 | (function_call_expression
38 | function: [
39 | (qualified_name) @name
40 | (name) @name
41 | ]
42 | ) @call_node
43 |
44 | (member_call_expression
45 | name: (name) @name
46 | ) @call_node
47 |
48 | (scoped_call_expression
49 | name: (name) @name
50 | ) @call_node
51 |
52 | (object_creation_expression) @call_node
53 | """,
54 | "variables": """
55 | (variable_name) @variable
56 | """,
57 | }
58 |
59 | class PhpTreeSitterParser:
60 | def __init__(self, generic_parser_wrapper: Any):
61 | self.generic_parser_wrapper = generic_parser_wrapper
62 | self.language_name = "php"
63 | self.language = generic_parser_wrapper.language
64 | self.parser = generic_parser_wrapper.parser
65 |
66 | def parse(self, file_path: Path, is_dependency: bool = False) -> Dict[str, Any]:
67 | try:
68 | with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
69 | source_code = f.read()
70 |
71 | if not source_code.strip():
72 | warning_logger(f"Empty or whitespace-only file: {file_path}")
73 | return {
74 | "file_path": str(file_path),
75 | "functions": [],
76 | "classes": [],
77 | "interfaces": [],
78 | "traits": [],
79 | "variables": [],
80 | "imports": [],
81 | "function_calls": [],
82 | "is_dependency": is_dependency,
83 | "lang": self.language_name,
84 | }
85 |
86 | tree = self.parser.parse(bytes(source_code, "utf8"))
87 |
88 | parsed_functions = []
89 | parsed_classes = []
90 | parsed_interfaces = []
91 | parsed_traits = []
92 | parsed_variables = []
93 | parsed_imports = []
94 | parsed_calls = []
95 |
96 | for capture_name, query in PHP_QUERIES.items():
97 | results = execute_query(self.language, query, tree.root_node)
98 |
99 | if capture_name == "functions":
100 | parsed_functions = self._parse_functions(results, source_code, file_path)
101 | elif capture_name == "classes":
102 | # We group classes, interfaces, traits here, but separating them is cleaner
103 | # Wait, my query combines them. I should verify results.
104 | # execute_query returns (node, capture_name)
105 | # I can filter inside _parse_classes
106 | parsed_classes, parsed_interfaces, parsed_traits = self._parse_types(results, source_code, file_path)
107 | elif capture_name == "imports":
108 | parsed_imports = self._parse_imports(results, source_code)
109 | elif capture_name == "calls":
110 | parsed_calls = self._parse_calls(results, source_code)
111 | elif capture_name == "variables":
112 | parsed_variables = self._parse_variables(results, source_code, file_path)
113 |
114 | return {
115 | "file_path": str(file_path),
116 | "functions": parsed_functions,
117 | "classes": parsed_classes,
118 | "interfaces": parsed_interfaces,
119 | "traits": parsed_traits,
120 | "variables": parsed_variables,
121 | "imports": parsed_imports,
122 | "function_calls": parsed_calls,
123 | "is_dependency": is_dependency,
124 | "lang": self.language_name,
125 | }
126 |
127 | except Exception as e:
128 | error_logger(f"Error parsing PHP file {file_path}: {e}")
129 | return {
130 | "file_path": str(file_path),
131 | "functions": [],
132 | "classes": [],
133 | "interfaces": [],
134 | "traits": [],
135 | "variables": [],
136 | "imports": [],
137 | "function_calls": [],
138 | "is_dependency": is_dependency,
139 | "lang": self.language_name,
140 | }
141 |
142 | def _get_parent_context(self, node: Any) -> Tuple[Optional[str], Optional[str], Optional[int]]:
143 | curr = node.parent
144 | while curr:
145 | if curr.type in ("function_definition", "method_declaration", "class_declaration", "interface_declaration", "trait_declaration"):
146 | name_node = curr.child_by_field_name("name")
147 | return (
148 | self._get_node_text(name_node) if name_node else None,
149 | curr.type,
150 | curr.start_point[0] + 1,
151 | )
152 | curr = curr.parent
153 | return None, None, None
154 |
155 | def _get_node_text(self, node: Any) -> str:
156 | if not node: return ""
157 | return node.text.decode("utf-8")
158 |
159 | def _parse_functions(self, captures: list, source_code: str, file_path: Path) -> list[Dict[str, Any]]:
160 | functions = []
161 | seen_nodes = set()
162 |
163 | for node, capture_name in captures:
164 | if capture_name == "function_node":
165 | node_id = (node.start_byte, node.end_byte, node.type)
166 | if node_id in seen_nodes:
167 | continue
168 | seen_nodes.add(node_id)
169 |
170 | try:
171 | start_line = node.start_point[0] + 1
172 | end_line = node.end_point[0] + 1
173 |
174 | name_node = node.child_by_field_name("name")
175 | if name_node:
176 | func_name = self._get_node_text(name_node)
177 |
178 | params_node = node.child_by_field_name("parameters")
179 | parameters = []
180 | if params_node:
181 | # PHP parameters: function($a, $b)
182 | for child in params_node.children:
183 | if "variable_name" in child.type or "simple_parameter" in child.type:
184 | # Extract variable name from simple_parameter
185 | var_node = child if "variable_name" in child.type else child.child_by_field_name("name")
186 | if var_node:
187 | parameters.append(self._get_node_text(var_node))
188 |
189 | source_text = self._get_node_text(node)
190 |
191 | # Get class context
192 | context_name, context_type, context_line = self._get_parent_context(node)
193 |
194 | functions.append({
195 | "name": func_name,
196 | "parameters": parameters,
197 | "line_number": start_line,
198 | "end_line": end_line,
199 | "source": source_text,
200 | "file_path": str(file_path),
201 | "lang": self.language_name,
202 | "context": context_name,
203 | "context_type": context_type,
204 | "class_context": context_name if context_type and ("class" in context_type or "interface" in context_type or "trait" in context_type) else None
205 | })
206 |
207 | except Exception as e:
208 | error_logger(f"Error parsing function in {file_path}: {e}")
209 | continue
210 |
211 | return functions
212 |
213 | def _parse_types(self, captures: list, source_code: str, file_path: Path) -> Tuple[list, list, list]:
214 | classes = []
215 | interfaces = []
216 | traits = []
217 | seen_nodes = set()
218 |
219 | for node, capture_name in captures:
220 | if capture_name in ("class", "interface", "trait"):
221 | node_id = (node.start_byte, node.end_byte, node.type)
222 | if node_id in seen_nodes:
223 | continue
224 | seen_nodes.add(node_id)
225 |
226 | try:
227 | start_line = node.start_point[0] + 1
228 | end_line = node.end_point[0] + 1
229 |
230 | name_node = node.child_by_field_name("name")
231 | if name_node:
232 | type_name = self._get_node_text(name_node)
233 | source_text = self._get_node_text(node)
234 |
235 | bases = []
236 | # Look for extends/implements
237 | base_clause_node = node.child_by_field_name('base_clause') # extends
238 | interfaces_clause_node = node.child_by_field_name('interfaces_clause') # implements
239 |
240 | if base_clause_node:
241 | # Children of base_clause contain identifiers
242 | for child in base_clause_node.children:
243 | if child.type in ('name', 'qualified_name'):
244 | bases.append(self._get_node_text(child))
245 |
246 | if interfaces_clause_node:
247 | for child in interfaces_clause_node.children:
248 | if child.type in ('name', 'qualified_name'):
249 | bases.append(self._get_node_text(child))
250 |
251 | type_data = {
252 | "name": type_name,
253 | "line_number": start_line,
254 | "end_line": end_line,
255 | "bases": bases,
256 | "source": source_text,
257 | "file_path": str(file_path),
258 | "lang": self.language_name,
259 | }
260 |
261 | if capture_name == "class":
262 | classes.append(type_data)
263 | elif capture_name == "interface":
264 | interfaces.append(type_data)
265 | elif capture_name == "trait":
266 | traits.append(type_data)
267 |
268 | except Exception as e:
269 | error_logger(f"Error parsing type in {file_path}: {e}")
270 | continue
271 |
272 | return classes, interfaces, traits
273 |
274 | def _parse_variables(self, captures: list, source_code: str, file_path: Path) -> list[Dict[str, Any]]:
275 | variables = []
276 | seen_vars = set()
277 |
278 | for node, capture_name in captures:
279 | if capture_name == "variable":
280 | try:
281 | var_name = self._get_node_text(node)
282 | start_line = node.start_point[0] + 1
283 |
284 | start_byte = node.start_byte
285 | if start_byte in seen_vars:
286 | continue
287 | seen_vars.add(start_byte)
288 |
289 | ctx_name, ctx_type, ctx_line = self._get_parent_context(node)
290 |
291 | # Infer type from assignment
292 | inferred_type = "mixed"
293 | parent = node.parent
294 | if parent and parent.type == 'assignment_expression':
295 | # $var = new Class();
296 | left = parent.child_by_field_name('left')
297 | right = parent.child_by_field_name('right')
298 |
299 | # Ensure we are looking at the left side variable
300 | if left == node and right and right.type == 'object_creation_expression':
301 | # Extract class name from right side
302 | for child in right.children:
303 | if child.type in ('name', 'qualified_name'):
304 | inferred_type = self._get_node_text(child)
305 | break
306 |
307 | variables.append({
308 | "name": var_name,
309 | "type": inferred_type,
310 | "line_number": start_line,
311 | "file_path": str(file_path),
312 | "lang": self.language_name,
313 | "context": ctx_name,
314 | "class_context": ctx_name if ctx_type and ("class" in ctx_type or "interface" in ctx_type or "trait" in ctx_type) else None
315 | })
316 | except Exception as e:
317 | continue
318 |
319 | return variables
320 |
321 | def _parse_imports(self, captures: list, source_code: str) -> list[dict]:
322 | imports = []
323 |
324 | for node, capture_name in captures:
325 | if capture_name == "import":
326 | try:
327 | import_text = self._get_node_text(node)
328 | # use Foo\Bar as Baz;
329 | # Node usually has children: name (qualified_name), optional alias
330 |
331 | name_node = None
332 | alias_node = None
333 |
334 | for child in node.children:
335 | if child.type == "qualified_name" or child.type == "name":
336 | name_node = child
337 | # Alias in PHP: use X as Y; The 'as' is usually implicit structure or explicit?
338 | # Tree sitter grammar: use_declaration -> use_clause -> (use_as_clause (qualified_name) (name))
339 |
340 | # Assuming simple handling for now, extracting string from text
341 | # Regex might be safer given tree complexity for `use`
342 | import_match = re.search(r'use\s+([\w\\]+)(?:\s+as\s+(\w+))?', import_text)
343 | if import_match:
344 | import_path = import_match.group(1).strip()
345 | alias = import_match.group(2).strip() if import_match.group(2) else None
346 |
347 | import_data = {
348 | "name": import_path,
349 | "full_import_name": import_text,
350 | "line_number": node.start_point[0] + 1,
351 | "alias": alias,
352 | "context": (None, None),
353 | "lang": self.language_name,
354 | "is_dependency": False,
355 | }
356 | imports.append(import_data)
357 | except Exception as e:
358 | error_logger(f"Error parsing import: {e}")
359 | continue
360 |
361 | return imports
362 |
363 | def _parse_calls(self, captures: list, source_code: str) -> list[dict]:
364 | calls = []
365 | seen_calls = set()
366 |
367 | for node, capture_name in captures:
368 | # For object_creation_expression without @name capture on inner node, we catch the whole node as @call_node
369 | # But the 'calls' query uses @name for function/method calls on the identifier, and @call_node for full expression.
370 | # My query change: (object_creation_expression) @call_node
371 | # This means for obj creation, I won't get a separate @name capture. I need to iterate @call_node captures too?
372 | # actually execute_query returns (node, capture_name).
373 |
374 | # Let's handle 'name' capture which gives us the function name
375 | if capture_name == "name":
376 | try:
377 | call_name = self._get_node_text(node)
378 | line_number = node.start_point[0] + 1
379 |
380 | # Ensure we identify the full call node
381 | call_node = node.parent
382 | while call_node and call_node.type not in ("function_call_expression", "member_call_expression", "scoped_call_expression"):
383 | call_node = call_node.parent
384 |
385 | if not call_node:
386 | continue # It might be a name inside object creation or something we handle otherwise
387 |
388 | # Avoid duplicates
389 | call_key = f"{call_name}_{line_number}"
390 | if call_key in seen_calls:
391 | continue
392 | seen_calls.add(call_key)
393 |
394 | # Extract arguments
395 | args = []
396 | args_node = call_node.child_by_field_name('arguments')
397 | if args_node:
398 | for arg in args_node.children:
399 | if arg.type not in ('(', ')', ','):
400 | args.append(self._get_node_text(arg))
401 |
402 | full_name = call_name # Default
403 | if call_node.type == 'member_call_expression':
404 | # $obj->method()
405 | obj_node = call_node.child_by_field_name('object')
406 | if obj_node:
407 | receiver = self._get_node_text(obj_node)
408 | # Normalize -> to . for graph builder compatibility
409 | full_name = f"{receiver}.{call_name}"
410 | elif call_node.type == 'scoped_call_expression':
411 | # Class::method()
412 | scope_node = call_node.child_by_field_name('scope')
413 | if scope_node:
414 | receiver = self._get_node_text(scope_node)
415 | # Normalize :: to . for graph builder compatibility
416 | full_name = f"{receiver}.{call_name}"
417 |
418 | ctx_name, ctx_type, ctx_line = self._get_parent_context(node)
419 |
420 | call_data = {
421 | "name": call_name,
422 | "full_name": full_name,
423 | "line_number": line_number,
424 | "args": args,
425 | "inferred_obj_type": None,
426 | "context": (ctx_name, ctx_type, ctx_line),
427 | "class_context": (ctx_name, ctx_line) if ctx_type and ("class" in ctx_type or "interface" in ctx_type or "trait" in ctx_type) else (None, None),
428 | "lang": self.language_name,
429 | "is_dependency": False,
430 | }
431 | calls.append(call_data)
432 | except Exception as e:
433 | error_logger(f"Error parsing call: {e}")
434 | continue
435 |
436 | # Handle object creation separately as capture is on the whole node
437 | elif capture_name == "call_node" and node.type == "object_creation_expression":
438 | try:
439 | line_number = node.start_point[0] + 1
440 |
441 | # Find class name (child not named 'arguments')
442 | class_name = "Unknown"
443 | for child in node.children:
444 | if child.type in ('name', 'qualified_name'):
445 | class_name = self._get_node_text(child)
446 | break
447 | if child.type == "variable_name": # dynamic new $class()
448 | class_name = self._get_node_text(child)
449 | break
450 |
451 | call_key = f"new {class_name}_{line_number}"
452 | if call_key in seen_calls:
453 | continue
454 | seen_calls.add(call_key)
455 |
456 | args = []
457 | args_node = node.child_by_field_name('arguments')
458 | if args_node:
459 | for arg in args_node.children:
460 | if arg.type not in ('(', ')', ','):
461 | args.append(self._get_node_text(arg))
462 |
463 | full_name = class_name # For GraphBuilder to link to Class
464 |
465 | ctx_name, ctx_type, ctx_line = self._get_parent_context(node)
466 |
467 | call_data = {
468 | "name": class_name,
469 | "full_name": full_name, # Usually we want the class name here so GB can link to Class node
470 | "line_number": line_number,
471 | "args": args,
472 | "inferred_obj_type": None,
473 | "context": (ctx_name, ctx_type, ctx_line),
474 | "class_context": (ctx_name, ctx_line) if ctx_type and ("class" in ctx_type or "interface" in ctx_type or "trait" in ctx_type) else (None, None),
475 | "lang": self.language_name,
476 | "is_dependency": False,
477 | }
478 | calls.append(call_data)
479 | except Exception:
480 | continue
481 |
482 | return calls
483 |
484 | def pre_scan_php(files: list[Path], parser_wrapper) -> dict:
485 | name_to_files = {}
486 | return name_to_files
487 |
```
--------------------------------------------------------------------------------
/docs/site/core/index.html:
--------------------------------------------------------------------------------
```html
1 |
2 | <!doctype html>
3 | <html lang="en" class="no-js">
4 | <head>
5 |
6 | <meta charset="utf-8">
7 | <meta name="viewport" content="width=device-width,initial-scale=1">
8 |
9 |
10 |
11 |
12 | <link rel="prev" href="../server/">
13 |
14 |
15 | <link rel="next" href="../tools/">
16 |
17 |
18 |
19 |
20 |
21 | <link rel="icon" href="../assets/images/favicon.png">
22 | <meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.1">
23 |
24 |
25 |
26 | <title>Core Concepts - CodeGraphContext</title>
27 |
28 |
29 |
30 | <link rel="stylesheet" href="../assets/stylesheets/main.484c7ddc.min.css">
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
44 | <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
45 | <style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
46 |
47 |
48 |
49 | <script>__md_scope=new URL("..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
50 |
51 |
52 |
53 |
54 |
55 | </head>
56 |
57 |
58 | <body dir="ltr">
59 |
60 |
61 | <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
62 | <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
63 | <label class="md-overlay" for="__drawer"></label>
64 | <div data-md-component="skip">
65 |
66 |
67 | <a href="#core-concepts" class="md-skip">
68 | Skip to content
69 | </a>
70 |
71 | </div>
72 | <div data-md-component="announce">
73 |
74 | </div>
75 |
76 |
77 |
78 |
79 |
80 |
81 | <header class="md-header md-header--shadow" data-md-component="header">
82 | <nav class="md-header__inner md-grid" aria-label="Header">
83 | <a href=".." title="CodeGraphContext" class="md-header__button md-logo" aria-label="CodeGraphContext" data-md-component="logo">
84 |
85 |
86 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
87 |
88 | </a>
89 | <label class="md-header__button md-icon" for="__drawer">
90 |
91 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
92 | </label>
93 | <div class="md-header__title" data-md-component="header-title">
94 | <div class="md-header__ellipsis">
95 | <div class="md-header__topic">
96 | <span class="md-ellipsis">
97 | CodeGraphContext
98 | </span>
99 | </div>
100 | <div class="md-header__topic" data-md-component="header-topic">
101 | <span class="md-ellipsis">
102 |
103 | Core Concepts
104 |
105 | </span>
106 | </div>
107 | </div>
108 | </div>
109 |
110 |
111 | <script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
112 |
113 |
114 |
115 |
116 |
117 | <label class="md-header__button md-icon" for="__search">
118 |
119 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
120 | </label>
121 | <div class="md-search" data-md-component="search" role="dialog">
122 | <label class="md-search__overlay" for="__search"></label>
123 | <div class="md-search__inner" role="search">
124 | <form class="md-search__form" name="search">
125 | <input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
126 | <label class="md-search__icon md-icon" for="__search">
127 |
128 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
129 |
130 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
131 | </label>
132 | <nav class="md-search__options" aria-label="Search">
133 |
134 | <button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
135 |
136 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
137 | </button>
138 | </nav>
139 |
140 | </form>
141 | <div class="md-search__output">
142 | <div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
143 | <div class="md-search-result" data-md-component="search-result">
144 | <div class="md-search-result__meta">
145 | Initializing search
146 | </div>
147 | <ol class="md-search-result__list" role="presentation"></ol>
148 | </div>
149 | </div>
150 | </div>
151 | </div>
152 | </div>
153 |
154 |
155 |
156 | </nav>
157 |
158 | </header>
159 |
160 | <div class="md-container" data-md-component="container">
161 |
162 |
163 |
164 |
165 |
166 |
167 | <main class="md-main" data-md-component="main">
168 | <div class="md-main__inner md-grid">
169 |
170 |
171 |
172 | <div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
173 | <div class="md-sidebar__scrollwrap">
174 | <div class="md-sidebar__inner">
175 |
176 |
177 |
178 |
179 | <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
180 | <label class="md-nav__title" for="__drawer">
181 | <a href=".." title="CodeGraphContext" class="md-nav__button md-logo" aria-label="CodeGraphContext" data-md-component="logo">
182 |
183 |
184 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
185 |
186 | </a>
187 | CodeGraphContext
188 | </label>
189 |
190 | <ul class="md-nav__list" data-md-scrollfix>
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 | <li class="md-nav__item">
199 | <a href=".." class="md-nav__link">
200 |
201 |
202 |
203 | <span class="md-ellipsis">
204 |
205 |
206 | Home
207 |
208 |
209 |
210 | </span>
211 |
212 |
213 |
214 | </a>
215 | </li>
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 | <li class="md-nav__item">
226 | <a href="../installation/" class="md-nav__link">
227 |
228 |
229 |
230 | <span class="md-ellipsis">
231 |
232 |
233 | Installation
234 |
235 |
236 |
237 | </span>
238 |
239 |
240 |
241 | </a>
242 | </li>
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 | <li class="md-nav__item">
253 | <a href="../use_cases/" class="md-nav__link">
254 |
255 |
256 |
257 | <span class="md-ellipsis">
258 |
259 |
260 | Use Cases
261 |
262 |
263 |
264 | </span>
265 |
266 |
267 |
268 | </a>
269 | </li>
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 | <li class="md-nav__item">
280 | <a href="../architecture/" class="md-nav__link">
281 |
282 |
283 |
284 | <span class="md-ellipsis">
285 |
286 |
287 | Architecture
288 |
289 |
290 |
291 | </span>
292 |
293 |
294 |
295 | </a>
296 | </li>
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 | <li class="md-nav__item">
307 | <a href="../cli/" class="md-nav__link">
308 |
309 |
310 |
311 | <span class="md-ellipsis">
312 |
313 |
314 | CLI Reference
315 |
316 |
317 |
318 | </span>
319 |
320 |
321 |
322 | </a>
323 | </li>
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 | <li class="md-nav__item">
334 | <a href="../watching/" class="md-nav__link">
335 |
336 |
337 |
338 | <span class="md-ellipsis">
339 |
340 |
341 | Live Watching
342 |
343 |
344 |
345 | </span>
346 |
347 |
348 |
349 | </a>
350 | </li>
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 | <li class="md-nav__item">
361 | <a href="../server/" class="md-nav__link">
362 |
363 |
364 |
365 | <span class="md-ellipsis">
366 |
367 |
368 | Server
369 |
370 |
371 |
372 | </span>
373 |
374 |
375 |
376 | </a>
377 | </li>
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 | <li class="md-nav__item md-nav__item--active">
390 |
391 | <input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
392 |
393 |
394 |
395 |
396 |
397 | <label class="md-nav__link md-nav__link--active" for="__toc">
398 |
399 |
400 |
401 | <span class="md-ellipsis">
402 |
403 |
404 | Core Concepts
405 |
406 |
407 |
408 | </span>
409 |
410 |
411 |
412 | <span class="md-nav__icon md-icon"></span>
413 | </label>
414 |
415 | <a href="./" class="md-nav__link md-nav__link--active">
416 |
417 |
418 |
419 | <span class="md-ellipsis">
420 |
421 |
422 | Core Concepts
423 |
424 |
425 |
426 | </span>
427 |
428 |
429 |
430 | </a>
431 |
432 |
433 |
434 | <nav class="md-nav md-nav--secondary" aria-label="Table of contents">
435 |
436 |
437 |
438 |
439 |
440 |
441 | <label class="md-nav__title" for="__toc">
442 | <span class="md-nav__icon md-icon"></span>
443 | Table of contents
444 | </label>
445 | <ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
446 |
447 | <li class="md-nav__item">
448 | <a href="#databasemanager" class="md-nav__link">
449 | <span class="md-ellipsis">
450 |
451 | DatabaseManager
452 |
453 | </span>
454 | </a>
455 |
456 | <nav class="md-nav" aria-label="DatabaseManager">
457 | <ul class="md-nav__list">
458 |
459 | <li class="md-nav__item">
460 | <a href="#key-methods" class="md-nav__link">
461 | <span class="md-ellipsis">
462 |
463 | Key Methods
464 |
465 | </span>
466 | </a>
467 |
468 | </li>
469 |
470 | </ul>
471 | </nav>
472 |
473 | </li>
474 |
475 | <li class="md-nav__item">
476 | <a href="#jobmanager" class="md-nav__link">
477 | <span class="md-ellipsis">
478 |
479 | JobManager
480 |
481 | </span>
482 | </a>
483 |
484 | <nav class="md-nav" aria-label="JobManager">
485 | <ul class="md-nav__list">
486 |
487 | <li class="md-nav__item">
488 | <a href="#jobstatus" class="md-nav__link">
489 | <span class="md-ellipsis">
490 |
491 | JobStatus
492 |
493 | </span>
494 | </a>
495 |
496 | </li>
497 |
498 | <li class="md-nav__item">
499 | <a href="#jobinfo" class="md-nav__link">
500 | <span class="md-ellipsis">
501 |
502 | JobInfo
503 |
504 | </span>
505 | </a>
506 |
507 | </li>
508 |
509 | <li class="md-nav__item">
510 | <a href="#key-methods_1" class="md-nav__link">
511 | <span class="md-ellipsis">
512 |
513 | Key Methods
514 |
515 | </span>
516 | </a>
517 |
518 | </li>
519 |
520 | </ul>
521 | </nav>
522 |
523 | </li>
524 |
525 | <li class="md-nav__item">
526 | <a href="#codewatcher" class="md-nav__link">
527 | <span class="md-ellipsis">
528 |
529 | CodeWatcher
530 |
531 | </span>
532 | </a>
533 |
534 | <nav class="md-nav" aria-label="CodeWatcher">
535 | <ul class="md-nav__list">
536 |
537 | <li class="md-nav__item">
538 | <a href="#repositoryeventhandler" class="md-nav__link">
539 | <span class="md-ellipsis">
540 |
541 | RepositoryEventHandler
542 |
543 | </span>
544 | </a>
545 |
546 | </li>
547 |
548 | <li class="md-nav__item">
549 | <a href="#key-methods_2" class="md-nav__link">
550 | <span class="md-ellipsis">
551 |
552 | Key Methods
553 |
554 | </span>
555 | </a>
556 |
557 | </li>
558 |
559 | </ul>
560 | </nav>
561 |
562 | </li>
563 |
564 | </ul>
565 |
566 | </nav>
567 |
568 | </li>
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 | <li class="md-nav__item">
579 | <a href="../tools/" class="md-nav__link">
580 |
581 |
582 |
583 | <span class="md-ellipsis">
584 |
585 |
586 | Tools
587 |
588 |
589 |
590 | </span>
591 |
592 |
593 |
594 | </a>
595 | </li>
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 | <li class="md-nav__item">
606 | <a href="../cookbook/" class="md-nav__link">
607 |
608 |
609 |
610 | <span class="md-ellipsis">
611 |
612 |
613 | Cookbook
614 |
615 |
616 |
617 | </span>
618 |
619 |
620 |
621 | </a>
622 | </li>
623 |
624 |
625 |
626 |
627 |
628 |
629 |
630 |
631 |
632 |
633 |
634 |
635 |
636 |
637 | <li class="md-nav__item md-nav__item--nested">
638 |
639 |
640 |
641 | <input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_11" >
642 |
643 |
644 | <label class="md-nav__link" for="__nav_11" id="__nav_11_label" tabindex="0">
645 |
646 |
647 |
648 | <span class="md-ellipsis">
649 |
650 |
651 | Contributing
652 |
653 |
654 |
655 | </span>
656 |
657 |
658 |
659 | <span class="md-nav__icon md-icon"></span>
660 | </label>
661 |
662 | <nav class="md-nav" data-md-level="1" aria-labelledby="__nav_11_label" aria-expanded="false">
663 | <label class="md-nav__title" for="__nav_11">
664 | <span class="md-nav__icon md-icon"></span>
665 |
666 |
667 | Contributing
668 |
669 |
670 | </label>
671 | <ul class="md-nav__list" data-md-scrollfix>
672 |
673 |
674 |
675 |
676 |
677 |
678 |
679 | <li class="md-nav__item">
680 | <a href="../contributing/" class="md-nav__link">
681 |
682 |
683 |
684 | <span class="md-ellipsis">
685 |
686 |
687 | Overview
688 |
689 |
690 |
691 | </span>
692 |
693 |
694 |
695 | </a>
696 | </li>
697 |
698 |
699 |
700 |
701 |
702 |
703 |
704 |
705 |
706 |
707 | <li class="md-nav__item">
708 | <a href="../contributing_languages/" class="md-nav__link">
709 |
710 |
711 |
712 | <span class="md-ellipsis">
713 |
714 |
715 | Adding New Languages
716 |
717 |
718 |
719 | </span>
720 |
721 |
722 |
723 | </a>
724 | </li>
725 |
726 |
727 |
728 |
729 | </ul>
730 | </nav>
731 |
732 | </li>
733 |
734 |
735 |
736 |
737 |
738 |
739 |
740 |
741 |
742 | <li class="md-nav__item">
743 | <a href="../troubleshooting/" class="md-nav__link">
744 |
745 |
746 |
747 | <span class="md-ellipsis">
748 |
749 |
750 | Troubleshooting
751 |
752 |
753 |
754 | </span>
755 |
756 |
757 |
758 | </a>
759 | </li>
760 |
761 |
762 |
763 |
764 |
765 |
766 |
767 |
768 |
769 | <li class="md-nav__item">
770 | <a href="../future_work/" class="md-nav__link">
771 |
772 |
773 |
774 | <span class="md-ellipsis">
775 |
776 |
777 | Future Work
778 |
779 |
780 |
781 | </span>
782 |
783 |
784 |
785 | </a>
786 | </li>
787 |
788 |
789 |
790 |
791 |
792 |
793 |
794 |
795 |
796 | <li class="md-nav__item">
797 | <a href="../license/" class="md-nav__link">
798 |
799 |
800 |
801 | <span class="md-ellipsis">
802 |
803 |
804 | License
805 |
806 |
807 |
808 | </span>
809 |
810 |
811 |
812 | </a>
813 | </li>
814 |
815 |
816 |
817 | </ul>
818 | </nav>
819 | </div>
820 | </div>
821 | </div>
822 |
823 |
824 |
825 | <div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
826 | <div class="md-sidebar__scrollwrap">
827 | <div class="md-sidebar__inner">
828 |
829 |
830 | <nav class="md-nav md-nav--secondary" aria-label="Table of contents">
831 |
832 |
833 |
834 |
835 |
836 |
837 | <label class="md-nav__title" for="__toc">
838 | <span class="md-nav__icon md-icon"></span>
839 | Table of contents
840 | </label>
841 | <ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
842 |
843 | <li class="md-nav__item">
844 | <a href="#databasemanager" class="md-nav__link">
845 | <span class="md-ellipsis">
846 |
847 | DatabaseManager
848 |
849 | </span>
850 | </a>
851 |
852 | <nav class="md-nav" aria-label="DatabaseManager">
853 | <ul class="md-nav__list">
854 |
855 | <li class="md-nav__item">
856 | <a href="#key-methods" class="md-nav__link">
857 | <span class="md-ellipsis">
858 |
859 | Key Methods
860 |
861 | </span>
862 | </a>
863 |
864 | </li>
865 |
866 | </ul>
867 | </nav>
868 |
869 | </li>
870 |
871 | <li class="md-nav__item">
872 | <a href="#jobmanager" class="md-nav__link">
873 | <span class="md-ellipsis">
874 |
875 | JobManager
876 |
877 | </span>
878 | </a>
879 |
880 | <nav class="md-nav" aria-label="JobManager">
881 | <ul class="md-nav__list">
882 |
883 | <li class="md-nav__item">
884 | <a href="#jobstatus" class="md-nav__link">
885 | <span class="md-ellipsis">
886 |
887 | JobStatus
888 |
889 | </span>
890 | </a>
891 |
892 | </li>
893 |
894 | <li class="md-nav__item">
895 | <a href="#jobinfo" class="md-nav__link">
896 | <span class="md-ellipsis">
897 |
898 | JobInfo
899 |
900 | </span>
901 | </a>
902 |
903 | </li>
904 |
905 | <li class="md-nav__item">
906 | <a href="#key-methods_1" class="md-nav__link">
907 | <span class="md-ellipsis">
908 |
909 | Key Methods
910 |
911 | </span>
912 | </a>
913 |
914 | </li>
915 |
916 | </ul>
917 | </nav>
918 |
919 | </li>
920 |
921 | <li class="md-nav__item">
922 | <a href="#codewatcher" class="md-nav__link">
923 | <span class="md-ellipsis">
924 |
925 | CodeWatcher
926 |
927 | </span>
928 | </a>
929 |
930 | <nav class="md-nav" aria-label="CodeWatcher">
931 | <ul class="md-nav__list">
932 |
933 | <li class="md-nav__item">
934 | <a href="#repositoryeventhandler" class="md-nav__link">
935 | <span class="md-ellipsis">
936 |
937 | RepositoryEventHandler
938 |
939 | </span>
940 | </a>
941 |
942 | </li>
943 |
944 | <li class="md-nav__item">
945 | <a href="#key-methods_2" class="md-nav__link">
946 | <span class="md-ellipsis">
947 |
948 | Key Methods
949 |
950 | </span>
951 | </a>
952 |
953 | </li>
954 |
955 | </ul>
956 | </nav>
957 |
958 | </li>
959 |
960 | </ul>
961 |
962 | </nav>
963 | </div>
964 | </div>
965 | </div>
966 |
967 |
968 |
969 | <div class="md-content" data-md-component="content">
970 |
971 | <article class="md-content__inner md-typeset">
972 |
973 |
974 |
975 |
976 |
977 | <h1 id="core-concepts">Core Concepts</h1>
978 | <p>The core of CodeGraphContext is built upon a few key components that manage the database connection, handle background tasks, and watch for file system changes.</p>
979 | <h2 id="databasemanager"><code>DatabaseManager</code></h2>
980 | <p>The <code>DatabaseManager</code> class in <code>database.py</code> is a thread-safe singleton responsible for managing the connection to the Neo4j database. This ensures that only one connection pool is created and shared across the application, which is crucial for performance and resource management.</p>
981 | <h3 id="key-methods">Key Methods</h3>
982 | <ul>
983 | <li><code>get_driver()</code>: Returns the active Neo4j Driver instance, creating it if it doesn't exist.</li>
984 | <li><code>close_driver()</code>: Closes the Neo4j driver connection.</li>
985 | <li><code>is_connected()</code>: Checks if the database connection is currently active.</li>
986 | </ul>
987 | <h2 id="jobmanager"><code>JobManager</code></h2>
988 | <p>The <code>JobManager</code> class in <code>jobs.py</code> handles long-running, background jobs, such as code indexing. It stores job information in memory and provides a thread-safe way to create, update, and retrieve information about these jobs.</p>
989 | <h3 id="jobstatus"><code>JobStatus</code></h3>
990 | <p>An enumeration for the possible statuses of a background job:
991 | - <code>PENDING</code>
992 | - <code>RUNNING</code>
993 | - <code>COMPLETED</code>
994 | - <code>FAILED</code>
995 | - <code>CANCELLED</code></p>
996 | <h3 id="jobinfo"><code>JobInfo</code></h3>
997 | <p>A data class that holds all information about a single background job, including its ID, status, start/end times, progress, and any errors.</p>
998 | <h3 id="key-methods_1">Key Methods</h3>
999 | <ul>
1000 | <li><code>create_job()</code>: Creates a new job with a unique ID.</li>
1001 | <li><code>update_job()</code>: Updates the information for a specific job.</li>
1002 | <li><code>get_job()</code>: Retrieves the information for a single job.</li>
1003 | <li><code>list_jobs()</code>: Returns a list of all jobs.</li>
1004 | <li><code>cleanup_old_jobs()</code>: Removes old, completed jobs from memory.</li>
1005 | </ul>
1006 | <h2 id="codewatcher"><code>CodeWatcher</code></h2>
1007 | <p>The <code>CodeWatcher</code> class in <code>watcher.py</code> implements the live file-watching functionality using the <code>watchdog</code> library. It observes directories for changes and triggers updates to the code graph.</p>
1008 | <h3 id="repositoryeventhandler"><code>RepositoryEventHandler</code></h3>
1009 | <p>A dedicated event handler for a single repository. It performs an initial scan and then uses a debouncing mechanism to efficiently handle file changes, creations, or deletions.</p>
1010 | <h3 id="key-methods_2">Key Methods</h3>
1011 | <ul>
1012 | <li><code>watch_directory()</code>: Schedules a directory to be watched for changes.</li>
1013 | <li><code>unwatch_directory()</code>: Stops watching a directory.</li>
1014 | <li><code>list_watched_paths()</code>: Returns a list of all currently watched directory paths.</li>
1015 | <li><code>start()</code>: Starts the observer thread.</li>
1016 | <li><code>stop()</code>: Stops the observer thread gracefully.</li>
1017 | </ul>
1018 |
1019 |
1020 |
1021 |
1022 |
1023 |
1024 |
1025 |
1026 |
1027 |
1028 |
1029 |
1030 |
1031 | </article>
1032 | </div>
1033 |
1034 |
1035 | <script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
1036 | </div>
1037 |
1038 | </main>
1039 |
1040 | <footer class="md-footer">
1041 |
1042 | <div class="md-footer-meta md-typeset">
1043 | <div class="md-footer-meta__inner md-grid">
1044 | <div class="md-copyright">
1045 |
1046 |
1047 | Made with
1048 | <a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
1049 | Material for MkDocs
1050 | </a>
1051 |
1052 | </div>
1053 |
1054 | </div>
1055 | </div>
1056 | </footer>
1057 |
1058 | </div>
1059 | <div class="md-dialog" data-md-component="dialog">
1060 | <div class="md-dialog__inner md-typeset"></div>
1061 | </div>
1062 |
1063 |
1064 |
1065 |
1066 |
1067 | <script id="__config" type="application/json">{"annotate": null, "base": "..", "features": [], "search": "../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
1068 |
1069 |
1070 | <script src="../assets/javascripts/bundle.79ae519e.min.js"></script>
1071 |
1072 |
1073 | </body>
1074 | </html>
```
--------------------------------------------------------------------------------
/docs/site/architecture/index.html:
--------------------------------------------------------------------------------
```html
1 |
2 | <!doctype html>
3 | <html lang="en" class="no-js">
4 | <head>
5 |
6 | <meta charset="utf-8">
7 | <meta name="viewport" content="width=device-width,initial-scale=1">
8 |
9 |
10 |
11 |
12 | <link rel="prev" href="../use_cases/">
13 |
14 |
15 | <link rel="next" href="../cli/">
16 |
17 |
18 |
19 |
20 |
21 | <link rel="icon" href="../assets/images/favicon.png">
22 | <meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.1">
23 |
24 |
25 |
26 | <title>Architecture - CodeGraphContext</title>
27 |
28 |
29 |
30 | <link rel="stylesheet" href="../assets/stylesheets/main.484c7ddc.min.css">
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
44 | <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
45 | <style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
46 |
47 |
48 |
49 | <script>__md_scope=new URL("..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
50 |
51 |
52 |
53 |
54 |
55 | </head>
56 |
57 |
58 | <body dir="ltr">
59 |
60 |
61 | <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
62 | <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
63 | <label class="md-overlay" for="__drawer"></label>
64 | <div data-md-component="skip">
65 |
66 |
67 | <a href="#architecture-documentation" class="md-skip">
68 | Skip to content
69 | </a>
70 |
71 | </div>
72 | <div data-md-component="announce">
73 |
74 | </div>
75 |
76 |
77 |
78 |
79 |
80 |
81 | <header class="md-header md-header--shadow" data-md-component="header">
82 | <nav class="md-header__inner md-grid" aria-label="Header">
83 | <a href=".." title="CodeGraphContext" class="md-header__button md-logo" aria-label="CodeGraphContext" data-md-component="logo">
84 |
85 |
86 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
87 |
88 | </a>
89 | <label class="md-header__button md-icon" for="__drawer">
90 |
91 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
92 | </label>
93 | <div class="md-header__title" data-md-component="header-title">
94 | <div class="md-header__ellipsis">
95 | <div class="md-header__topic">
96 | <span class="md-ellipsis">
97 | CodeGraphContext
98 | </span>
99 | </div>
100 | <div class="md-header__topic" data-md-component="header-topic">
101 | <span class="md-ellipsis">
102 |
103 | Architecture
104 |
105 | </span>
106 | </div>
107 | </div>
108 | </div>
109 |
110 |
111 | <script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
112 |
113 |
114 |
115 |
116 |
117 | <label class="md-header__button md-icon" for="__search">
118 |
119 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
120 | </label>
121 | <div class="md-search" data-md-component="search" role="dialog">
122 | <label class="md-search__overlay" for="__search"></label>
123 | <div class="md-search__inner" role="search">
124 | <form class="md-search__form" name="search">
125 | <input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
126 | <label class="md-search__icon md-icon" for="__search">
127 |
128 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
129 |
130 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
131 | </label>
132 | <nav class="md-search__options" aria-label="Search">
133 |
134 | <button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
135 |
136 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
137 | </button>
138 | </nav>
139 |
140 | </form>
141 | <div class="md-search__output">
142 | <div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
143 | <div class="md-search-result" data-md-component="search-result">
144 | <div class="md-search-result__meta">
145 | Initializing search
146 | </div>
147 | <ol class="md-search-result__list" role="presentation"></ol>
148 | </div>
149 | </div>
150 | </div>
151 | </div>
152 | </div>
153 |
154 |
155 |
156 | </nav>
157 |
158 | </header>
159 |
160 | <div class="md-container" data-md-component="container">
161 |
162 |
163 |
164 |
165 |
166 |
167 | <main class="md-main" data-md-component="main">
168 | <div class="md-main__inner md-grid">
169 |
170 |
171 |
172 | <div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
173 | <div class="md-sidebar__scrollwrap">
174 | <div class="md-sidebar__inner">
175 |
176 |
177 |
178 |
179 | <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
180 | <label class="md-nav__title" for="__drawer">
181 | <a href=".." title="CodeGraphContext" class="md-nav__button md-logo" aria-label="CodeGraphContext" data-md-component="logo">
182 |
183 |
184 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
185 |
186 | </a>
187 | CodeGraphContext
188 | </label>
189 |
190 | <ul class="md-nav__list" data-md-scrollfix>
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 | <li class="md-nav__item">
199 | <a href=".." class="md-nav__link">
200 |
201 |
202 |
203 | <span class="md-ellipsis">
204 |
205 |
206 | Home
207 |
208 |
209 |
210 | </span>
211 |
212 |
213 |
214 | </a>
215 | </li>
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 | <li class="md-nav__item">
226 | <a href="../installation/" class="md-nav__link">
227 |
228 |
229 |
230 | <span class="md-ellipsis">
231 |
232 |
233 | Installation
234 |
235 |
236 |
237 | </span>
238 |
239 |
240 |
241 | </a>
242 | </li>
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 | <li class="md-nav__item">
253 | <a href="../use_cases/" class="md-nav__link">
254 |
255 |
256 |
257 | <span class="md-ellipsis">
258 |
259 |
260 | Use Cases
261 |
262 |
263 |
264 | </span>
265 |
266 |
267 |
268 | </a>
269 | </li>
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 | <li class="md-nav__item md-nav__item--active">
282 |
283 | <input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
284 |
285 |
286 |
287 |
288 |
289 | <label class="md-nav__link md-nav__link--active" for="__toc">
290 |
291 |
292 |
293 | <span class="md-ellipsis">
294 |
295 |
296 | Architecture
297 |
298 |
299 |
300 | </span>
301 |
302 |
303 |
304 | <span class="md-nav__icon md-icon"></span>
305 | </label>
306 |
307 | <a href="./" class="md-nav__link md-nav__link--active">
308 |
309 |
310 |
311 | <span class="md-ellipsis">
312 |
313 |
314 | Architecture
315 |
316 |
317 |
318 | </span>
319 |
320 |
321 |
322 | </a>
323 |
324 |
325 |
326 | <nav class="md-nav md-nav--secondary" aria-label="Table of contents">
327 |
328 |
329 |
330 |
331 |
332 |
333 | <label class="md-nav__title" for="__toc">
334 | <span class="md-nav__icon md-icon"></span>
335 | Table of contents
336 | </label>
337 | <ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
338 |
339 | <li class="md-nav__item">
340 | <a href="#high-level-overview" class="md-nav__link">
341 | <span class="md-ellipsis">
342 |
343 | High-Level Overview
344 |
345 | </span>
346 | </a>
347 |
348 | </li>
349 |
350 | <li class="md-nav__item">
351 | <a href="#backend-architecture" class="md-nav__link">
352 | <span class="md-ellipsis">
353 |
354 | Backend Architecture
355 |
356 | </span>
357 | </a>
358 |
359 | <nav class="md-nav" aria-label="Backend Architecture">
360 | <ul class="md-nav__list">
361 |
362 | <li class="md-nav__item">
363 | <a href="#core-components" class="md-nav__link">
364 | <span class="md-ellipsis">
365 |
366 | Core Components
367 |
368 | </span>
369 | </a>
370 |
371 | </li>
372 |
373 | <li class="md-nav__item">
374 | <a href="#tools" class="md-nav__link">
375 | <span class="md-ellipsis">
376 |
377 | Tools
378 |
379 | </span>
380 | </a>
381 |
382 | </li>
383 |
384 | <li class="md-nav__item">
385 | <a href="#server" class="md-nav__link">
386 | <span class="md-ellipsis">
387 |
388 | Server
389 |
390 | </span>
391 | </a>
392 |
393 | </li>
394 |
395 | <li class="md-nav__item">
396 | <a href="#cli" class="md-nav__link">
397 | <span class="md-ellipsis">
398 |
399 | CLI
400 |
401 | </span>
402 | </a>
403 |
404 | </li>
405 |
406 | </ul>
407 | </nav>
408 |
409 | </li>
410 |
411 | <li class="md-nav__item">
412 | <a href="#frontend-architecture" class="md-nav__link">
413 | <span class="md-ellipsis">
414 |
415 | Frontend Architecture
416 |
417 | </span>
418 | </a>
419 |
420 | </li>
421 |
422 | <li class="md-nav__item">
423 | <a href="#testing" class="md-nav__link">
424 | <span class="md-ellipsis">
425 |
426 | Testing
427 |
428 | </span>
429 | </a>
430 |
431 | </li>
432 |
433 | </ul>
434 |
435 | </nav>
436 |
437 | </li>
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 | <li class="md-nav__item">
448 | <a href="../cli/" class="md-nav__link">
449 |
450 |
451 |
452 | <span class="md-ellipsis">
453 |
454 |
455 | CLI Reference
456 |
457 |
458 |
459 | </span>
460 |
461 |
462 |
463 | </a>
464 | </li>
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 | <li class="md-nav__item">
475 | <a href="../watching/" class="md-nav__link">
476 |
477 |
478 |
479 | <span class="md-ellipsis">
480 |
481 |
482 | Live Watching
483 |
484 |
485 |
486 | </span>
487 |
488 |
489 |
490 | </a>
491 | </li>
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 | <li class="md-nav__item">
502 | <a href="../server/" class="md-nav__link">
503 |
504 |
505 |
506 | <span class="md-ellipsis">
507 |
508 |
509 | Server
510 |
511 |
512 |
513 | </span>
514 |
515 |
516 |
517 | </a>
518 | </li>
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 | <li class="md-nav__item">
529 | <a href="../core/" class="md-nav__link">
530 |
531 |
532 |
533 | <span class="md-ellipsis">
534 |
535 |
536 | Core Concepts
537 |
538 |
539 |
540 | </span>
541 |
542 |
543 |
544 | </a>
545 | </li>
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 | <li class="md-nav__item">
556 | <a href="../tools/" class="md-nav__link">
557 |
558 |
559 |
560 | <span class="md-ellipsis">
561 |
562 |
563 | Tools
564 |
565 |
566 |
567 | </span>
568 |
569 |
570 |
571 | </a>
572 | </li>
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
581 |
582 | <li class="md-nav__item">
583 | <a href="../cookbook/" class="md-nav__link">
584 |
585 |
586 |
587 | <span class="md-ellipsis">
588 |
589 |
590 | Cookbook
591 |
592 |
593 |
594 | </span>
595 |
596 |
597 |
598 | </a>
599 | </li>
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 | <li class="md-nav__item md-nav__item--nested">
615 |
616 |
617 |
618 | <input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_11" >
619 |
620 |
621 | <label class="md-nav__link" for="__nav_11" id="__nav_11_label" tabindex="0">
622 |
623 |
624 |
625 | <span class="md-ellipsis">
626 |
627 |
628 | Contributing
629 |
630 |
631 |
632 | </span>
633 |
634 |
635 |
636 | <span class="md-nav__icon md-icon"></span>
637 | </label>
638 |
639 | <nav class="md-nav" data-md-level="1" aria-labelledby="__nav_11_label" aria-expanded="false">
640 | <label class="md-nav__title" for="__nav_11">
641 | <span class="md-nav__icon md-icon"></span>
642 |
643 |
644 | Contributing
645 |
646 |
647 | </label>
648 | <ul class="md-nav__list" data-md-scrollfix>
649 |
650 |
651 |
652 |
653 |
654 |
655 |
656 | <li class="md-nav__item">
657 | <a href="../contributing/" class="md-nav__link">
658 |
659 |
660 |
661 | <span class="md-ellipsis">
662 |
663 |
664 | Overview
665 |
666 |
667 |
668 | </span>
669 |
670 |
671 |
672 | </a>
673 | </li>
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 | <li class="md-nav__item">
685 | <a href="../contributing_languages/" class="md-nav__link">
686 |
687 |
688 |
689 | <span class="md-ellipsis">
690 |
691 |
692 | Adding New Languages
693 |
694 |
695 |
696 | </span>
697 |
698 |
699 |
700 | </a>
701 | </li>
702 |
703 |
704 |
705 |
706 | </ul>
707 | </nav>
708 |
709 | </li>
710 |
711 |
712 |
713 |
714 |
715 |
716 |
717 |
718 |
719 | <li class="md-nav__item">
720 | <a href="../troubleshooting/" class="md-nav__link">
721 |
722 |
723 |
724 | <span class="md-ellipsis">
725 |
726 |
727 | Troubleshooting
728 |
729 |
730 |
731 | </span>
732 |
733 |
734 |
735 | </a>
736 | </li>
737 |
738 |
739 |
740 |
741 |
742 |
743 |
744 |
745 |
746 | <li class="md-nav__item">
747 | <a href="../future_work/" class="md-nav__link">
748 |
749 |
750 |
751 | <span class="md-ellipsis">
752 |
753 |
754 | Future Work
755 |
756 |
757 |
758 | </span>
759 |
760 |
761 |
762 | </a>
763 | </li>
764 |
765 |
766 |
767 |
768 |
769 |
770 |
771 |
772 |
773 | <li class="md-nav__item">
774 | <a href="../license/" class="md-nav__link">
775 |
776 |
777 |
778 | <span class="md-ellipsis">
779 |
780 |
781 | License
782 |
783 |
784 |
785 | </span>
786 |
787 |
788 |
789 | </a>
790 | </li>
791 |
792 |
793 |
794 | </ul>
795 | </nav>
796 | </div>
797 | </div>
798 | </div>
799 |
800 |
801 |
802 | <div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
803 | <div class="md-sidebar__scrollwrap">
804 | <div class="md-sidebar__inner">
805 |
806 |
807 | <nav class="md-nav md-nav--secondary" aria-label="Table of contents">
808 |
809 |
810 |
811 |
812 |
813 |
814 | <label class="md-nav__title" for="__toc">
815 | <span class="md-nav__icon md-icon"></span>
816 | Table of contents
817 | </label>
818 | <ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
819 |
820 | <li class="md-nav__item">
821 | <a href="#high-level-overview" class="md-nav__link">
822 | <span class="md-ellipsis">
823 |
824 | High-Level Overview
825 |
826 | </span>
827 | </a>
828 |
829 | </li>
830 |
831 | <li class="md-nav__item">
832 | <a href="#backend-architecture" class="md-nav__link">
833 | <span class="md-ellipsis">
834 |
835 | Backend Architecture
836 |
837 | </span>
838 | </a>
839 |
840 | <nav class="md-nav" aria-label="Backend Architecture">
841 | <ul class="md-nav__list">
842 |
843 | <li class="md-nav__item">
844 | <a href="#core-components" class="md-nav__link">
845 | <span class="md-ellipsis">
846 |
847 | Core Components
848 |
849 | </span>
850 | </a>
851 |
852 | </li>
853 |
854 | <li class="md-nav__item">
855 | <a href="#tools" class="md-nav__link">
856 | <span class="md-ellipsis">
857 |
858 | Tools
859 |
860 | </span>
861 | </a>
862 |
863 | </li>
864 |
865 | <li class="md-nav__item">
866 | <a href="#server" class="md-nav__link">
867 | <span class="md-ellipsis">
868 |
869 | Server
870 |
871 | </span>
872 | </a>
873 |
874 | </li>
875 |
876 | <li class="md-nav__item">
877 | <a href="#cli" class="md-nav__link">
878 | <span class="md-ellipsis">
879 |
880 | CLI
881 |
882 | </span>
883 | </a>
884 |
885 | </li>
886 |
887 | </ul>
888 | </nav>
889 |
890 | </li>
891 |
892 | <li class="md-nav__item">
893 | <a href="#frontend-architecture" class="md-nav__link">
894 | <span class="md-ellipsis">
895 |
896 | Frontend Architecture
897 |
898 | </span>
899 | </a>
900 |
901 | </li>
902 |
903 | <li class="md-nav__item">
904 | <a href="#testing" class="md-nav__link">
905 | <span class="md-ellipsis">
906 |
907 | Testing
908 |
909 | </span>
910 | </a>
911 |
912 | </li>
913 |
914 | </ul>
915 |
916 | </nav>
917 | </div>
918 | </div>
919 | </div>
920 |
921 |
922 |
923 | <div class="md-content" data-md-component="content">
924 |
925 | <article class="md-content__inner md-typeset">
926 |
927 |
928 |
929 |
930 |
931 | <h1 id="architecture-documentation">Architecture Documentation</h1>
932 | <p>This document provides a detailed overview of the architecture of the CodeGraphContext project.</p>
933 | <h2 id="high-level-overview">High-Level Overview</h2>
934 | <p>The project is a client-server application designed to analyze and visualize codebases. It consists of:</p>
935 | <ul>
936 | <li><strong>A Python backend:</strong> This is the core of the application, responsible for parsing and analyzing code, building a graph representation of the codebase, and exposing this data through an API.</li>
937 | <li><strong>A web-based frontend:</strong> A user interface for interacting with the backend, visualizing the code graph, and exploring the codebase.</li>
938 | <li><strong>A command-line interface (CLI):</strong> For managing the backend and performing analysis from the terminal.</li>
939 | </ul>
940 | <h2 id="backend-architecture">Backend Architecture</h2>
941 | <p>The backend is a Python application located in the <code>src/codegraphcontext</code> directory.</p>
942 | <h3 id="core-components">Core Components</h3>
943 | <p>The <code>src/codegraphcontext/core</code> directory contains the fundamental building blocks of the backend:</p>
944 | <ul>
945 | <li><strong>Database:</strong> A graph database is used to store the code graph. This allows for efficient querying of relationships between code elements (e.g., function calls, class inheritance).</li>
946 | <li><strong>Jobs:</strong> Asynchronous jobs are used for long-running tasks like indexing a new codebase. This prevents the application from becoming unresponsive.</li>
947 | <li><strong>Watcher:</strong> A file system watcher monitors the codebase for changes and triggers re-indexing, keeping the code graph up-to-date.</li>
948 | </ul>
949 | <h3 id="tools">Tools</h3>
950 | <p>The <code>src/codegraphcontext/tools</code> directory contains the logic for code analysis:</p>
951 | <ul>
952 | <li><strong>Graph Builder:</strong> This component is responsible for parsing the code and building the graph representation that is stored in the database.</li>
953 | <li><strong>Code Finder:</strong> Provides functionality to search for specific code elements within the indexed codebase.</li>
954 | <li><strong>Import Extractor:</strong> This tool analyzes the import statements in the code to understand dependencies between modules.</li>
955 | </ul>
956 | <h3 id="server">Server</h3>
957 | <p>The <code>src/codegraphcontext/server.py</code> file implements the API server. It exposes the functionality of the backend to the frontend through a JSON-RPC API.</p>
958 | <h3 id="cli">CLI</h3>
959 | <p>The <code>src/codegraphcontext/cli</code> directory contains the implementation of the command-line interface. It allows users to:</p>
960 | <ul>
961 | <li>Start and stop the backend server.</li>
962 | <li>Index new projects.</li>
963 | <li>Run analysis tools from the command line.</li>
964 | </ul>
965 | <h2 id="frontend-architecture">Frontend Architecture</h2>
966 | <p>The frontend is a modern web application located in the <code>website/</code> directory.</p>
967 | <ul>
968 | <li><strong>Framework:</strong> It is built using React and TypeScript.</li>
969 | <li><strong>Build Tool:</strong> Vite is used for fast development and building the application.</li>
970 | <li><strong>Component-Based:</strong> The UI is organized into reusable components, located in <code>website/src/components</code>. This includes UI elements like buttons and dialogs, as well as higher-level components for different sections of the application.</li>
971 | <li><strong>Styling:</strong> Tailwind CSS is used for styling the application.</li>
972 | </ul>
973 | <h2 id="testing">Testing</h2>
974 | <p>The <code>tests/</code> directory contains the test suite for the project.</p>
975 | <ul>
976 | <li><strong>Integration Tests:</strong> <code>test_cgc_integration.py</code> contains tests that verify the interaction between different components of the backend.</li>
977 | <li><strong>Unit Tests:</strong> Other files in this directory contain unit tests for specific modules and functions.</li>
978 | <li><strong>Sample Project:</strong> The <code>tests/sample_project</code> directory contains a variety of Python files used as input for testing the code analysis tools.</li>
979 | </ul>
980 |
981 |
982 |
983 |
984 |
985 |
986 |
987 |
988 |
989 |
990 |
991 |
992 |
993 | </article>
994 | </div>
995 |
996 |
997 | <script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
998 | </div>
999 |
1000 | </main>
1001 |
1002 | <footer class="md-footer">
1003 |
1004 | <div class="md-footer-meta md-typeset">
1005 | <div class="md-footer-meta__inner md-grid">
1006 | <div class="md-copyright">
1007 |
1008 |
1009 | Made with
1010 | <a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
1011 | Material for MkDocs
1012 | </a>
1013 |
1014 | </div>
1015 |
1016 | </div>
1017 | </div>
1018 | </footer>
1019 |
1020 | </div>
1021 | <div class="md-dialog" data-md-component="dialog">
1022 | <div class="md-dialog__inner md-typeset"></div>
1023 | </div>
1024 |
1025 |
1026 |
1027 |
1028 |
1029 | <script id="__config" type="application/json">{"annotate": null, "base": "..", "features": [], "search": "../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
1030 |
1031 |
1032 | <script src="../assets/javascripts/bundle.79ae519e.min.js"></script>
1033 |
1034 |
1035 | </body>
1036 | </html>
```
--------------------------------------------------------------------------------
/src/codegraphcontext/tools/languages/csharp.py:
--------------------------------------------------------------------------------
```python
1 | from pathlib import Path
2 | from typing import Any, Dict, Optional, Tuple
3 | import re
4 | from codegraphcontext.utils.debug_log import debug_log, info_logger, error_logger, warning_logger
5 | from codegraphcontext.utils.tree_sitter_manager import execute_query
6 |
7 | CSHARP_QUERIES = {
8 | "functions": """
9 | (method_declaration
10 | name: (identifier) @name
11 | parameters: (parameter_list) @params
12 | ) @function_node
13 |
14 | (constructor_declaration
15 | name: (identifier) @name
16 | parameters: (parameter_list) @params
17 | ) @function_node
18 |
19 | (local_function_statement
20 | name: (identifier) @name
21 | parameters: (parameter_list) @params
22 | ) @function_node
23 | """,
24 | "classes": """
25 | (class_declaration
26 | name: (identifier) @name
27 | (base_list)? @bases
28 | ) @class
29 | """,
30 | "interfaces": """
31 | (interface_declaration
32 | name: (identifier) @name
33 | (base_list)? @bases
34 | ) @interface
35 | """,
36 | "structs": """
37 | (struct_declaration
38 | name: (identifier) @name
39 | (base_list)? @bases
40 | ) @struct
41 | """,
42 | "enums": """
43 | (enum_declaration
44 | name: (identifier) @name
45 | ) @enum
46 | """,
47 | "records": """
48 | (record_declaration
49 | name: (identifier) @name
50 | (base_list)? @bases
51 | ) @record
52 | """,
53 | "properties": """
54 | (property_declaration
55 | name: (identifier) @name
56 | ) @property
57 | """,
58 | "imports": """
59 | (using_directive) @import
60 | """,
61 | "calls": """
62 | (invocation_expression
63 | function: [
64 | (identifier) @name
65 | (member_access_expression
66 | name: (identifier) @name
67 | )
68 | ]
69 | )
70 |
71 | (object_creation_expression
72 | type: [
73 | (identifier) @name
74 | (qualified_name) @name
75 | ]
76 | )
77 | """,
78 | }
79 |
80 | class CSharpTreeSitterParser:
81 | def __init__(self, generic_parser_wrapper: Any):
82 | self.generic_parser_wrapper = generic_parser_wrapper
83 | self.language_name = "c_sharp"
84 | self.language = generic_parser_wrapper.language
85 | self.parser = generic_parser_wrapper.parser
86 |
87 | def parse(self, file_path: Path, is_dependency: bool = False) -> Dict[str, Any]:
88 | try:
89 | with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
90 | source_code = f.read()
91 |
92 | if not source_code.strip():
93 | warning_logger(f"Empty or whitespace-only file: {file_path}")
94 | return {
95 | "file_path": str(file_path),
96 | "functions": [],
97 | "classes": [],
98 | "interfaces": [],
99 | "structs": [],
100 | "enums": [],
101 | "records": [],
102 | "properties": [],
103 | "variables": [],
104 | "imports": [],
105 | "function_calls": [],
106 | "is_dependency": is_dependency,
107 | "lang": self.language_name,
108 | }
109 |
110 | tree = self.parser.parse(bytes(source_code, "utf8"))
111 |
112 | parsed_functions = []
113 | parsed_classes = []
114 | parsed_interfaces = []
115 | parsed_structs = []
116 | parsed_enums = []
117 | parsed_records = []
118 | parsed_properties = []
119 | parsed_imports = []
120 | parsed_calls = []
121 |
122 | for capture_name, query_str in CSHARP_QUERIES.items():
123 | captures = execute_query(self.language, query_str, tree.root_node)
124 |
125 | if capture_name == "functions":
126 | parsed_functions = self._parse_functions(captures, source_code, file_path, tree.root_node)
127 | elif capture_name == "classes":
128 | parsed_classes = self._parse_type_declarations(captures, source_code, file_path, "Class")
129 | elif capture_name == "interfaces":
130 | parsed_interfaces = self._parse_type_declarations(captures, source_code, file_path, "Interface")
131 | elif capture_name == "structs":
132 | parsed_structs = self._parse_type_declarations(captures, source_code, file_path, "Struct")
133 | elif capture_name == "enums":
134 | parsed_enums = self._parse_type_declarations(captures, source_code, file_path, "Enum")
135 | elif capture_name == "records":
136 | parsed_records = self._parse_type_declarations(captures, source_code, file_path, "Record")
137 | elif capture_name == "properties":
138 | parsed_properties = self._parse_properties(captures, source_code, file_path, tree.root_node)
139 | elif capture_name == "imports":
140 | parsed_imports = self._parse_imports(captures, source_code)
141 | elif capture_name == "calls":
142 | parsed_calls = self._parse_calls(captures, source_code)
143 |
144 | return {
145 | "file_path": str(file_path),
146 | "functions": parsed_functions,
147 | "classes": parsed_classes,
148 | "interfaces": parsed_interfaces,
149 | "structs": parsed_structs,
150 | "enums": parsed_enums,
151 | "records": parsed_records,
152 | "properties": parsed_properties,
153 | "variables": [],
154 | "imports": parsed_imports,
155 | "function_calls": parsed_calls,
156 | "is_dependency": is_dependency,
157 | "lang": self.language_name,
158 | }
159 |
160 | except Exception as e:
161 | error_logger(f"Error parsing C# file {file_path}: {e}")
162 | return {
163 | "file_path": str(file_path),
164 | "functions": [],
165 | "classes": [],
166 | "interfaces": [],
167 | "structs": [],
168 | "enums": [],
169 | "records": [],
170 | "properties": [],
171 | "variables": [],
172 | "imports": [],
173 | "function_calls": [],
174 | "is_dependency": is_dependency,
175 | "lang": self.language_name,
176 | }
177 |
178 | def _parse_functions(self, captures: list, source_code: str, file_path: Path, root_node) -> list[Dict[str, Any]]:
179 | functions = []
180 | source_lines = source_code.splitlines()
181 |
182 | for node, capture_name in captures:
183 | if capture_name == "function_node":
184 | try:
185 | start_line = node.start_point[0] + 1
186 | end_line = node.end_point[0] + 1
187 |
188 | name_captures = [
189 | (n, cn) for n, cn in captures
190 | if cn == "name" and n.parent.id == node.id
191 | ]
192 |
193 | if name_captures:
194 | name_node = name_captures[0][0]
195 | func_name = source_code[name_node.start_byte:name_node.end_byte]
196 |
197 | params_captures = [
198 | (n, cn) for n, cn in captures
199 | if cn == "params" and n.parent.id == node.id
200 | ]
201 |
202 | parameters = []
203 | if params_captures:
204 | params_node = params_captures[0][0]
205 | parameters = self._extract_parameters(params_node)
206 |
207 | # Extract attributes applied to this function
208 | attributes = []
209 | if node.parent and node.parent.type == "attribute_list":
210 | attr_text = source_code[node.parent.start_byte:node.parent.end_byte]
211 | attributes.append(attr_text)
212 |
213 | # Find containing class/struct/interface
214 | class_context = self._find_containing_type(node, source_code)
215 |
216 | source_text = source_code[node.start_byte:node.end_byte]
217 |
218 | func_data = {
219 | "name": func_name,
220 | "args": parameters,
221 | "attributes": attributes,
222 | "line_number": start_line,
223 | "end_line": end_line,
224 | "source": source_text,
225 | "file_path": str(file_path),
226 | "lang": self.language_name,
227 | }
228 |
229 | # Add class context if found
230 | if class_context:
231 | func_data["class_context"] = class_context
232 |
233 | functions.append(func_data)
234 |
235 | except Exception as e:
236 | error_logger(f"Error parsing function in {file_path}: {e}")
237 | continue
238 |
239 | return functions
240 |
241 | def _parse_type_declarations(self, captures: list, source_code: str, file_path: Path, type_label: str) -> list[Dict[str, Any]]:
242 | """Parse class, interface, struct, enum, or record declarations with inheritance info."""
243 | types = []
244 |
245 | # Map capture names based on type
246 | capture_map = {
247 | "Class": "class",
248 | "Interface": "interface",
249 | "Struct": "struct",
250 | "Enum": "enum",
251 | "Record": "record"
252 | }
253 | expected_capture = capture_map.get(type_label, "class")
254 |
255 | for node, capture_name in captures:
256 | if capture_name == expected_capture:
257 | try:
258 | start_line = node.start_point[0] + 1
259 | end_line = node.end_point[0] + 1
260 |
261 | name_captures = [
262 | (n, cn) for n, cn in captures
263 | if cn == "name" and n.parent.id == node.id
264 | ]
265 |
266 | if name_captures:
267 | name_node = name_captures[0][0]
268 | type_name = source_code[name_node.start_byte:name_node.end_byte]
269 |
270 | # Extract base classes/interfaces
271 | bases = []
272 | bases_captures = [
273 | (n, cn) for n, cn in captures
274 | if cn == "bases" and n.parent.id == node.id
275 | ]
276 |
277 | if bases_captures:
278 | bases_node = bases_captures[0][0]
279 | bases_text = source_code[bases_node.start_byte:bases_node.end_byte]
280 | # Parse base list: ": BaseClass, IInterface1, IInterface2"
281 | bases_text = bases_text.strip().lstrip(':').strip()
282 | if bases_text:
283 | bases = [b.strip() for b in bases_text.split(',')]
284 |
285 | source_text = source_code[node.start_byte:node.end_byte]
286 |
287 | type_data = {
288 | "name": type_name,
289 | "line_number": start_line,
290 | "end_line": end_line,
291 | "source": source_text,
292 | "file_path": str(file_path),
293 | "lang": self.language_name,
294 | }
295 |
296 | # Add bases if found
297 | if bases:
298 | type_data["bases"] = bases
299 |
300 | types.append(type_data)
301 |
302 | except Exception as e:
303 | error_logger(f"Error parsing {type_label} in {file_path}: {e}")
304 | continue
305 |
306 | return types
307 |
308 | def _parse_imports(self, captures: list, source_code: str) -> list[dict]:
309 | imports = []
310 |
311 | for node, capture_name in captures:
312 | if capture_name == "import":
313 | try:
314 | import_text = source_code[node.start_byte:node.end_byte]
315 | # Match: using System.Collections.Generic; or using static System.Math;
316 | import_match = re.search(r'using\s+(?:static\s+)?([^;]+)', import_text)
317 | if import_match:
318 | import_path = import_match.group(1).strip()
319 |
320 | # Check for alias: using MyAlias = System.Collections.Generic.List<int>;
321 | alias = None
322 | if '=' in import_path:
323 | parts = import_path.split('=')
324 | alias = parts[0].strip()
325 | import_path = parts[1].strip()
326 |
327 | import_data = {
328 | "name": import_path,
329 | "full_import_name": import_path,
330 | "line_number": node.start_point[0] + 1,
331 | "alias": alias,
332 | "context": (None, None),
333 | "lang": self.language_name,
334 | "is_dependency": False,
335 | }
336 | imports.append(import_data)
337 | except Exception as e:
338 | error_logger(f"Error parsing import: {e}")
339 | continue
340 |
341 | return imports
342 |
343 | def _get_parent_context(self, node: Any, types: Tuple[str, ...] = ('class_declaration', 'struct_declaration', 'function_declaration', 'method_declaration')):
344 | """Find parent context for C# constructs."""
345 | curr = node.parent
346 | while curr:
347 | if curr.type in types:
348 | if curr.type in ('method_declaration', 'function_declaration'):
349 | name_node = curr.child_by_field_name('name')
350 | return self._get_node_text(name_node) if name_node else None, curr.type, curr.start_point[0] + 1
351 | else:
352 | # Classes, structs, etc.
353 | name_node = curr.child_by_field_name('name')
354 | return self._get_node_text(name_node) if name_node else None, curr.type, curr.start_point[0] + 1
355 | curr = curr.parent
356 | return None, None, None
357 |
358 | def _get_node_text(self, node: Any) -> str:
359 | if not node: return ""
360 | return node.text.decode("utf-8")
361 |
362 | def _parse_calls(self, captures: list, source_code: str) -> list[dict]:
363 | calls = []
364 | seen_calls = set()
365 |
366 | for node, capture_name in captures:
367 | if capture_name == "name":
368 | try:
369 | call_name = source_code[node.start_byte:node.end_byte]
370 | line_number = node.start_point[0] + 1
371 |
372 | # Avoid duplicates
373 | call_key = f"{call_name}_{line_number}"
374 | if call_key in seen_calls:
375 | continue
376 | seen_calls.add(call_key)
377 |
378 | # Get context
379 | context_name, context_type, context_line = self._get_parent_context(node)
380 | class_context = context_name if context_type and 'class' in context_type else None
381 |
382 | call_data = {
383 | "name": call_name,
384 | "full_name": call_name,
385 | "line_number": line_number,
386 | "args": [],
387 | "inferred_obj_type": None,
388 | "context": (context_name, context_type, context_line),
389 | "class_context": class_context,
390 | "lang": self.language_name,
391 | "is_dependency": False,
392 | }
393 | calls.append(call_data)
394 | except Exception as e:
395 | error_logger(f"Error parsing call: {e}")
396 | continue
397 |
398 | return calls
399 |
400 |
401 | def _extract_parameters(self, params_node) -> list[str]:
402 | params = []
403 | if not params_node:
404 | return params
405 |
406 | # Iterate over parameter nodes in the parameter list
407 | for child in params_node.children:
408 | if child.type == "parameter":
409 | # find the identifier
410 | name_node = child.child_by_field_name("name")
411 | if name_node:
412 | params.append(self._get_node_text(name_node))
413 | else:
414 | # Fallback: scan children for identifier if field name not present in this grammar version
415 | for sub in child.children:
416 | if sub.type == "identifier":
417 | params.append(self._get_node_text(sub))
418 | break
419 | return params
420 |
421 | def _find_containing_type(self, node, source_code):
422 | """Find the containing class, struct, interface, or record for a given node."""
423 | current = node.parent
424 | while current:
425 | if current.type in ['class_declaration', 'struct_declaration', 'interface_declaration', 'record_declaration']:
426 | # Find the name of this type
427 | for child in current.children:
428 | if child.type == 'identifier':
429 | return source_code[child.start_byte:child.end_byte]
430 | current = current.parent
431 | return None
432 |
433 | def _parse_properties(self, captures: list, source_code: str, file_path: Path, root_node) -> list[Dict[str, Any]]:
434 | """Parse C# properties."""
435 | properties = []
436 |
437 | for node, capture_name in captures:
438 | if capture_name == "property":
439 | try:
440 | start_line = node.start_point[0] + 1
441 | end_line = node.end_point[0] + 1
442 |
443 | name_captures = [
444 | (n, cn) for n, cn in captures
445 | if cn == "name" and n.parent == node
446 | ]
447 |
448 | if name_captures:
449 | name_node = name_captures[0][0]
450 | prop_name = source_code[name_node.start_byte:name_node.end_byte]
451 |
452 | # Get property type from node children
453 | prop_type = None
454 | for child in node.children:
455 | if child.type in ['predefined_type', 'identifier', 'generic_name', 'nullable_type', 'array_type']:
456 | prop_type = source_code[child.start_byte:child.end_byte]
457 | break
458 |
459 |
460 | # Find containing class/struct
461 | class_context = self._find_containing_type(node, source_code)
462 |
463 | source_text = source_code[node.start_byte:node.end_byte]
464 |
465 | prop_data = {
466 | "name": prop_name,
467 | "type": prop_type,
468 | "line_number": start_line,
469 | "end_line": end_line,
470 | "source": source_text,
471 | "file_path": str(file_path),
472 | "lang": self.language_name,
473 | }
474 |
475 | if class_context:
476 | prop_data["class_context"] = class_context
477 |
478 | properties.append(prop_data)
479 |
480 | except Exception as e:
481 | error_logger(f"Error parsing property in {file_path}: {e}")
482 | continue
483 |
484 | return properties
485 |
486 |
487 |
488 | def pre_scan_csharp(files: list[Path], parser_wrapper) -> dict:
489 | """Pre-scan C# files to build a name-to-files mapping."""
490 | name_to_files = {}
491 |
492 | for file_path in files:
493 | try:
494 | with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
495 | content = f.read()
496 |
497 | # Match class declarations
498 | class_matches = re.finditer(
499 | r'\b(?:public\s+|private\s+|protected\s+|internal\s+)?(?:static\s+)?(?:abstract\s+)?(?:sealed\s+)?(?:partial\s+)?class\s+(\w+)',
500 | content
501 | )
502 | for match in class_matches:
503 | class_name = match.group(1)
504 | if class_name not in name_to_files:
505 | name_to_files[class_name] = []
506 | name_to_files[class_name].append(str(file_path))
507 |
508 | # Match interface declarations
509 | interface_matches = re.finditer(
510 | r'\b(?:public\s+|private\s+|protected\s+|internal\s+)?(?:partial\s+)?interface\s+(\w+)',
511 | content
512 | )
513 | for match in interface_matches:
514 | interface_name = match.group(1)
515 | if interface_name not in name_to_files:
516 | name_to_files[interface_name] = []
517 | name_to_files[interface_name].append(str(file_path))
518 |
519 | # Match struct declarations
520 | struct_matches = re.finditer(
521 | r'\b(?:public\s+|private\s+|protected\s+|internal\s+)?(?:readonly\s+)?(?:partial\s+)?struct\s+(\w+)',
522 | content
523 | )
524 | for match in struct_matches:
525 | struct_name = match.group(1)
526 | if struct_name not in name_to_files:
527 | name_to_files[struct_name] = []
528 | name_to_files[struct_name].append(str(file_path))
529 |
530 | # Match record declarations
531 | record_matches = re.finditer(
532 | r'\b(?:public\s+|private\s+|protected\s+|internal\s+)?(?:sealed\s+)?record\s+(?:class\s+)?(\w+)',
533 | content
534 | )
535 | for match in record_matches:
536 | record_name = match.group(1)
537 | if record_name not in name_to_files:
538 | name_to_files[record_name] = []
539 | name_to_files[record_name].append(str(file_path))
540 |
541 | except Exception as e:
542 | error_logger(f"Error pre-scanning C# file {file_path}: {e}")
543 |
544 | return name_to_files
545 |
```