This commit is contained in:
2025-11-23 05:52:28 +01:00
parent 2998a6e806
commit 9b5301f2c3
15 changed files with 1648 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
# CodeParser Module
The `codeparser` module provides a comprehensive indexing and analysis system for V codebases. It walks directory trees, parses all V files, and allows efficient searching, filtering, and analysis of code structures.
## Features
- **Directory Scanning**: Automatically walks directory trees and finds all V files
- **Batch Parsing**: Parses multiple files efficiently
- **Indexing**: Indexes code by module, structs, functions, interfaces, constants
- **Search**: Find specific items by name
- **Filtering**: Use predicates to filter code items
- **Statistics**: Get module statistics (file count, struct count, etc.)
- **Export**: Export complete codebase structure as JSON
- **Error Handling**: Gracefully handles parse errors
## Basic Usage
```v
import incubaid.herolib.core.codeparser
// Create a parser for a directory
mut parser := codeparser.new('/path/to/herolib')!
// List all modules
modules := parser.list_modules()
for mod in modules {
println('Module: ${mod}')
}
// Find a specific struct
struct_ := parser.find_struct('User', 'mymodule')!
println('Struct: ${struct_.name}')
// List all public functions
pub_fns := parser.filter_public_functions()
// Get methods on a struct
methods := parser.list_methods_on_struct('User')
// Export to JSON
json_str := parser.to_json()!
```
## API Reference
### Factory
- `new(root_dir: string) !CodeParser` - Create parser for a directory
### Listers
- `list_modules() []string` - All modules
- `list_files() []string` - All files
- `list_files_in_module(module: string) []string` - Files in module
- `list_structs(module: string = '') []Struct` - All structs
- `list_functions(module: string = '') []Function` - All functions
- `list_interfaces(module: string = '') []Interface` - All interfaces
- `list_methods_on_struct(struct: string, module: string = '') []Function` - Methods
- `list_imports(module: string = '') []Import` - All imports
- `list_constants(module: string = '') []Const` - All constants
### Finders
- `find_struct(name: string, module: string = '') !Struct`
- `find_function(name: string, module: string = '') !Function`
- `find_interface(name: string, module: string = '') !Interface`
- `find_method(struct: string, method: string, module: string = '') !Function`
- `find_module(name: string) !ParsedModule`
- `find_file(path: string) !ParsedFile`
- `find_structs_with_method(method: string, module: string = '') []string`
- `find_callers(function: string, module: string = '') []Function`
### Filters
- `filter_structs(predicate: fn(Struct) bool, module: string = '') []Struct`
- `filter_functions(predicate: fn(Function) bool, module: string = '') []Function`
- `filter_public_structs(module: string = '') []Struct`
- `filter_public_functions(module: string = '') []Function`
- `filter_functions_with_receiver(module: string = '') []Function`
- `filter_functions_returning_error(module: string = '') []Function`
- `filter_structs_with_field(type: string, module: string = '') []Struct`
- `filter_structs_by_name(pattern: string, module: string = '') []Struct`
- `filter_functions_by_name(pattern: string, module: string = '') []Function`
### Export
- `to_json(module: string = '') !string` - Export to JSON
- `to_json_pretty(module: string = '') !string` - Pretty-printed JSON
### Error Handling
- `has_errors() bool` - Check if parsing errors occurred
- `error_count() int` - Get number of errors
- `print_errors()` - Print all errors
## Example: Analyzing a Module
```v
import incubaid.herolib.core.codeparser
mut parser := codeparser.new(os.home_dir() + '/code/github/incubaid/herolib/lib/core')!
// Get all public functions in the 'pathlib' module
pub_fns := parser.filter_public_functions('incubaid.herolib.core.pathlib')
for fn in pub_fns {
println('${fn.name}() -> ${fn.result.typ.symbol()}')
}
// Find all structs with a specific method
structs := parser.find_structs_with_method('read')
// Export pathlib module to JSON
json_str := parser.to_json('incubaid.herolib.core.pathlib')!
println(json_str)
```
## Implementation Notes
1. **Lazy Parsing**: Files are parsed only when needed
2. **Error Recovery**: Parsing errors don't stop the indexing process
3. **Memory Efficient**: Maintains index in memory but doesn't duplicate code
4. **Module Agnostic**: Works with any V module structure
5. **Cross-Module Search**: Can search across entire codebase or single module

View File

