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
38 changes: 27 additions & 11 deletions codegraph-kernel/src/resolve/js_scope_upstream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,33 @@ impl KernelResolver {
{
return None;
}
// The declarator walk below depends only on the candidate — the ref
// enters only through `visible(scope, point)`. Memoize the walk so a
// name with hundreds of same-file candidates pays the parent-chain
// climb once, not once per ref (minified bundles are the hot case).
// Keyed by ref language too: parsed_tree picks its parser from
// r.language, so the same candidate walks a different tree under
// typescript vs javascript refs (review on #566).
// Check BEFORE the file/tree loads: a memo hit needs no tree at all,
// and a missing or global scope needs no source either — the loads
// were the residual cost that kept the memo from helping (#566
// follow-up).
let memo_key = (n.id.clone(), r.language.clone());
if let Some(&hit) = self.js_decl_scope_memo.get(&memo_key) {
return match hit {
Some(None) => Some(true),
None => None,
Some(Some(scope)) => {
let source = self.read_file(&n.file_path)?;
let line = source.get((r.line - 1).max(0) as usize)?;
let point = Point::new(
(r.line - 1).max(0) as usize,
super::names::js_unit_to_byte(line, r.column.max(0) as usize),
);
Some(visible(Some(scope), point))
}
};
}
let source = self.read_file(&n.file_path)?;
let tree = self.js_binding_tree(&source, r)?;
let line = source.get((r.line - 1).max(0) as usize)?;
Expand All @@ -321,17 +348,6 @@ impl KernelResolver {
(n.start_line - 1).max(0) as usize,
super::names::js_unit_to_byte(line, n.start_column.max(0) as usize),
);
// The declarator walk below depends only on the candidate — the ref
// enters only through `visible(scope, point)`. Memoize the walk so a
// name with hundreds of same-file candidates pays the parent-chain
// climb once, not once per ref (minified bundles are the hot case).
// Keyed by ref language too: parsed_tree picks its parser from
// r.language, so the same candidate walks a different tree under
// typescript vs javascript refs (review on #566).
let memo_key = (n.id.clone(), r.language.clone());
if let Some(&hit) = self.js_decl_scope_memo.get(&memo_key) {
return hit.map(|scope| visible(scope, point));
}
let mut current = tree
.root_node()
.descendant_for_point_range(declaration, declaration);
Expand Down
4 changes: 4 additions & 0 deletions codegraph-kernel/src/resolve/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,9 @@ pub struct KernelResolver {
/// ref language) — parsed_tree selects its parser from the ref's
/// language, so the same candidate can walk a different tree.
js_decl_scope_memo: HashMap<(String, String), js_scope_upstream::JsDeclScope>,
/// is_lexically_reachable's `bare` regex, by reference_name — refs are
/// evaluated in per-ref loops, so one entry covers a whole loop.
bare_name_memo: Option<(String, bool)>,
language_type_scope_memo: HashMap<String, Rc<language_type_scope::LanguageTypeScope>>,
scala_package_object_membership_memo: HashMap<String, Option<(String, i64, i64)>>,
cpp_default_declaration_memo: HashMap<String, declarations_upstream::CppDefaultDeclarationIndex>,
Expand Down Expand Up @@ -983,6 +986,7 @@ impl KernelResolver {
manifest_external_memo: HashMap::new(),
lexical_scope_memo: HashMap::new(),
js_decl_scope_memo: HashMap::new(),
bare_name_memo: None,
language_type_scope_memo: HashMap::new(),
scala_package_object_membership_memo: HashMap::new(),
cpp_default_declaration_memo: HashMap::new(),
Expand Down
16 changes: 15 additions & 1 deletion codegraph-kernel/src/resolve/names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ fn objc_file_local_elsewhere(n: &KNode, r: &ResolveRefIn) -> bool {

fn prefer_adjacent_implementations(candidates: &mut Vec<Arc<KNode>>, r: &ResolveRefIn) {
if r.reference_kind != "calls" || !is_js_family(&r.language) { return; }
// Nothing to demote without a .d.ts candidate — skip the HashSet build
// and its two String allocations per candidate.
if !candidates.iter().any(|n| [".d.ts", ".d.mts", ".d.cts"].iter().any(|s| n.file_path.ends_with(s))) {
return;
}
let implementations: HashSet<_> = candidates.iter()
.filter(|n| !re!(r"\.d\.[cm]?ts$").is_match(&n.file_path))
.filter_map(|n| n.file_path.rsplit_once('.').map(|(stem, _)| (stem.to_string(), n.qualified_name.clone())))
Expand Down Expand Up @@ -137,7 +142,16 @@ impl KernelResolver {
/// (`case class B()` in a test method), or a method of such a type —
/// declared inside a function is reachable only from inside it.
pub(super) fn is_lexically_reachable(&mut self, candidate: &KNode, r: &ResolveRefIn) -> Res<bool> {
let bare = re!(r"^[A-Za-z_$][\w$]*$").is_match(&r.reference_name);
// Same reference_name for every candidate in a ref's loop — one-entry
// memo instead of the thread-local re! per candidate.
let bare = match &self.bare_name_memo {
Some((name, b)) if name == &r.reference_name => *b,
_ => {
let b = re!(r"^[A-Za-z_$][\w$]*$").is_match(&r.reference_name);
self.bare_name_memo = Some((r.reference_name.clone(), b));
b
}
};
if candidate.kind == "function" && bare && !self.object_member_reachable_by_name(candidate,r)? { return Ok(false); }
if bare && js_objects_upstream::path_holder(candidate) && js_objects_upstream::literal_owner(candidate) {
let last = js_objects_upstream::last_segment(candidate);
Expand Down
Loading