...
This commit is contained in:
124
lib/core/codeparser/README.md
Normal file
124
lib/core/codeparser/README.md
Normal 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
|
||||
363
lib/core/codeparser/advanced_test.v
Normal file
363
lib/core/codeparser/advanced_test.v
Normal 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)
|
||||
}
|
||||
150
lib/core/codeparser/codeparser.v
Normal file
150
lib/core/codeparser/codeparser.v
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
123
lib/core/codeparser/factory.v
Normal file
123
lib/core/codeparser/factory.v
Normal 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}')
|
||||
}
|
||||
}
|
||||
73
lib/core/codeparser/filters.v
Normal file
73
lib/core/codeparser/filters.v
Normal 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)
|
||||
}
|
||||
164
lib/core/codeparser/finders.v
Normal file
164
lib/core/codeparser/finders.v
Normal 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
|
||||
}
|
||||
192
lib/core/codeparser/json_export.v
Normal file
192
lib/core/codeparser/json_export.v
Normal 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)!)
|
||||
}
|
||||
149
lib/core/codeparser/listers.v
Normal file
149
lib/core/codeparser/listers.v
Normal 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
|
||||
}
|
||||
64
lib/core/codeparser/testdata/functions.v
vendored
Normal file
64
lib/core/codeparser/testdata/functions.v
vendored
Normal 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
40
lib/core/codeparser/testdata/methods.v
vendored
Normal 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
51
lib/core/codeparser/testdata/models.v
vendored
Normal 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
|
||||
}
|
||||
36
lib/core/codeparser/testdata/services/cache.v
vendored
Normal file
36
lib/core/codeparser/testdata/services/cache.v
vendored
Normal 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()
|
||||
}
|
||||
49
lib/core/codeparser/testdata/services/database.v
vendored
Normal file
49
lib/core/codeparser/testdata/services/database.v
vendored
Normal 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
|
||||
}
|
||||
44
lib/core/codeparser/testdata/utils/helpers.v
vendored
Normal file
44
lib/core/codeparser/testdata/utils/helpers.v
vendored
Normal 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
|
||||
}
|
||||
26
lib/core/codeparser/testdata/utils/validators.v
vendored
Normal file
26
lib/core/codeparser/testdata/utils/validators.v
vendored
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user