@@ -0,0 +1,363 @@
module codeparser
import incubaid.herolib.ui.console
import incubaid.herolib.core.pathlib
import incubaid.herolib.core.code
import os
fn test_comprehensive_code_parsing() {
console.print_header('Comprehensive Code myparser Tests')
console.print_lf(1)
// Setup test files by copying testdata
test_dir := setup_test_directory()
console.print_item('Copied testdata to: ${test_dir}')
console.print_lf(1)
// Run all tests
test_module_parsing()
test_struct_parsing()
test_function_parsing()
test_imports_and_modules()
test_type_system()
test_visibility_modifiers()
test_method_parsing()
test_constants_parsing()
console.print_green(' All comprehensive tests passed!')
console.print_lf(1)
// Cleanup
os.rmdir_all(test_dir) or {}
console.print_item('Cleaned up test directory')
}
// setup_test_directory copies the testdata directory to /tmp/codeparsertest
fn setup_test_directory() string {
test_dir := '/tmp/codeparsertest'
// Remove existing test directory
os.rmdir_all(test_dir) or {}
// Find the testdata directory relative to this file
current_file := @FILE
current_dir := os.dir(current_file)
testdata_dir := os.join_path(current_dir, 'testdata')
// Verify testdata directory exists
if !os.is_dir(testdata_dir) {
panic('testdata directory not found at: ${testdata_dir}')
}
// Copy testdata to test directory
os.mkdir_all(test_dir) or { panic('Failed to create test directory') }
copy_directory(testdata_dir, test_dir) or { panic('Failed to copy testdata: ${err}') }
return test_dir
}
// copy_directory recursively copies a directory and all its contents
fn copy_directory(src string, dst string) ! {
entries := os.ls(src)!
for entry in entries {
src_path := os.join_path(src, entry)
dst_path := os.join_path(dst, entry)
if os.is_dir(src_path) {
os.mkdir_all(dst_path)!
copy_directory(src_path, dst_path)!
} else {
content := os.read_file(src_path)!
os.write_file(dst_path, content)!
}
}
}
fn test_module_parsing() {
console.print_header('Test 1: Module and File Parsing')
mut myparser := new('/tmp/codeparsertest', ParseOptions{ recursive: true })!
parse()!
v_files := myparser.files.keys()
console.print_item('Found ${v_files.len} V files')
mut total_items := 0
for file_path in v_files {
vfile := myparser.files[file_path]
console.print_item(' ${os.base(file_path)}: ${vfile.items.len} items')
total_items += vfile.items.len
}
assert v_files.len >= 7, 'Expected at least 7 V files, got ${v_files.len}' // 5 new files + 2 existing
assert total_items > 0, 'Expected to parse some items'
console.print_green(' Module parsing test passed')
console.print_lf(1)
}
fn test_struct_parsing() {
console.print_header('Test 2: Struct Parsing')
models_file := os.join_path('/tmp/codeparsertest', 'models.v')
content := os.read_file(models_file) or {
assert false, 'Failed to read models.v'
return
}
vfile := parse_vfile(content) or {
assert false, 'Failed to parse models.v: ${err}'
return
}
structs := vfile.structs()
assert structs.len >= 3, 'Expected at least 3 structs, got ${structs.len}'
// Check User struct
user_struct := structs.filter(it.name == 'User')
assert user_struct.len == 1, 'User struct not found'
user := user_struct[0]
assert user.is_pub == true, 'User struct should be public'
assert user.fields.len == 6, 'User struct should have 6 fields, got ${user.fields.len}'
console.print_item(' User struct: ${user.fields.len} fields (public)')
// Check Profile struct
profile_struct := structs.filter(it.name == 'Profile')
assert profile_struct.len == 1, 'Profile struct not found'
assert profile_struct[0].is_pub == true, 'Profile should be public'
console.print_item(' Profile struct: ${profile_struct[0].fields.len} fields (public)')
// Check Settings struct (private)
settings_struct := structs.filter(it.name == 'Settings')
assert settings_struct.len == 1, 'Settings struct not found'
assert settings_struct[0].is_pub == false, 'Settings should be private'
console.print_item(' Settings struct: ${settings_struct[0].fields.len} fields (private)')
// Check InternalConfig struct
config_struct := structs.filter(it.name == 'InternalConfig')
assert config_struct.len == 1, 'InternalConfig struct not found'
assert config_struct[0].is_pub == false, 'InternalConfig should be private'
console.print_item(' InternalConfig struct (private)')
console.print_green(' Struct parsing test passed')
console.print_lf(1)
}
fn test_function_parsing() {
console.print_header('Test 3: Function Parsing')
mut myparser := new('/tmp/codeparsertest', ParseOptions{ recursive: true })!
myparser.parse()!
mut functions := []code.Function{}
for _, vfile in myparser.files {
functions << vfile.functions()
}
pub_functions := functions.filter(it.is_pub)
priv_functions := functions.filter(!it.is_pub)
assert pub_functions.len >= 8, 'Expected at least 8 public functions, got ${pub_functions.len}'
assert priv_functions.len >= 4, 'Expected at least 4 private functions, got ${priv_functions.len}'
// Check create_user function
create_user_fn := functions.filter(it.name == 'create_user')
assert create_user_fn.len == 1, 'create_user function not found'
create_fn := create_user_fn[0]
assert create_fn.is_pub == true, 'create_user should be public'
assert create_fn.params.len == 2, 'create_user should have 2 parameters'
assert create_fn.description.len > 0, 'create_user should have description'
console.print_item(' create_user: ${create_fn.params.len} params, public')
// Check get_user function
get_user_fn := functions.filter(it.name == 'get_user')
assert get_user_fn.len == 1, 'get_user function not found'
assert get_user_fn[0].is_pub == true
console.print_item(' get_user: public function')
// Check delete_user function
delete_user_fn := functions.filter(it.name == 'delete_user')
assert delete_user_fn.len == 1, 'delete_user function not found'
console.print_item(' delete_user: public function')
// Check validate_email (private)
validate_fn := functions.filter(it.name == 'validate_email')
assert validate_fn.len == 1, 'validate_email function not found'
assert validate_fn[0].is_pub == false, 'validate_email should be private'
console.print_item(' validate_email: private function')
console.print_green(' Function parsing test passed')
console.print_lf(1)
}
fn test_imports_and_modules() {
console.print_header('Test 4: Imports and Module Names')
models_file := os.join_path('/tmp/codeparsertest', 'models.v')
content := os.read_file(models_file) or {
assert false, 'Failed to read models.v'
return
}
vfile := parse_vfile(content) or {
assert false, 'Failed to parse models.v: ${err}'
return
}
assert vfile.mod == 'testapp', 'Module name should be testapp, got ${vfile.mod}'
assert vfile.imports.len == 2, 'Expected 2 imports, got ${vfile.imports.len}'
console.print_item(' Module name: ${vfile.mod}')
console.print_item(' Imports: ${vfile.imports.len}')
for import_ in vfile.imports {
console.print_item(' - ${import_.mod}')
}
assert 'time' in vfile.imports.map(it.mod), 'time import not found'
assert 'os' in vfile.imports.map(it.mod), 'os import not found'
console.print_green(' Import and module test passed')
console.print_lf(1)
}
fn test_type_system() {
console.print_header('Test 5: Type System')
models_file := os.join_path('/tmp/codeparsertest', 'models.v')
content := os.read_file(models_file) or {
assert false, 'Failed to read models.v'
return
}
vfile := parse_vfile(content) or {
assert false, 'Failed to parse models.v: ${err}'
return
}
structs := vfile.structs()
user_struct := structs.filter(it.name == 'User')[0]
// Test different field types
id_field := user_struct.fields.filter(it.name == 'id')[0]
assert id_field.typ.symbol() == 'int', 'id field should be int, got ${id_field.typ.symbol()}'
email_field := user_struct.fields.filter(it.name == 'email')[0]
assert email_field.typ.symbol() == 'string', 'email field should be string'
active_field := user_struct.fields.filter(it.name == 'active')[0]
assert active_field.typ.symbol() == 'bool', 'active field should be bool'
console.print_item(' Integer type: ${id_field.typ.symbol()}')
console.print_item(' String type: ${email_field.typ.symbol()}')
console.print_item(' Boolean type: ${active_field.typ.symbol()}')
console.print_green(' Type system test passed')
console.print_lf(1)
}
fn test_visibility_modifiers() {
console.print_header('Test 6: Visibility Modifiers')
models_file := os.join_path('/tmp/codeparsertest', 'models.v')
content := os.read_file(models_file) or {
assert false, 'Failed to read models.v'
return
}
vfile := parse_vfile(content) or {
assert false, 'Failed to parse models.v: ${err}'
return
}
structs := vfile.structs()
// Check User struct visibility
user_struct := structs.filter(it.name == 'User')[0]
assert user_struct.is_pub == true, 'User struct should be public'
pub_fields := user_struct.fields.filter(it.is_pub)
mut_fields := user_struct.fields.filter(it.is_mut)
console.print_item(' User struct: public')
console.print_item(' - Public fields: ${pub_fields.len}')
console.print_item(' - Mutable fields: ${mut_fields.len}')
// Check InternalConfig visibility
config_struct := structs.filter(it.name == 'InternalConfig')[0]
assert config_struct.is_pub == false, 'InternalConfig should be private'
console.print_item(' InternalConfig: private')
console.print_green(' Visibility modifiers test passed')
console.print_lf(1)
}
fn test_method_parsing() {
console.print_header('Test 7: Method Parsing')
mut myparser := new('/tmp/codeparsertest', recursive: true)!
myparser.parse()!
mut methods := []code.Function{}
for _, vfile in myparser.files {
methods << vfile.functions().filter(it.receiver.name != '')
}
assert methods.len >= 11, 'Expected at least 11 methods, got ${methods.len}'
// Check activate method
activate_methods := methods.filter(it.name == 'activate')
assert activate_methods.len == 1, 'activate method not found'
assert activate_methods[0].receiver.mutable == true, 'activate should have mutable receiver'
console.print_item(' activate: mutable method')
// Check is_active method
is_active_methods := methods.filter(it.name == 'is_active')
assert is_active_methods.len == 1, 'is_active method not found'
assert is_active_methods[0].receiver.mutable == false, 'is_active should have immutable receiver'
console.print_item(' is_active: immutable method')
// Check get_display_name method
display_methods := methods.filter(it.name == 'get_display_name')
assert display_methods.len == 1, 'get_display_name method not found'
console.print_item(' get_display_name: method found')
console.print_green(' Method parsing test passed')
console.print_lf(1)
}
fn test_constants_parsing() {
console.print_header('Test 8: Constants Parsing')
models_file := os.join_path('/tmp/codeparsertest', 'models.v')
content := os.read_file(models_file) or {
assert false, 'Failed to read models.v'
return
}
vfile := parse_vfile(content) or {
assert false, 'Failed to parse models.v: ${err}'
return
}
assert vfile.consts.len == 3, 'Expected 3 constants, got ${vfile.consts.len}'
// Check app_version constant
version_const := vfile.consts.filter(it.name == 'app_version')
assert version_const.len == 1, 'app_version constant not found'
console.print_item(' app_version: ${version_const[0].value}')
// Check max_users constant
max_users_const := vfile.consts.filter(it.name == 'max_users')
assert max_users_const.len == 1, 'max_users constant not found'
console.print_item(' max_users: ${max_users_const[0].value}')
// Check default_timeout constant
timeout_const := vfile.consts.filter(it.name == 'default_timeout')
assert timeout_const.len == 1, 'default_timeout constant not found'
console.print_item(' default_timeout: ${timeout_const[0].value}')
console.print_green(' Constants parsing test passed')
console.print_lf(1)
}

