codebase_index 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +29 -0
- data/CODE_OF_CONDUCT.md +83 -0
- data/CONTRIBUTING.md +65 -0
- data/LICENSE.txt +21 -0
- data/README.md +481 -0
- data/exe/codebase-console-mcp +22 -0
- data/exe/codebase-index-mcp +61 -0
- data/exe/codebase-index-mcp-http +64 -0
- data/exe/codebase-index-mcp-start +58 -0
- data/lib/codebase_index/ast/call_site_extractor.rb +106 -0
- data/lib/codebase_index/ast/method_extractor.rb +76 -0
- data/lib/codebase_index/ast/node.rb +88 -0
- data/lib/codebase_index/ast/parser.rb +653 -0
- data/lib/codebase_index/ast.rb +6 -0
- data/lib/codebase_index/builder.rb +137 -0
- data/lib/codebase_index/chunking/chunk.rb +84 -0
- data/lib/codebase_index/chunking/semantic_chunker.rb +290 -0
- data/lib/codebase_index/console/adapters/cache_adapter.rb +58 -0
- data/lib/codebase_index/console/adapters/good_job_adapter.rb +66 -0
- data/lib/codebase_index/console/adapters/sidekiq_adapter.rb +66 -0
- data/lib/codebase_index/console/adapters/solid_queue_adapter.rb +66 -0
- data/lib/codebase_index/console/audit_logger.rb +75 -0
- data/lib/codebase_index/console/bridge.rb +170 -0
- data/lib/codebase_index/console/confirmation.rb +90 -0
- data/lib/codebase_index/console/connection_manager.rb +173 -0
- data/lib/codebase_index/console/console_response_renderer.rb +78 -0
- data/lib/codebase_index/console/model_validator.rb +81 -0
- data/lib/codebase_index/console/safe_context.rb +82 -0
- data/lib/codebase_index/console/server.rb +557 -0
- data/lib/codebase_index/console/sql_validator.rb +172 -0
- data/lib/codebase_index/console/tools/tier1.rb +118 -0
- data/lib/codebase_index/console/tools/tier2.rb +117 -0
- data/lib/codebase_index/console/tools/tier3.rb +110 -0
- data/lib/codebase_index/console/tools/tier4.rb +79 -0
- data/lib/codebase_index/coordination/pipeline_lock.rb +109 -0
- data/lib/codebase_index/cost_model/embedding_cost.rb +88 -0
- data/lib/codebase_index/cost_model/estimator.rb +128 -0
- data/lib/codebase_index/cost_model/provider_pricing.rb +67 -0
- data/lib/codebase_index/cost_model/storage_cost.rb +52 -0
- data/lib/codebase_index/cost_model.rb +22 -0
- data/lib/codebase_index/db/migrations/001_create_units.rb +38 -0
- data/lib/codebase_index/db/migrations/002_create_edges.rb +35 -0
- data/lib/codebase_index/db/migrations/003_create_embeddings.rb +37 -0
- data/lib/codebase_index/db/migrations/004_create_snapshots.rb +45 -0
- data/lib/codebase_index/db/migrations/005_create_snapshot_units.rb +40 -0
- data/lib/codebase_index/db/migrator.rb +71 -0
- data/lib/codebase_index/db/schema_version.rb +73 -0
- data/lib/codebase_index/dependency_graph.rb +227 -0
- data/lib/codebase_index/embedding/indexer.rb +130 -0
- data/lib/codebase_index/embedding/openai.rb +105 -0
- data/lib/codebase_index/embedding/provider.rb +135 -0
- data/lib/codebase_index/embedding/text_preparer.rb +112 -0
- data/lib/codebase_index/evaluation/baseline_runner.rb +115 -0
- data/lib/codebase_index/evaluation/evaluator.rb +146 -0
- data/lib/codebase_index/evaluation/metrics.rb +79 -0
- data/lib/codebase_index/evaluation/query_set.rb +148 -0
- data/lib/codebase_index/evaluation/report_generator.rb +90 -0
- data/lib/codebase_index/extracted_unit.rb +145 -0
- data/lib/codebase_index/extractor.rb +956 -0
- data/lib/codebase_index/extractors/action_cable_extractor.rb +228 -0
- data/lib/codebase_index/extractors/ast_source_extraction.rb +46 -0
- data/lib/codebase_index/extractors/behavioral_profile.rb +309 -0
- data/lib/codebase_index/extractors/caching_extractor.rb +261 -0
- data/lib/codebase_index/extractors/callback_analyzer.rb +232 -0
- data/lib/codebase_index/extractors/concern_extractor.rb +253 -0
- data/lib/codebase_index/extractors/configuration_extractor.rb +219 -0
- data/lib/codebase_index/extractors/controller_extractor.rb +494 -0
- data/lib/codebase_index/extractors/database_view_extractor.rb +278 -0
- data/lib/codebase_index/extractors/decorator_extractor.rb +260 -0
- data/lib/codebase_index/extractors/engine_extractor.rb +204 -0
- data/lib/codebase_index/extractors/event_extractor.rb +211 -0
- data/lib/codebase_index/extractors/factory_extractor.rb +289 -0
- data/lib/codebase_index/extractors/graphql_extractor.rb +917 -0
- data/lib/codebase_index/extractors/i18n_extractor.rb +117 -0
- data/lib/codebase_index/extractors/job_extractor.rb +369 -0
- data/lib/codebase_index/extractors/lib_extractor.rb +249 -0
- data/lib/codebase_index/extractors/mailer_extractor.rb +339 -0
- data/lib/codebase_index/extractors/manager_extractor.rb +202 -0
- data/lib/codebase_index/extractors/middleware_extractor.rb +133 -0
- data/lib/codebase_index/extractors/migration_extractor.rb +469 -0
- data/lib/codebase_index/extractors/model_extractor.rb +960 -0
- data/lib/codebase_index/extractors/phlex_extractor.rb +252 -0
- data/lib/codebase_index/extractors/policy_extractor.rb +214 -0
- data/lib/codebase_index/extractors/poro_extractor.rb +246 -0
- data/lib/codebase_index/extractors/pundit_extractor.rb +223 -0
- data/lib/codebase_index/extractors/rails_source_extractor.rb +473 -0
- data/lib/codebase_index/extractors/rake_task_extractor.rb +343 -0
- data/lib/codebase_index/extractors/route_extractor.rb +181 -0
- data/lib/codebase_index/extractors/scheduled_job_extractor.rb +331 -0
- data/lib/codebase_index/extractors/serializer_extractor.rb +334 -0
- data/lib/codebase_index/extractors/service_extractor.rb +254 -0
- data/lib/codebase_index/extractors/shared_dependency_scanner.rb +91 -0
- data/lib/codebase_index/extractors/shared_utility_methods.rb +99 -0
- data/lib/codebase_index/extractors/state_machine_extractor.rb +398 -0
- data/lib/codebase_index/extractors/test_mapping_extractor.rb +225 -0
- data/lib/codebase_index/extractors/validator_extractor.rb +225 -0
- data/lib/codebase_index/extractors/view_component_extractor.rb +310 -0
- data/lib/codebase_index/extractors/view_template_extractor.rb +261 -0
- data/lib/codebase_index/feedback/gap_detector.rb +89 -0
- data/lib/codebase_index/feedback/store.rb +119 -0
- data/lib/codebase_index/flow_analysis/operation_extractor.rb +209 -0
- data/lib/codebase_index/flow_analysis/response_code_mapper.rb +154 -0
- data/lib/codebase_index/flow_assembler.rb +290 -0
- data/lib/codebase_index/flow_document.rb +191 -0
- data/lib/codebase_index/flow_precomputer.rb +102 -0
- data/lib/codebase_index/formatting/base.rb +40 -0
- data/lib/codebase_index/formatting/claude_adapter.rb +98 -0
- data/lib/codebase_index/formatting/generic_adapter.rb +56 -0
- data/lib/codebase_index/formatting/gpt_adapter.rb +64 -0
- data/lib/codebase_index/formatting/human_adapter.rb +78 -0
- data/lib/codebase_index/graph_analyzer.rb +374 -0
- data/lib/codebase_index/mcp/index_reader.rb +394 -0
- data/lib/codebase_index/mcp/renderers/claude_renderer.rb +81 -0
- data/lib/codebase_index/mcp/renderers/json_renderer.rb +17 -0
- data/lib/codebase_index/mcp/renderers/markdown_renderer.rb +352 -0
- data/lib/codebase_index/mcp/renderers/plain_renderer.rb +240 -0
- data/lib/codebase_index/mcp/server.rb +935 -0
- data/lib/codebase_index/mcp/tool_response_renderer.rb +62 -0
- data/lib/codebase_index/model_name_cache.rb +51 -0
- data/lib/codebase_index/notion/client.rb +217 -0
- data/lib/codebase_index/notion/exporter.rb +219 -0
- data/lib/codebase_index/notion/mapper.rb +39 -0
- data/lib/codebase_index/notion/mappers/column_mapper.rb +65 -0
- data/lib/codebase_index/notion/mappers/migration_mapper.rb +39 -0
- data/lib/codebase_index/notion/mappers/model_mapper.rb +164 -0
- data/lib/codebase_index/notion/rate_limiter.rb +68 -0
- data/lib/codebase_index/observability/health_check.rb +81 -0
- data/lib/codebase_index/observability/instrumentation.rb +34 -0
- data/lib/codebase_index/observability/structured_logger.rb +75 -0
- data/lib/codebase_index/operator/error_escalator.rb +81 -0
- data/lib/codebase_index/operator/pipeline_guard.rb +99 -0
- data/lib/codebase_index/operator/status_reporter.rb +80 -0
- data/lib/codebase_index/railtie.rb +26 -0
- data/lib/codebase_index/resilience/circuit_breaker.rb +99 -0
- data/lib/codebase_index/resilience/index_validator.rb +185 -0
- data/lib/codebase_index/resilience/retryable_provider.rb +108 -0
- data/lib/codebase_index/retrieval/context_assembler.rb +249 -0
- data/lib/codebase_index/retrieval/query_classifier.rb +131 -0
- data/lib/codebase_index/retrieval/ranker.rb +273 -0
- data/lib/codebase_index/retrieval/search_executor.rb +327 -0
- data/lib/codebase_index/retriever.rb +160 -0
- data/lib/codebase_index/ruby_analyzer/class_analyzer.rb +190 -0
- data/lib/codebase_index/ruby_analyzer/dataflow_analyzer.rb +78 -0
- data/lib/codebase_index/ruby_analyzer/fqn_builder.rb +18 -0
- data/lib/codebase_index/ruby_analyzer/mermaid_renderer.rb +275 -0
- data/lib/codebase_index/ruby_analyzer/method_analyzer.rb +143 -0
- data/lib/codebase_index/ruby_analyzer/trace_enricher.rb +139 -0
- data/lib/codebase_index/ruby_analyzer.rb +87 -0
- data/lib/codebase_index/session_tracer/file_store.rb +111 -0
- data/lib/codebase_index/session_tracer/middleware.rb +143 -0
- data/lib/codebase_index/session_tracer/redis_store.rb +112 -0
- data/lib/codebase_index/session_tracer/session_flow_assembler.rb +263 -0
- data/lib/codebase_index/session_tracer/session_flow_document.rb +223 -0
- data/lib/codebase_index/session_tracer/solid_cache_store.rb +145 -0
- data/lib/codebase_index/session_tracer/store.rb +67 -0
- data/lib/codebase_index/storage/graph_store.rb +120 -0
- data/lib/codebase_index/storage/metadata_store.rb +169 -0
- data/lib/codebase_index/storage/pgvector.rb +163 -0
- data/lib/codebase_index/storage/qdrant.rb +172 -0
- data/lib/codebase_index/storage/vector_store.rb +156 -0
- data/lib/codebase_index/temporal/snapshot_store.rb +341 -0
- data/lib/codebase_index/version.rb +5 -0
- data/lib/codebase_index.rb +223 -0
- data/lib/generators/codebase_index/install_generator.rb +32 -0
- data/lib/generators/codebase_index/pgvector_generator.rb +37 -0
- data/lib/generators/codebase_index/templates/add_pgvector_to_codebase_index.rb.erb +15 -0
- data/lib/generators/codebase_index/templates/create_codebase_index_tables.rb.erb +43 -0
- data/lib/tasks/codebase_index.rake +583 -0
- data/lib/tasks/codebase_index_evaluation.rake +115 -0
- metadata +252 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Self-healing wrapper for the CodebaseIndex MCP server.
|
|
3
|
+
# Ensures Ruby dependencies are installed, validates the index directory,
|
|
4
|
+
# and starts the stdio MCP server.
|
|
5
|
+
#
|
|
6
|
+
# Usage (direct):
|
|
7
|
+
# codebase-index-mcp-start /path/to/index_dir
|
|
8
|
+
#
|
|
9
|
+
# Usage (.mcp.json):
|
|
10
|
+
# {
|
|
11
|
+
# "command": "${HOME}/work/codebase_index/exe/codebase-index-mcp-start",
|
|
12
|
+
# "args": ["${HOME}/my-rails-app/tmp/codebase_index"]
|
|
13
|
+
# }
|
|
14
|
+
#
|
|
15
|
+
# All diagnostic output goes to stderr to keep stdio clean for MCP protocol.
|
|
16
|
+
|
|
17
|
+
set -euo pipefail
|
|
18
|
+
|
|
19
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
20
|
+
GEM_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
21
|
+
INDEX_DIR="${1:-${CODEBASE_INDEX_DIR:-}}"
|
|
22
|
+
|
|
23
|
+
# --- Validate index directory ---
|
|
24
|
+
if [[ -z "$INDEX_DIR" ]]; then
|
|
25
|
+
echo "Error: No index directory specified." >&2
|
|
26
|
+
echo "Usage: codebase-index-mcp-start /path/to/index_dir" >&2
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
if [[ ! -d "$INDEX_DIR" ]]; then
|
|
31
|
+
echo "Error: Index directory does not exist: $INDEX_DIR" >&2
|
|
32
|
+
echo "Run extraction first: bundle exec rake codebase_index:extract" >&2
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
if [[ ! -f "$INDEX_DIR/manifest.json" ]]; then
|
|
37
|
+
echo "Error: No manifest.json in: $INDEX_DIR" >&2
|
|
38
|
+
echo "Run extraction first: bundle exec rake codebase_index:extract" >&2
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# --- Ensure Ruby dependencies are installed ---
|
|
43
|
+
export BUNDLE_GEMFILE="${GEM_DIR}/Gemfile"
|
|
44
|
+
|
|
45
|
+
if ! bundle check > /dev/null 2>&1; then
|
|
46
|
+
echo "Installing codebase_index dependencies..." >&2
|
|
47
|
+
if ! bundle install --quiet >&2 2>&1; then
|
|
48
|
+
echo "Error: bundle install failed. Check Ruby version and network." >&2
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
echo "Dependencies installed." >&2
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# --- Pin MCP protocol version for Claude Code compatibility ---
|
|
55
|
+
export MCP_PROTOCOL_VERSION="${MCP_PROTOCOL_VERSION:-2024-11-05}"
|
|
56
|
+
|
|
57
|
+
# --- Start the MCP server ---
|
|
58
|
+
exec bundle exec ruby "${GEM_DIR}/exe/codebase-index-mcp" "$INDEX_DIR"
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'set'
|
|
4
|
+
require_relative 'node'
|
|
5
|
+
|
|
6
|
+
module CodebaseIndex
|
|
7
|
+
module Ast
|
|
8
|
+
# Method names that are too common to be useful in call graphs.
|
|
9
|
+
INSIGNIFICANT_METHODS = Set.new(%w[
|
|
10
|
+
to_s to_i to_f to_a to_h to_sym to_r to_c to_str to_proc
|
|
11
|
+
nil? present? blank? empty? any? none? frozen? is_a? kind_of?
|
|
12
|
+
respond_to? respond_to_missing? instance_of? equal?
|
|
13
|
+
== != eql? <=> === =~ !~ >= <= > <
|
|
14
|
+
! & | ^ ~ + - * / % **
|
|
15
|
+
freeze dup clone inspect hash object_id class
|
|
16
|
+
send __send__ method tap then yield_self itself
|
|
17
|
+
new allocate
|
|
18
|
+
[] []=
|
|
19
|
+
length size count
|
|
20
|
+
first last
|
|
21
|
+
map each select reject flat_map collect detect find_index
|
|
22
|
+
merge merge! update
|
|
23
|
+
keys values
|
|
24
|
+
push pop shift unshift
|
|
25
|
+
strip chomp chop downcase upcase
|
|
26
|
+
puts print p pp warn raise fail
|
|
27
|
+
require require_relative load autoload
|
|
28
|
+
attr_reader attr_writer attr_accessor
|
|
29
|
+
private protected public
|
|
30
|
+
include extend prepend
|
|
31
|
+
]).freeze
|
|
32
|
+
|
|
33
|
+
# Extracts call sites from an AST node tree.
|
|
34
|
+
#
|
|
35
|
+
# Returns method calls found in the tree, ordered by source line number.
|
|
36
|
+
# Used by both RubyAnalyzer (call graph building) and FlowAssembler
|
|
37
|
+
# (execution flow ordering).
|
|
38
|
+
#
|
|
39
|
+
# @example Extracting calls from a method body
|
|
40
|
+
# parser = Ast::Parser.new
|
|
41
|
+
# root = parser.parse(source)
|
|
42
|
+
# calls = Ast::CallSiteExtractor.new.extract(root)
|
|
43
|
+
# calls.first #=> { receiver: "User", method_name: "find", arguments: ["id"], line: 3, block: false }
|
|
44
|
+
#
|
|
45
|
+
class CallSiteExtractor
|
|
46
|
+
# Extract all call sites from an AST node, ordered by line number.
|
|
47
|
+
#
|
|
48
|
+
# @param node [Ast::Node] The AST node to search
|
|
49
|
+
# @return [Array<Hash>] Call site hashes ordered by line ascending
|
|
50
|
+
def extract(node)
|
|
51
|
+
calls = []
|
|
52
|
+
collect_calls(node, calls)
|
|
53
|
+
calls.sort_by { |c| c[:line] }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Extract only significant call sites, filtering out noise.
|
|
57
|
+
#
|
|
58
|
+
# @param node [Ast::Node] The AST node to search
|
|
59
|
+
# @param known_units [Array<String>] Known unit identifiers for relevance filtering
|
|
60
|
+
# @return [Array<Hash>] Filtered call site hashes
|
|
61
|
+
def extract_significant(node, known_units: [])
|
|
62
|
+
calls = extract(node)
|
|
63
|
+
known_set = Set.new(known_units)
|
|
64
|
+
|
|
65
|
+
calls.reject do |call|
|
|
66
|
+
INSIGNIFICANT_METHODS.include?(call[:method_name]) &&
|
|
67
|
+
(known_units.empty? || !known_set.include?(call[:receiver]))
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def collect_calls(node, calls)
|
|
74
|
+
return unless node.is_a?(Ast::Node)
|
|
75
|
+
|
|
76
|
+
case node.type
|
|
77
|
+
when :send
|
|
78
|
+
calls << {
|
|
79
|
+
receiver: node.receiver,
|
|
80
|
+
method_name: node.method_name,
|
|
81
|
+
arguments: node.arguments || [],
|
|
82
|
+
line: node.line,
|
|
83
|
+
block: false
|
|
84
|
+
}
|
|
85
|
+
when :block
|
|
86
|
+
# The send node in a block gets block: true
|
|
87
|
+
send_child = node.children&.first
|
|
88
|
+
if send_child.is_a?(Ast::Node) && send_child.type == :send
|
|
89
|
+
calls << {
|
|
90
|
+
receiver: send_child.receiver,
|
|
91
|
+
method_name: send_child.method_name,
|
|
92
|
+
arguments: send_child.arguments || [],
|
|
93
|
+
line: send_child.line,
|
|
94
|
+
block: true
|
|
95
|
+
}
|
|
96
|
+
end
|
|
97
|
+
# Also recurse into block body (children[1])
|
|
98
|
+
node.children&.drop(1)&.each { |child| collect_calls(child, calls) }
|
|
99
|
+
return # Don't double-recurse into children
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
(node.children || []).each { |child| collect_calls(child, calls) }
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'parser'
|
|
4
|
+
require_relative 'node'
|
|
5
|
+
|
|
6
|
+
module CodebaseIndex
|
|
7
|
+
module Ast
|
|
8
|
+
# Extracts method definitions and their source from Ruby source code.
|
|
9
|
+
#
|
|
10
|
+
# Replaces the fragile ~240 lines of `nesting_delta` / `neutralize_strings_and_comments`
|
|
11
|
+
# / `detect_heredoc_start` indentation heuristics in controller and mailer extractors.
|
|
12
|
+
#
|
|
13
|
+
# @example Extracting a method's source
|
|
14
|
+
# extractor = Ast::MethodExtractor.new
|
|
15
|
+
# source = extractor.extract_method_source(code, "create")
|
|
16
|
+
# # => "def create\n @user = User.find(params[:id])\nend\n"
|
|
17
|
+
#
|
|
18
|
+
class MethodExtractor
|
|
19
|
+
# @param parser [Ast::Parser, nil] Parser instance (creates default if nil)
|
|
20
|
+
def initialize(parser: nil)
|
|
21
|
+
@parser = parser || Parser.new
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Extract a method definition node by name.
|
|
25
|
+
#
|
|
26
|
+
# @param source [String] Ruby source code
|
|
27
|
+
# @param method_name [String] Method name to find
|
|
28
|
+
# @param class_method [Boolean] If true, look for `def self.method_name`
|
|
29
|
+
# @return [Ast::Node, nil] The :def or :defs node, or nil if not found
|
|
30
|
+
def extract_method(source, method_name, class_method: false)
|
|
31
|
+
root = @parser.parse(source)
|
|
32
|
+
target_type = class_method ? :defs : :def
|
|
33
|
+
|
|
34
|
+
root.find_all(target_type).find do |node|
|
|
35
|
+
node.method_name == method_name.to_s
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Extract all method definition nodes from source.
|
|
40
|
+
#
|
|
41
|
+
# @param source [String] Ruby source code
|
|
42
|
+
# @return [Array<Ast::Node>] All :def and :defs nodes
|
|
43
|
+
def extract_all_methods(source)
|
|
44
|
+
root = @parser.parse(source)
|
|
45
|
+
root.find_all(:def) + root.find_all(:defs)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Extract the raw source text of a method, including def...end.
|
|
49
|
+
#
|
|
50
|
+
# This is the key replacement for `extract_action_source` in the controller
|
|
51
|
+
# and mailer extractors. Uses AST line tracking instead of indentation heuristics.
|
|
52
|
+
#
|
|
53
|
+
# @param source [String] Ruby source code
|
|
54
|
+
# @param method_name [String] Method name to find
|
|
55
|
+
# @param class_method [Boolean] If true, look for `def self.method_name`
|
|
56
|
+
# @return [String, nil] The method source text, or nil if not found
|
|
57
|
+
def extract_method_source(source, method_name, class_method: false)
|
|
58
|
+
node = extract_method(source, method_name, class_method: class_method)
|
|
59
|
+
return nil unless node
|
|
60
|
+
|
|
61
|
+
# If the node has a source field populated by the parser, use it
|
|
62
|
+
return node.source if node.source
|
|
63
|
+
|
|
64
|
+
# Fallback: extract by line range
|
|
65
|
+
return nil unless node.line && node.end_line
|
|
66
|
+
|
|
67
|
+
lines = source.lines
|
|
68
|
+
start_idx = node.line - 1
|
|
69
|
+
end_idx = node.end_line - 1
|
|
70
|
+
return nil if start_idx.negative? || end_idx >= lines.length
|
|
71
|
+
|
|
72
|
+
lines[start_idx..end_idx].join
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CodebaseIndex
|
|
4
|
+
module Ast
|
|
5
|
+
# Normalized AST node struct used by all consumers.
|
|
6
|
+
#
|
|
7
|
+
# Provides a parser-independent representation of Ruby AST nodes.
|
|
8
|
+
# Both Prism and the parser gem are normalized to this common structure.
|
|
9
|
+
#
|
|
10
|
+
# @example Creating a send node
|
|
11
|
+
# node = Ast::Node.new(
|
|
12
|
+
# type: :send,
|
|
13
|
+
# children: [],
|
|
14
|
+
# line: 42,
|
|
15
|
+
# receiver: "User",
|
|
16
|
+
# method_name: "find",
|
|
17
|
+
# arguments: ["id"]
|
|
18
|
+
# )
|
|
19
|
+
#
|
|
20
|
+
Node = Struct.new(
|
|
21
|
+
:type, # Symbol: :send, :block, :if, :def, :defs, :class, :module, :const, :begin, etc.
|
|
22
|
+
:children, # Array<Ast::Node | String | Symbol | Integer | nil>
|
|
23
|
+
:line, # Integer: 1-based source line number
|
|
24
|
+
:receiver, # String | nil: method call receiver (for :send)
|
|
25
|
+
:method_name, # String | nil: method name (for :send, :def, :defs)
|
|
26
|
+
:arguments, # Array<String>: argument representations (for :send)
|
|
27
|
+
:source, # String | nil: raw source text of this node
|
|
28
|
+
:end_line, # Integer | nil: 1-based end line number (when available)
|
|
29
|
+
keyword_init: true
|
|
30
|
+
) do
|
|
31
|
+
# Find all descendant nodes matching a type.
|
|
32
|
+
#
|
|
33
|
+
# @param target_type [Symbol] The node type to search for
|
|
34
|
+
# @return [Array<Ast::Node>] All matching descendant nodes
|
|
35
|
+
def find_all(target_type)
|
|
36
|
+
results = []
|
|
37
|
+
queue = [self]
|
|
38
|
+
while (current = queue.shift)
|
|
39
|
+
results << current if current.type == target_type
|
|
40
|
+
(current.children || []).each do |child|
|
|
41
|
+
queue << child if child.is_a?(Ast::Node)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
results
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Find the first descendant node matching a type (depth-first).
|
|
48
|
+
#
|
|
49
|
+
# @param target_type [Symbol] The node type to search for
|
|
50
|
+
# @return [Ast::Node, nil] The first matching node or nil
|
|
51
|
+
def find_first(target_type)
|
|
52
|
+
return self if type == target_type
|
|
53
|
+
|
|
54
|
+
(children || []).each do |child|
|
|
55
|
+
next unless child.is_a?(Ast::Node)
|
|
56
|
+
|
|
57
|
+
result = child.find_first(target_type)
|
|
58
|
+
return result if result
|
|
59
|
+
end
|
|
60
|
+
nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Return source text representation.
|
|
64
|
+
#
|
|
65
|
+
# @return [String] The source field if present, otherwise a reconstruction
|
|
66
|
+
def to_source
|
|
67
|
+
return source if source
|
|
68
|
+
|
|
69
|
+
case type
|
|
70
|
+
when :send
|
|
71
|
+
parts = []
|
|
72
|
+
parts << receiver if receiver
|
|
73
|
+
parts << method_name if method_name
|
|
74
|
+
parts.join('.')
|
|
75
|
+
when :const
|
|
76
|
+
parts = []
|
|
77
|
+
parts << receiver if receiver
|
|
78
|
+
parts << method_name if method_name
|
|
79
|
+
parts.join('::')
|
|
80
|
+
when :def, :defs
|
|
81
|
+
"def #{method_name}"
|
|
82
|
+
else
|
|
83
|
+
type.to_s
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|