Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

- Pointer-returning C and C++ functions and variable-bound JavaScript,
TypeScript, and TSX functions and generators retain their full definitions
in the source index. Calls through a function expression's private name no
longer resolve to an unrelated outer function with the same name.

- Malformed text configuration files no longer block the entire repository
index. Their source remains visible with an explicit coverage gap; stale
symbols and structural claims from an earlier valid version are removed.
Expand Down
3 changes: 1 addition & 2 deletions crates/codestory-indexer/rules/c.scm
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
(function_definition
declarator: (function_declarator
declarator: (identifier) @name)) @def
declarator: (_) @name) @def
{
node @name.node
attr (@name.node) kind = "FUNCTION"
Expand Down
29 changes: 5 additions & 24 deletions crates/codestory-indexer/rules/cpp.scm
Original file line number Diff line number Diff line change
Expand Up @@ -89,22 +89,7 @@
}

(function_definition
declarator: (function_declarator
declarator: (_) @name)) @def
{
node @name.node
attr (@name.node) kind = "FUNCTION"
attr (@name.node) name = (source-text @name)
attr (@name.node) start_row = (start-row @def)
attr (@name.node) start_col = (start-column @def)
attr (@name.node) end_row = (end-row @def)
attr (@name.node) end_col = (end-column @def)
}