View File

@@ -0,0 +1,150 @@
module codeparser
import incubaid.herolib.core.code
import incubaid.herolib.ui.console
import os
@[params]
pub struct ParseOptions {
pub:
recursive bool = true
exclude_patterns []string
include_patterns []string = ['*.v']
}
pub struct CodeParser {
pub:
root_path string
options ParseOptions
pub mut:
files map[string]code.VFile
modules []code.Module
errors []string
}
pub fn new(path string, opts ParseOptions) !CodeParser {
mut parser := CodeParser{
root_path: path
options: opts
}
return parser
}
pub fn (mut parser CodeParser) parse() ! {
parser.files.clear()
parser.errors.clear()
v_files := parser.collect_files()!
for file_path in v_files {
console.print_debug('Parsing: ${file_path}')
content := os.read_file(file_path) or {
parser.errors << 'Failed to read ${file_path}: ${err}'
continue
}
vfile := code.parse_vfile(content) or {
parser.errors << 'Failed to parse ${file_path}: ${err}'
continue
}
parser.files[file_path] = vfile
}
}
pub fn (parser CodeParser) collect_files() ![]string {
mut files := []string{}
if parser.options.recursive {
files = parser.collect_files_recursive(parser.root_path)!
} else {
files = code.list_v_files(parser.root_path)!
}
return files
}
fn (parser CodeParser) collect_files_recursive(dir string) ![]string {
mut all_files := []string{}
items := os.ls(dir)!
for item in items {
path := os.join_path(dir, item)
if parser.should_skip(path) {
continue
}
if os.is_dir(path) {
sub_files := parser.collect_files_recursive(path)!
all_files << sub_files
} else if item.ends_with('.v') && !item.ends_with('_.v') {
all_files << path
}
}
return all_files
}
fn (parser CodeParser) should_skip(path string) bool {
basename := os.base(path)
// Skip common directories
if basename in ['.git', 'node_modules', '.vscode', '__pycache__', '.github'] {
return true
}
for pattern in parser.options.exclude_patterns {
if basename.contains(pattern) {
return true
}
}
return false
}
pub fn (parser CodeParser) summarize() CodeSummary {
mut summary := CodeSummary{}
for _, vfile in parser.files {
summary.total_files++
summary.total_imports += vfile.imports.len
summary.total_structs += vfile.structs().len
summary.total_functions += vfile.functions().len
summary.total_consts += vfile.consts.len
}
summary.total_errors = parser.errors.len
return summary
}
pub struct CodeSummary {
pub mut:
total_files int
total_imports int
total_structs int
total_functions int
total_consts int
total_errors int
}
pub fn (summary CodeSummary) print() {
console.print_header('Code Summary')
console.print_item('Files parsed: ${summary.total_files}')
console.print_item('Imports: ${summary.total_imports}')
console.print_item('Structs: ${summary.total_structs}')
console.print_item('Functions: ${summary.total_functions}')
console.print_item('Constants: ${summary.total_consts}')
console.print_item('Errors: ${summary.total_errors}')
}
pub fn (parser CodeParser) print_errors() {
if parser.errors.len > 0 {
console.print_header('Parsing Errors')
for err in parser.errors {
console.print_stderr(err)
}
}
}

