feat: Add Ruby module extraction with containment and qualified names
Addresses Ruby methods inside modules missing owner in qualified_name by adding visitNode hook to extract module AST nodes. Methods inside modules now get Module::method qualified names with proper containment relationships. Includes ExtractorContext wiring with pushScope/popScope for language hooks and updates isInsideClassLikeNode to include module kind for nested method handling.
This commit is contained in:
@@ -1787,6 +1787,70 @@ require_relative 'helper'
|
||||
});
|
||||
});
|
||||
|
||||
describe('Ruby modules', () => {
|
||||
it('should extract module as module node with containment', () => {
|
||||
const code = `
|
||||
module CachedCounting
|
||||
def self.disable
|
||||
@enabled = false
|
||||
end
|
||||
|
||||
def perform_increment!(key, count)
|
||||
write_cache!(key, count)
|
||||
end
|
||||
end
|
||||
`;
|
||||
const result = extractFromSource('concerns/cached_counting.rb', code);
|
||||
|
||||
const moduleNode = result.nodes.find((n) => n.kind === 'module' && n.name === 'CachedCounting');
|
||||
expect(moduleNode).toBeDefined();
|
||||
expect(moduleNode?.qualifiedName).toBe('CachedCounting');
|
||||
|
||||
// Methods inside module should have module-qualified names
|
||||
const disableMethod = result.nodes.find((n) => n.name === 'disable' && n.kind === 'method');
|
||||
expect(disableMethod).toBeDefined();
|
||||
expect(disableMethod?.qualifiedName).toBe('CachedCounting::disable');
|
||||
|
||||
const incrementMethod = result.nodes.find((n) => n.name === 'perform_increment!' && n.kind === 'method');
|
||||
expect(incrementMethod).toBeDefined();
|
||||
expect(incrementMethod?.qualifiedName).toBe('CachedCounting::perform_increment!');
|
||||
|
||||
// Containment edge from module to methods
|
||||
const containsEdges = result.edges.filter((e) => e.source === moduleNode?.id && e.kind === 'contains');
|
||||
expect(containsEdges.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
it('should handle nested modules with classes', () => {
|
||||
const code = `
|
||||
module Discourse
|
||||
module Auth
|
||||
class AuthProvider
|
||||
def authenticate(params)
|
||||
validate(params)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
`;
|
||||
const result = extractFromSource('lib/auth.rb', code);
|
||||
|
||||
const discourseModule = result.nodes.find((n) => n.kind === 'module' && n.name === 'Discourse');
|
||||
expect(discourseModule).toBeDefined();
|
||||
|
||||
const authModule = result.nodes.find((n) => n.kind === 'module' && n.name === 'Auth');
|
||||
expect(authModule).toBeDefined();
|
||||
expect(authModule?.qualifiedName).toBe('Discourse::Auth');
|
||||
|
||||
const authProvider = result.nodes.find((n) => n.kind === 'class' && n.name === 'AuthProvider');
|
||||
expect(authProvider).toBeDefined();
|
||||
expect(authProvider?.qualifiedName).toBe('Discourse::Auth::AuthProvider');
|
||||
|
||||
const authMethod = result.nodes.find((n) => n.name === 'authenticate');
|
||||
expect(authMethod).toBeDefined();
|
||||
expect(authMethod?.qualifiedName).toBe('Discourse::Auth::AuthProvider::authenticate');
|
||||
});
|
||||
});
|
||||
|
||||
describe('C/C++ imports', () => {
|
||||
it('should extract system include', () => {
|
||||
const code = `#include <iostream>`;
|
||||
|
||||
Reference in New Issue
Block a user