(function_definition
declarator: (pointer_declarator
declarator: (function_declarator
declarator: (_) @name))) @def
declarator: (_) @name) @def
{
node @name.node
attr (@name.node) kind = "FUNCTION"
Expand Down Expand Up @@ -168,8 +153,7 @@
name: (_) @ns_name
body: (declaration_list
(function_definition
declarator: (function_declarator
declarator: (_) @member_name))))
declarator: (_) @member_name)))
{
edge @ns_name.node -> @member_name.node
attr (@ns_name.node -> @member_name.node) kind = "MEMBER"
Expand All @@ -189,8 +173,7 @@
name: (_) @class_name
body: (field_declaration_list
(function_definition
declarator: (function_declarator
declarator: (_) @method_name))))
declarator: (_) @method_name)))
{
edge @class_name.node -> @method_name.node
attr (@class_name.node -> @method_name.node) kind = "MEMBER"
Expand Down Expand Up @@ -243,8 +226,7 @@
name: (_) @class_name
body: (field_declaration_list
(function_definition
declarator: (function_declarator
declarator: (_) @method_name))))
declarator: (_) @method_name)))
{
edge @class_name.node -> @method_name.node
attr (@class_name.node -> @method_name.node) kind = "MEMBER"
Expand Down Expand Up @@ -306,8 +288,7 @@
name: (_) @class_name
body: (field_declaration_list
(function_definition
declarator: (function_declarator
declarator: (_) @method_name)))))
declarator: (_) @method_name))))
{
edge @class_name.node -> @method_name.node
attr (@class_name.node -> @method_name.node) kind = "MEMBER"
Expand Down
11 changes: 7 additions & 4 deletions crates/codestory-indexer/rules/javascript.scm
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
(function_declaration
name: (identifier) @name) @def
[
(function_declaration name: (identifier) @name)
(generator_function_declaration name: (identifier) @name)
] @def
{
node @name.node
attr (@name.node) kind = "FUNCTION"
Expand Down Expand Up @@ -190,10 +192,11 @@
attr (@binding_name.node -> @module.node) kind = "IMPORT"
}

;; Lambda assignment
;; Callable variable bindings. The expression's optional private name is not
;; a second declaration in the enclosing scope.
(variable_declarator
name: (identifier) @name
value: (arrow_function) @def)
value: [(arrow_function) (function_expression) (generator_function)] @def)
{
node @name.node
attr (@name.node) kind = "FUNCTION"
Expand Down
23 changes: 6 additions & 17 deletions crates/codestory-indexer/rules/tsx.graph.scm
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
(function_declaration
name: (identifier) @name) @def
[
(function_declaration name: (identifier) @name)
(generator_function_declaration name: (identifier) @name)
] @def
{
node @name.node
attr (@name.node) kind = "FUNCTION"
Expand Down Expand Up @@ -387,23 +389,10 @@
attr (@binding_name.node -> @module.node) kind = "IMPORT"
}

;; Lambda assignment
(variable_declarator
name: (identifier) @name
value: (arrow_function) @def)
{
node @name.node
attr (@name.node) kind = "FUNCTION"
attr (@name.node) name = (source-text @name)
attr (@name.node) start_row = (start-row @def)
attr (@name.node) start_col = (start-column @def)
attr (@name.node) end_row = (end-row @def)
attr (@name.node) end_col = (end-column @def)
}

;; Callable variable bindings
(variable_declarator
name: (identifier) @name
value: (function_expression) @def)
value: [(arrow_function) (function_expression) (generator_function)] @def)
{
node @name.node
attr (@name.node) kind = "FUNCTION"
Expand Down
23 changes: 6 additions & 17 deletions crates/codestory-indexer/rules/typescript.graph.scm
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
(function_declaration
name: (identifier) @name) @def
[
(function_declaration name: (identifier) @name)
(generator_function_declaration name: (identifier) @name)
] @def
{
node @name.node
attr (@name.node) kind = "FUNCTION"
Expand Down Expand Up @@ -221,23 +223,10 @@
attr (@binding_name.node -> @module.node) kind = "IMPORT"
}

;; Lambda assignment
;; Callable variable bindings
(variable_declarator
name: (identifier) @name
value: (arrow_function) @def)
{
node @name.node
attr (@name.node) kind = "FUNCTION"
attr (@name.node) name = (source-text @name)
attr (@name.node) start_row = (start-row @def)
attr (@name.node) start_col = (start-column @def)
attr (@name.node) end_row = (end-row @def)
attr (@name.node) end_col = (end-column @def)
}

(variable_declarator
name: (identifier) @name
value: (function_expression) @def)
value: [(arrow_function) (function_expression) (generator_function)] @def)
{
node @name.node
attr (@name.node) kind = "FUNCTION"
Expand Down
71 changes: 71 additions & 0 deletions crates/codestory-indexer/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,15 @@ pub(crate) fn build_index_artifact_cache_key(
mix_str(&mut state, language_config.language_name);
mix_str(&mut state, language_config.graph_query);
mix_optional_str(&mut state, language_config.tags_query);
// Rust-side callable identity/scope extraction changed independently of the
// graph rules. Invalidate affected languages without discarding unrelated
// parser artifacts or changing the shared cache serialization schema.
if matches!(
language_config.language_name,
"c" | "cpp" | "javascript" | "typescript"
) {
mix_str(&mut state, "callable-identity-and-scope-v3");
}
mix_bool(&mut state, legacy_edge_identity);
mix_bool(&mut state, lazy_graph_execution);
mix_compilation_info(&mut state, root, compilation_info)?;
Expand Down Expand Up @@ -777,6 +786,68 @@ mod tests {
assert_ne!(first, second);
}

#[test]
fn parser_cache_key_invalidates_changed_callable_extraction_rules() {
for extension in ["c", "cpp", "js", "ts", "tsx"] {
let config = crate::get_language_for_ext(extension).expect("parser config");
let key = |config: &crate::LanguageConfig| {
build_index_artifact_cache_key(
Path::new("project"),
Path::new("source"),
b"unchanged source",
config,
None,
false,
true,
)
.expect("portable cache key")
};
let current = key(&config);
let old_config = crate::LanguageConfig {
graph_query: "(older_callable_rule)",
..config
};
assert_ne!(
current,
key(&old_config),
"{extension} must not reuse the old projection"
);
}
}

#[test]
fn callable_scope_cache_revision_is_limited_to_affected_languages() {
for extension in ["c", "cpp", "js", "ts", "tsx", "rs", "py"] {
let config = crate::get_language_for_ext(extension).expect("parser config");
let root = Path::new("project");
let cache_path = Path::new("source");
let source = b"unchanged source";
// Reconstruct the previously shipped key, with identical grammar
// and source bytes, to test Rust-side extraction invalidation.
let mut previous = FNV_OFFSET_BASIS;
mix_str(&mut previous, "index-artifact");
mix_u32(&mut previous, INDEX_ARTIFACT_CACHE_VERSION);
mix_path(&mut previous, cache_path).expect("portable path");
mix_bytes(&mut previous, source);
mix_str(&mut previous, config.language_name);
mix_str(&mut previous, config.graph_query);
mix_optional_str(&mut previous, config.tags_query);
mix_bool(&mut previous, false);
mix_bool(&mut previous, true);
mix_compilation_info(&mut previous, root, None).expect("portable config");
let previous = format!("v{INDEX_ARTIFACT_CACHE_VERSION}:{previous:016x}");
let current = build_index_artifact_cache_key(
root, cache_path, source, &config, None, false, true,
)
.expect("cache key");
assert_eq!(
current == previous,
matches!(extension, "rs" | "py"),
"{extension}"
);
}
}

#[test]
fn test_artifact_cache_key_skips_unportable_compile_paths() -> anyhow::Result<()> {
let temp = tempfile::tempdir()?;
Expand Down
50 changes: 45 additions & 5 deletions crates/codestory-indexer/src/languages/javascript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,12 @@ use super::typescript::{
collect_typescript_imported_type_bindings, typescript_property_belongs_to_owner,
};
use crate::{
CompiledLanguageRules, ImportedTypeBinding, LanguageRuleset, ManualReceiverCallSpec,
ManualReceiverSource, OptionalReceiverOwnerBinding, ReceiverCallSiteKey, ReceiverOwnerBinding,
collect_receiver_call_specs_in_callable, declaration_name, enclosing_node_with_kind,
javascript_binding_has_prior_write, js_like_callable_source_name,
js_ts_local_binding_visible_at_call, js_ts_visible_local_type_name, member_call_method_col,
CompiledLanguageRules, GraphNodeSpan, ImportedTypeBinding, LanguageRuleset,
ManualReceiverCallSpec, ManualReceiverSource, OptionalReceiverOwnerBinding,
ReceiverCallSiteKey, ReceiverOwnerBinding, collect_receiver_call_specs_in_callable,
declaration_name, enclosing_node_with_kind, javascript_binding_has_prior_write,
js_like_callable_source_name, js_ts_local_binding_visible_at_call,
js_ts_visible_local_type_name, member_call_method_col,
normalize_js_ts_private_receiver_surface, normalize_parameter_name,
normalized_receiver_variable, receiver_call_belongs_to_callable, receiver_callsite_key,
same_ts_span, trimmed_node_text, ts_node_graph_span, walk_tree_nodes,
Expand All @@ -59,6 +60,45 @@ pub(crate) const MEMBER_CALLSITE_MARKER: &str = "syntax:js-member-call";
/// Callsite marker for a bare call whose exact local name is also a runtime import binding.
pub(crate) const RUNTIME_IMPORT_CALLSITE_MARKER: &str = "syntax:js-runtime-import-call";

/// A named expression's private binding has no unscoped lookup authority.
pub(crate) const PRIVATE_NAME_CALLSITE_MARKER: &str = "syntax:js-private-name-call";

pub(crate) fn private_name_call_spans(tree: &Tree, source: &str) -> HashSet<GraphNodeSpan> {
let mut spans = HashSet::new();
walk_tree_nodes(tree.root_node(), &mut |call| {
if call.kind() != "call_expression" {
return;
}
let Some(target) = call
.child_by_field_name("function")
.filter(|target| target.kind() == "identifier")
else {
return;
};
let Some(name) = trimmed_node_text(target, source) else {
return;
};
let mut ancestor = call.parent();
while let Some(scope) = ancestor {
if matches!(scope.kind(), "function_expression" | "generator_function")
&& scope
.child_by_field_name("name")
.and_then(|binding| trimmed_node_text(binding, source))
.as_deref()
== Some(name.as_str())
{
// Nested callable bodies still close over this private name.
// Without a complete binding resolution, do not guess at a
// same-spelled outer declaration, parameter, or local shadow.
spans.insert(ts_node_graph_span(target));
break;
}
ancestor = scope.parent();
}
});
spans
}

const GRAPH_QUERY: &str = include_str!("../../rules/javascript.scm");

static RULES: OnceLock<Result<CompiledLanguageRules, String>> = OnceLock::new();
Expand Down
Loading
Loading