View File

@@ -0,0 +1,123 @@
module codeparser
import incubaid.herolib.core.pathlib
import incubaid.herolib.core.code
import log
// new creates a CodeParser from a root directory
// It walks the directory tree, parses all .v files, and indexes them
//
// Args:
// root_dir string - directory to scan (absolute or relative)
// Returns:
// CodeParser - indexed codebase
// error - if directory doesn't exist or other I/O errors
pub fn new(root_dir string) !CodeParser {
mut parser := CodeParser{
root_path: root_dir
}
parser.scan_directory()!
return parser
}
// scan_directory recursively walks the directory and parses all V files
fn (mut parser CodeParser) scan_directory() ! {
mut root := pathlib.get_dir(path: parser.root_dir, create: false)!
if !root.exists() {
return error('root directory does not exist: ${parser.root_dir}')
}
parser.walk_dir(mut root)!
}
// walk_dir recursively traverses directories and collects V files
fn (mut parser CodeParser) walk_dir(mut dir pathlib.Path) ! {
// Get all items in directory
mut items := dir.list()!
for item in items {
if item.is_file() && item.path.ends_with('.v') {
// Skip generated files
if item.path.ends_with('_.v') {
continue
}
parser.parse_file(item.path)
} else if item.is_dir() {
// Recursively walk subdirectories
mut subdir := pathlib.get_dir(path: item.path, create: false) or { continue }
parser.walk_dir(mut subdir) or { continue }
}
}
}
// parse_file parses a single V file and adds it to the index
fn (mut parser CodeParser) parse_file(file_path string) {
mut file := pathlib.get_file(path: file_path) or {
err_msg := 'failed to read file: ${err}'
parser.parse_errors << ParseError{
file_path: file_path
error: err_msg
}
return
}
content := file.read() or {
err_msg := 'failed to read content: ${err}'
parser.parse_errors << ParseError{
file_path: file_path
error: err_msg
}
return
}
// Parse the V file
vfile := code.parse_vfile(content) or {
err_msg := 'parse error: ${err}'
parser.parse_errors << ParseError{
file_path: file_path
error: err_msg
}
return
}
parsed_file := ParsedFile{
path: file_path
module_name: vfile.mod
vfile: vfile
parse_error: ''
}
parser.parsed_files[file_path] = parsed_file
// Index by module
if vfile.mod !in parser.modules {
parser.modules[vfile.mod] = []string{}
}
parser.modules[vfile.mod] << file_path
}
// has_errors returns true if any parsing errors occurred
pub fn (parser CodeParser) has_errors() bool {
return parser.parse_errors.len > 0
}
// error_count returns the number of parsing errors
pub fn (parser CodeParser) error_count() int {
return parser.parse_errors.len
}
// print_errors prints all parsing errors to stdout
pub fn (parser CodeParser) print_errors() {
if parser.parse_errors.len == 0 {
println('No parsing errors')
return
}
println('Parsing Errors (${parser.parse_errors.len}):')
for err in parser.parse_errors {
println(' ${err.file_path}: ${err.error}')
}
}

