[pitboss/grind] deferred session-0002 (20260516T052512Z-20f8)

This commit is contained in:
pitboss 2026-05-16 01:46:35 -05:00
parent 7a2f82c2ab
commit 282acddbbf
11 changed files with 214 additions and 45 deletions

View file

@ -3958,3 +3958,42 @@ fn rhs_array_literal_elements_recognise_per_language_shapes() {
// Non-array-shape node returns empty (defensive guard).
assert!(run("javascript", b"const x = tainted;\n", &["identifier"]).is_empty());
}
/// `CalleeSite.span` should carry the 1-based (line, col) of each call's
/// node span so downstream consumers (surface map, datastore/external
/// detectors) can render real coordinates instead of `line: 0`.
#[test]
fn callee_site_span_carries_line_and_column() {
// Three calls on three different lines. The leading newline puts
// line 1 at the blank line; `helper(x, y);` is on line 3, etc.
let src = b"
function outer(obj, x, y) {
helper(x, y);
obj.method(x);
}
";
let ts_lang = Language::from(tree_sitter_javascript::LANGUAGE);
let file_cfg = parse_to_file_cfg(src, "javascript", ts_lang);
let (_key, outer) = file_cfg
.summaries
.iter()
.find(|(k, _)| k.name == "outer")
.expect("outer summary should exist");
let helper_site = outer
.callees
.iter()
.find(|c| c.name == "helper")
.expect("helper call should be recorded");
let (line, col) = helper_site.span.expect("span populated at CFG-build time");
assert_eq!(line, 3, "helper(...) sits on the 3rd source line");
assert!(col >= 5, "indented 4 spaces — column is 1-based and > 4");
let method_site = outer
.callees
.iter()
.find(|c| c.name.ends_with("method"))
.expect("method call should be recorded");
let (mline, _) = method_site.span.expect("method span populated");
assert_eq!(mline, 4, "obj.method(x) on line 4");
}

View file

@ -5664,7 +5664,7 @@ pub(super) fn build_sub<'a>(
for idx in fn_graph.node_indices() {
let info = &fn_graph[idx];
if let Some(callee) = &info.call.callee {
let site = build_callee_site(callee, info, lang);
let site = build_callee_site(callee, info, lang, code);
// Dedup by (name, arity, receiver, qualifier, ordinal). A
// single function may legitimately contain multiple distinct
// calls to the same callee (e.g. different ordinals or
@ -6632,7 +6632,12 @@ fn apply_gated_label_rules(
/// remains the single segment immediately before the leaf (back-compat
/// with the legacy heuristic). For method calls the qualifier is
/// redundant with `receiver` and is left `None`.
fn build_callee_site(callee: &str, info: &NodeInfo, lang: &str) -> crate::summary::CalleeSite {
fn build_callee_site(
callee: &str,
info: &NodeInfo,
lang: &str,
code: &[u8],
) -> crate::summary::CalleeSite {
use crate::summary::CalleeSite;
let receiver = info.call.receiver.clone();
@ -6661,15 +6666,39 @@ fn build_callee_site(callee: &str, info: &NodeInfo, lang: &str) -> crate::summar
None
};
let span = callee_span_line_col(code, info.ast.span.0);
CalleeSite {
name: callee.to_string(),
arity,
receiver,
qualifier,
ordinal: info.call.call_ordinal,
span,
}
}
/// Convert a byte offset into a 1-based `(line, col)` pair against `code`.
///
/// Returns `None` only when `code` is empty (no source to resolve against);
/// out-of-range offsets are clamped to `code.len()` so a synthetic node
/// whose span overshoots the file still produces the last-line coordinate
/// rather than `None`.
fn callee_span_line_col(code: &[u8], offset: usize) -> Option<(u32, u32)> {
if code.is_empty() {
return None;
}
let clamped = offset.min(code.len());
let prefix = &code[..clamped];
let line = prefix.iter().filter(|&&b| b == b'\n').count() as u32 + 1;
let col_bytes = match prefix.iter().rposition(|&b| b == b'\n') {
Some(idx) => clamped - idx - 1,
None => clamped,
} as u32
+ 1;
Some((line, col_bytes))
}
/// Convert the graphlocal `FuncSummaries` into serialisable [`FuncSummary`]
/// values suitable for crossfile persistence.
pub(crate) fn export_summaries(