View File

@@ -0,0 +1,73 @@
module codeparser
import incubaid.herolib.core.code
// filter_structs filters structs using a predicate function
//
// Args:
// predicate - function that returns true for structs to include
// module - optional module filter
pub fn (parser CodeParser) filter_structs(predicate: fn(code.Struct) bool, module: string = '') []code.Struct {
structs := parser.list_structs(module)
return structs.filter(predicate(it))
}
// filter_functions filters functions using a predicate function
pub fn (parser CodeParser) filter_functions(predicate: fn(code.Function) bool, module: string = '') []code.Function {
functions := parser.list_functions(module)
return functions.filter(predicate(it))
}
// filter_public_structs returns only public structs
pub fn (parser CodeParser) filter_public_structs(module: string = '') []code.Struct {
return parser.filter_structs(fn (s code.Struct) bool {
return s.is_pub
}, module)
}
// filter_public_functions returns only public functions
pub fn (parser CodeParser) filter_public_functions(module: string = '') []code.Function {
return parser.filter_functions(fn (f code.Function) bool {
return f.is_pub
}, module)
}
// filter_functions_with_receiver returns functions that have a receiver (methods)
pub fn (parser CodeParser) filter_functions_with_receiver(module: string = '') []code.Function {
return parser.filter_functions(fn (f code.Function) bool {
return f.receiver.name != ''
}, module)
}
// filter_functions_returning_error returns functions that return error type (${ error type with ! })
pub fn (parser CodeParser) filter_functions_returning_error(module: string = '') []code.Function {
return parser.filter_functions(fn (f code.Function) bool {
return f.has_return || f.result.is_result
}, module)
}
// filter_structs_with_field returns structs that have a field of a specific type
pub fn (parser CodeParser) filter_structs_with_field(field_type: string, module: string = '') []code.Struct {
return parser.filter_structs(fn [field_type] (s code.Struct) bool {
for field in s.fields {
if field.typ.symbol() == field_type {
return true
}
}
return false
}, module)
}
// filter_by_name_pattern returns items matching a name pattern (substring match)
pub fn (parser CodeParser) filter_structs_by_name(pattern: string, module: string = '') []code.Struct {
return parser.filter_structs(fn [pattern] (s code.Struct) bool {
return s.name.contains(pattern)
}, module)
}
// filter_functions_by_name returns functions matching a name pattern
pub fn (parser CodeParser) filter_functions_by_name(pattern: string, module: string = '') []code.Function {
return parser.filter_functions(fn [pattern] (f code.Function) bool {
return f.name.contains(pattern)
}, module)
}

View File

@@ -0,0 +1,164 @@
module codeparser
import incubaid.herolib.core.code
// SearchContext provides context for a found item
pub struct SearchContext {
pub:
file_path string
module_name string
line_number int // optional, 0 if unknown
}
// find_struct searches for a struct by name
//
// Args:
// name string - struct name to find
// module string - optional module filter
// Returns:
// Struct - if found
// error - if not found
pub fn (parser CodeParser) find_struct(name: string, module: string = '') !code.Struct {
for _, parsed_file in parser.parsed_files {
if module != '' && parsed_file.module_name != module {
continue
}
structs := parsed_file.vfile.structs()
for struct_ in structs {
if struct_.name == name {
return struct_
}
}
}
return error('struct \'${name}\' not found${if module != '' { ' in module \'${module}\'' } else { '' }}')
}
// find_function searches for a function by name
//
// Args:
// name string - function name to find
// module string - optional module filter
// Returns:
// Function - if found
// error - if not found
pub fn (parser CodeParser) find_function(name: string, module: string = '') !code.Function {
for _, parsed_file in parser.parsed_files {
if module != '' && parsed_file.module_name != module {
continue
}
if func := parsed_file.vfile.get_function(name) {
return func
}
}
return error('function \'${name}\' not found${if module != '' { ' in module \'${module}\'' } else { '' }}')
}
// find_interface searches for an interface by name
pub fn (parser CodeParser) find_interface(name: string, module: string = '') !code.Interface {
for _, parsed_file in parser.parsed_files {
if module != '' && parsed_file.module_name != module {
continue
}
for item in parsed_file.vfile.items {
if item is code.Interface {
iface := item as code.Interface
if iface.name == name {
return iface
}
}
}
}
return error('interface \'${name}\' not found${if module != '' { ' in module \'${module}\'' } else { '' }}')
}
// find_method searches for a method on a struct
//
// Args:
// struct_name string - name of the struct
// method_name string - name of the method
// module string - optional module filter
// Returns:
// Function - if found
// error - if not found
pub fn (parser CodeParser) find_method(struct_name: string, method_name: string, module: string = '') !code.Function {
methods := parser.list_methods_on_struct(struct_name, module)
for method in methods {
if method.name == method_name {
return method
}
}
return error('method \'${method_name}\' on struct \'${struct_name}\' not found${if module != '' { ' in module \'${module}\'' } else { '' }}')
}
// find_module searches for a module by name
pub fn (parser CodeParser) find_module(module_name: string) !ParsedModule {
if module_name !in parser.modules {
return error('module \'${module_name}\' not found')
}
file_paths := parser.modules[module_name]
mut stats := ModuleStats{}
for file_path in file_paths {
if parsed_file := parser.parsed_files[file_path] {
stats.file_count++
stats.struct_count += parsed_file.vfile.structs().len
stats.function_count += parsed_file.vfile.functions().len
stats.const_count += parsed_file.vfile.consts.len
}
}
return ParsedModule{
name: module_name
file_paths: file_paths
stats: stats
}
}
// find_file retrieves parsed file information
pub fn (parser CodeParser) find_file(path: string) !ParsedFile {
if path !in parser.parsed_files {
return error('file \'${path}\' not found in parsed files')
}
return parser.parsed_files[path]
}
// find_structs_with_method finds all structs that have a specific method
pub fn (parser CodeParser) find_structs_with_method(method_name: string, module: string = '') []string {
mut struct_names := []string{}
functions := parser.list_functions(module)
for func in functions {
if func.name == method_name && func.receiver.name != '' {
struct_type := func.receiver.typ.symbol()
if struct_type !in struct_names {
struct_names << struct_type
}
}
}
return struct_names
}
// find_callers finds all functions that call a specific function (basic text matching)
pub fn (parser CodeParser) find_callers(function_name: string, module: string = '') []code.Function {
mut callers := []code.Function{}
functions := parser.list_functions(module)
for func in functions {
if func.body.contains(function_name) {
callers << func
}
}
return callers
}

View File

@@ -0,0 +1,192 @@
module codeparser
import json
import incubaid.herolib.core.code
// JSON export structures
pub struct CodeParserJSON {
pub:
root_dir string
modules map[string]ModuleJSON
summary SummaryJSON
}
pub struct ModuleJSON {
pub:
name string
files map[string]FileJSON
stats ModuleStats
imports []string
}
pub struct FileJSON {
pub:
path string
module_name string
items_count int
structs []StructJSON
functions []FunctionJSON
interfaces []InterfaceJSON
constants []ConstJSON
}
pub struct StructJSON {
pub:
name string
is_pub bool
field_count int
description string
}
pub struct FunctionJSON {
pub:
name string
is_pub bool
has_return bool
params int
receiver string
}
pub struct InterfaceJSON {
pub:
name string
is_pub bool
description string
}
pub struct ConstJSON {
pub:
name string
value string
}
pub struct SummaryJSON {
pub:
total_files int
total_modules int
total_structs int
total_functions int
total_interfaces int
}
// to_json exports the complete code structure to JSON
//
// Args:
// module - optional module filter (if empty, exports all modules)
// Returns:
// JSON string representation
pub fn (parser CodeParser) to_json(module: string = '') !string {
mut result := CodeParserJSON{
root_dir: parser.root_dir
modules: map[string]ModuleJSON{}
summary: SummaryJSON{}
}
modules_to_process := if module != '' {
if module in parser.modules {
[module]
} else {
return error('module \'${module}\' not found')
}
} else {
parser.list_modules()
}
for mod_name in modules_to_process {
file_paths := parser.modules[mod_name]
mut module_json := ModuleJSON{
name: mod_name
files: map[string]FileJSON{}
imports: []string{}
}
for file_path in file_paths {
if parsed_file := parser.parsed_files[file_path] {
vfile := parsed_file.vfile
// Build structs JSON
mut structs_json := []StructJSON{}
for struct_ in vfile.structs() {
structs_json << StructJSON{
name: struct_.name
is_pub: struct_.is_pub
field_count: struct_.fields.len
description: struct_.description
}
}
// Build functions JSON
mut functions_json := []FunctionJSON{}
for func in vfile.functions() {
functions_json << FunctionJSON{
name: func.name
is_pub: func.is_pub
has_return: func.has_return
params: func.params.len
receiver: func.receiver.typ.symbol()
}
}
// Build interfaces JSON
mut interfaces_json := []InterfaceJSON{}
for item in vfile.items {
if item is code.Interface {
iface := item as code.Interface
interfaces_json << InterfaceJSON{
name: iface.name
is_pub: iface.is_pub
description: iface.description
}
}
}
// Build constants JSON
mut consts_json := []ConstJSON{}
for const_ in vfile.consts {
consts_json << ConstJSON{
name: const_.name
value: const_.value
}
}
file_json := FileJSON{
path: file_path
module_name: vfile.mod
items_count: vfile.items.len
structs: structs_json
functions: functions_json
interfaces: interfaces_json
constants: consts_json
}
module_json.files[file_path] = file_json
// Add imports to module level
for imp in vfile.imports {
if imp.mod !in module_json.imports {
module_json.imports << imp.mod
}
}
// Update summary
result.summary.total_structs += structs_json.len
result.summary.total_functions += functions_json.len
result.summary.total_interfaces += interfaces_json.len
}
}
module_json.stats = parser.get_module_stats(mod_name)
result.modules[mod_name] = module_json
result.summary.total_modules++
}
result.summary.total_files = result.modules.values().map(it.stats.file_count).sum()
return json.encode(result)
}
// to_json_pretty exports to pretty-printed JSON
pub fn (parser CodeParser) to_json_pretty(module: string = '') !string {
json_str := parser.to_json(module)!
return json.encode_pretty(json.decode(map[string]interface{}, json_str)!)
}

View File

@@ -0,0 +1,149 @@
module codeparser
import incubaid.herolib.core.code
// list_modules returns all module names found in the codebase
pub fn (parser CodeParser) list_modules() []string {
return parser.modules.keys()
}
// list_files returns all parsed file paths
pub fn (parser CodeParser) list_files() []string {
return parser.parsed_files.keys()
}
// list_files_in_module returns all file paths in a specific module
pub fn (parser CodeParser) list_files_in_module(module: string) []string {
return parser.modules[module] or { []string{} }
}
// list_structs returns all structs in the codebase (optionally filtered by module)
pub fn (parser CodeParser) list_structs(module: string = '') []code.Struct {
mut structs := []code.Struct{}
for _, parsed_file in parser.parsed_files {
// Skip if module filter is provided and doesn't match
if module != '' && parsed_file.module_name != module {
continue
}
file_structs := parsed_file.vfile.structs()
structs << file_structs
}
return structs
}
// list_functions returns all functions in the codebase (optionally filtered by module)
pub fn (parser CodeParser) list_functions(module: string = '') []code.Function {
mut functions := []code.Function{}
for _, parsed_file in parser.parsed_files {
if module != '' && parsed_file.module_name != module {
continue
}
file_functions := parsed_file.vfile.functions()
functions << file_functions
}
return functions
}
// list_interfaces returns all interfaces in the codebase (optionally filtered by module)
pub fn (parser CodeParser) list_interfaces(module: string = '') []code.Interface {
mut interfaces := []code.Interface{}
for _, parsed_file in parser.parsed_files {
if module != '' && parsed_file.module_name != module {
continue
}
// Extract interfaces from items
for item in parsed_file.vfile.items {
if item is code.Interface {
interfaces << item
}
}
}
return interfaces
}
// list_methods_on_struct returns all methods (receiver functions) for a struct
//
// Args:
// struct_name string - name of the struct
// module string - optional module filter
pub fn (parser CodeParser) list_methods_on_struct(struct_name: string, module: string = '') []code.Function {
mut methods := []code.Function{}
functions := parser.list_functions(module)
for func in functions {
// Check if function has a receiver of the matching type
if func.receiver.typ.symbol().contains(struct_name) {
methods << func
}
}
return methods
}
// list_imports returns all unique imports used in the codebase (optionally filtered by module)
pub fn (parser CodeParser) list_imports(module: string = '') []code.Import {
mut imports := map[string]code.Import{}
for _, parsed_file in parser.parsed_files {
if module != '' && parsed_file.module_name != module {
continue
}
for imp in parsed_file.vfile.imports {
imports[imp.mod] = imp
}
}
return imports.values()
}
// list_constants returns all constants in the codebase (optionally filtered by module)
pub fn (parser CodeParser) list_constants(module: string = '') []code.Const {
mut consts := []code.Const{}
for _, parsed_file in parser.parsed_files {
if module != '' && parsed_file.module_name != module {
continue
}
consts << parsed_file.vfile.consts
}
return consts
}
// get_module_stats calculates statistics for a module
pub fn (parser CodeParser) get_module_stats(module: string) ModuleStats {
mut stats := ModuleStats{}
file_paths := parser.list_files_in_module(module)
stats.file_count = file_paths.len
for _, parsed_file in parser.parsed_files {
if parsed_file.module_name != module {
continue
}
stats.struct_count += parsed_file.vfile.structs().len
stats.function_count += parsed_file.vfile.functions().len
stats.const_count += parsed_file.vfile.consts.len
// Count interfaces
for item in parsed_file.vfile.items {
if item is code.Interface {
stats.interface_count++
}
}
}
return stats
}

View File

@@ -0,0 +1,64 @@
module testdata
import time
import json
// create_user creates a new user in the system
// Arguments:
// email: user email address
// username: unique username
// Returns: the created User or error
pub fn create_user(email string, username string) !User {
if email == '' {
return error('email cannot be empty')
}
if username == '' {
return error('username cannot be empty')
}
return User{
id: 1
email: email
username: username
active: true
created: time.now().str()
updated: time.now().str()
}
}
// get_user retrieves a user by ID
pub fn get_user(user_id int) ?User {
if user_id <= 0 {
return none
}
return User{
id: user_id
email: 'user_${user_id}@example.com'
username: 'user_${user_id}'
active: true
created: '2024-01-01'
updated: '2024-01-01'
}
}
// delete_user deletes a user from the system
pub fn delete_user(user_id int) ! {
if user_id <= 0 {
return error('invalid user id')
}
}
// Internal helper for validation
fn validate_email(email string) bool {
return email.contains('@')
}
// Process multiple users
fn batch_create_users(emails []string) ![]User {
mut users := []User{}
for email in emails {
user_name := email.split('@')[0]
user := create_user(email, user_name)!
users << user
}
return users
}

40
lib/core/codeparser/testdata/methods.v vendored Normal file
View File

@@ -0,0 +1,40 @@
module testdata
import time
// activate sets the user as active
pub fn (mut u User) activate() {
u.active = true
u.updated = time.now().str()
}
// deactivate sets the user as inactive
pub fn (mut u User) deactivate() {
u.active = false
u.updated = time.now().str()
}
// is_active returns whether the user is active
pub fn (u User) is_active() bool {
return u.active
}
// get_display_name returns the display name for the user
pub fn (u &User) get_display_name() string {
if u.username != '' {
return u.username
}
return u.email
}
// set_profile updates the user profile
pub fn (mut u User) set_profile(mut profile Profile) ! {
if profile.user_id != u.id {
return error('profile does not belong to this user')
}
}
// get_profile_info returns profile information as string
pub fn (p &Profile) get_profile_info() string {
return 'Bio: ${p.bio}, Followers: ${p.followers}'
}

51
lib/core/codeparser/testdata/models.v vendored Normal file
View File

@@ -0,0 +1,51 @@
module testdata
import time
import os
const (
app_version = '1.0.0'
max_users = 1000
default_timeout = 30
)
// User represents an application user
// It stores all information related to a user
// including contact and status information
pub struct User {
pub:
id int
email string
username string
pub mut:
active bool
created string
updated string
}
// Profile represents user profile information
pub struct Profile {
pub:
user_id int
bio string
avatar string
mut:
followers int
following int
pub mut:
verified bool
}
// Settings represents user settings
struct Settings {
pub:
theme_dark bool
language string
mut:
notifications_enabled bool
}
struct InternalConfig {
debug bool
log_level int
}

View File

@@ -0,0 +1,36 @@
module services
import time
// Cache represents in-memory cache
pub struct Cache {
pub mut:
max_size int = 1000
mut:
items map[string]string
}
// new creates a new cache instance
pub fn Cache.new() &Cache {
return &Cache{
items: map[string]string{}
}
}
// set stores a value in cache with TTL
pub fn (mut c Cache) set(key string, value string, ttl int) {
c.items[key] = value
}
// get retrieves a value from cache
pub fn (c &Cache) get(key string) ?string {
if key in c.items {
return c.items[key]
}
return none
}
// clear removes all items from cache
pub fn (mut c Cache) clear() {
c.items.clear()
}

View File

@@ -0,0 +1,49 @@
module services
import time
// Database handles all database operations
pub struct Database {
pub:
host string
port int
pub mut:
connected bool
pool_size int = 10
}
// new creates a new database connection
pub fn Database.new(host string, port int) !Database {
mut db := Database{
host: host
port: port
connected: false
}
return db
}
// connect establishes database connection
pub fn (mut db Database) connect() ! {
if db.host == '' {
return error('host cannot be empty')
}
db.connected = true
}
// disconnect closes database connection
pub fn (mut db Database) disconnect() ! {
db.connected = false
}
// query executes a database query
pub fn (db &Database) query(sql string) ![]map[string]string {
if !db.connected {
return error('database not connected')
}
return []map[string]string{}
}
// execute_command executes a command and returns rows affected
pub fn (db &Database) execute_command(cmd string) !int {
return 0
}

View File

@@ -0,0 +1,44 @@
module utils
import crypto.md5
// Helper functions for common operations
// sanitize_input removes potentially dangerous characters
pub fn sanitize_input(input string) string {
return input.replace('<', '').replace('>', '')
}
// validate_password checks if password meets requirements
pub fn validate_password(password string) bool {
return password.len >= 8
}
// hash_password creates a hash of the password
pub fn hash_password(password string) string {
return md5.sum(password.bytes()).hex()
}
// generate_token creates a random token
// It uses current time to generate unique tokens
fn generate_token() string {
return 'token_12345'
}
// convert_to_json converts a user to JSON
pub fn (u User) to_json() string {
return '{}'
}
// compare_emails checks if two emails are the same
pub fn compare_emails(email1 string, email2 string) bool {
return email1.to_lower() == email2.to_lower()
}
// truncate_string limits string to max length
fn truncate_string(text string, max_len int) string {
if text.len > max_len {
return text[..max_len]
}
return text
}

View File

@@ -0,0 +1,26 @@
module utils
// Email pattern validator
pub fn is_valid_email(email string) bool {
return email.contains('@') && email.contains('.')
}
// Phone number validator
pub fn is_valid_phone(phone string) bool {
return phone.len >= 10
}
// ID validator
fn is_valid_id(id int) bool {
return id > 0
}
// Check if string is alphanumeric
pub fn is_alphanumeric(text string) bool {
for c in text {
if !(c.is_alnum()) {
return false
}
}
return true
}