Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f1af774
feat(languages): hard-cut universal evidence for Swift Dart Scala Groovy
forhappy Aug 22, 2026
ccdecee
refactor(languages): split extended evidence by language
forhappy Aug 22, 2026
12fb8c8
Merge origin/main into codex/020-universal-language-wave
forhappy Aug 22, 2026
4690b80
refactor(languages): make language wave producers direct modules
forhappy Aug 22, 2026
f5785c9
feat(languages): restore language wave parity evidence
forhappy Aug 22, 2026
1c8ecb7
fix(graph): preserve Dart and Swift conformance edges
forhappy Aug 22, 2026
55563e1
Merge remote-tracking branch 'origin/main' into codex/020-universal-l…
forhappy Aug 22, 2026
4e12ca9
fix(graph): improve Groovy Scala and Dart evidence
forhappy Aug 22, 2026
bbbe10d
fix(graph): close language wave quality gaps
forhappy Aug 22, 2026
de41139
fix(groovy): bound source calls and audit declaration spans
forhappy Aug 23, 2026
a18e4f6
fix(dart): recover bounded local calls and constructions
forhappy Aug 23, 2026
c274032
fix(dart): recover implicit constructors and lexical call ownership
forhappy Aug 23, 2026
3e79b96
fix(languages): improve universal graph evidence for wave languages
forhappy Aug 23, 2026
be37dcb
fix(languages): avoid phantom groovy generic declarations
forhappy Aug 23, 2026
1784f68
fix(languages): recover qualified Swift extension identities
forhappy Aug 23, 2026
9fd50d8
fix(swift): preserve class extension owners in v1
forhappy Aug 23, 2026
fddbb29
test(languages): qualify Scala spans and Spock features
forhappy Aug 23, 2026
20cf959
fix(languages): recover nested Scala value bindings
forhappy Aug 23, 2026
61c29af
fix(languages): recover Swift parser-recovery declarations
forhappy Aug 23, 2026
3471678
fix(languages): strengthen Scala Dart and Groovy evidence
forhappy Aug 23, 2026
600376d
Merge origin/main into codex/020-universal-language-wave
forhappy Aug 23, 2026
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
103 changes: 103 additions & 0 deletions crates/compass-languages/src/evidence/dart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,30 @@ impl LanguageProfile for Dart {
}

fn declaration_kind_for_node(node: Node<'_>, _source: &[u8]) -> Option<&'static str> {
if node.kind() == "method_signature" {
return dart_signature_kind(node);
}
if matches!(
node.kind(),
"constructor_signature"
| "factory_constructor_signature"
| "getter_signature"
| "setter_signature"
| "function_signature"
) {
// These signatures are children of a method_signature wrapper in
// the Dart grammar. The wrapper owns the declaration range; keep
// the child from publishing a duplicate symbol. Top-level
// function_signature and declaration-wrapped constructors remain
// first-class evidence.
if node
.parent()
.is_some_and(|parent| parent.kind() == "method_signature")
{
return None;
}
return dart_signature_kind(node);
}
if node.kind() == "declaration"
&& node.parent().is_some_and(|parent| {
matches!(
Expand All @@ -43,6 +67,25 @@ impl LanguageProfile for Dart {
}

fn declaration_name_nodes_for_node(node: Node<'_>) -> Vec<Node<'_>> {
if node.kind() == "method_signature" {
return dart_signature_name(node).into_iter().collect();
}
if matches!(
node.kind(),
"constructor_signature"
| "factory_constructor_signature"
| "getter_signature"
| "setter_signature"
| "function_signature"
) {
if node
.parent()
.is_some_and(|parent| parent.kind() == "method_signature")
{
return Vec::new();
}
return dart_signature_name(node).into_iter().collect();
}
if node.kind() == "declaration" {
let mut cursor = node.walk();
if let Some(list) = node
Expand Down Expand Up @@ -137,6 +180,66 @@ impl LanguageProfile for Dart {
}
}

fn dart_signature_inner(node: Node<'_>) -> Option<Node<'_>> {
if node.kind() != "method_signature" {
return Some(node);
}
let mut cursor = node.walk();
node.named_children(&mut cursor).find(|child| {
matches!(
child.kind(),
"constructor_signature"
| "factory_constructor_signature"
| "getter_signature"
| "setter_signature"
| "function_signature"
)
})
}

fn dart_signature_kind(node: Node<'_>) -> Option<&'static str> {
let node = dart_signature_inner(node)?;
match node.kind() {
"constructor_signature" | "factory_constructor_signature" => Some("constructor"),
"getter_signature" | "setter_signature" => Some("property"),
"function_signature" => {
let mut current = node.parent();
while let Some(parent) = current {
if matches!(
parent.kind(),
"class_body" | "extension_body" | "mixin_body" | "enum_body"
) {
return Some("method");
}
current = parent.parent();
}
Some("function")
}
_ => None,
}
}

fn dart_signature_name(node: Node<'_>) -> Option<Node<'_>> {
let node = dart_signature_inner(node)?;
let mut identifiers = Vec::new();
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
if child.kind() == "identifier" {
identifiers.push(child);
}
}
if matches!(
node.kind(),
"constructor_signature" | "factory_constructor_signature"
) {
return identifiers
.get(1)
.copied()
.or_else(|| identifiers.first().copied());
}
identifiers.first().copied()
}

pub(super) fn emit_tree_evidence(
path: &Path,
source_file: &str,
Expand Down
94 changes: 91 additions & 3 deletions crates/compass-languages/src/evidence/groovy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ use std::path::Path;

use tree_sitter::Node;

use super::build::range_for_byte_span;
use super::model::{CandidateRelation, SemanticEvidenceBatch};
use super::shared::{self, LanguageProfile, State};
use super::shared::{self, LanguageProfile, ParsedImport, State};
use super::validate::EvidenceError;

struct Groovy;
Expand All @@ -17,6 +18,10 @@ impl LanguageProfile for Groovy {
shared::package_name_from_source(source)
}

fn parse_imports(statement: &str) -> Vec<ParsedImport> {
parse_groovy_import(statement)
}

fn has_source_supplement(declaration_count: usize) -> bool {
declaration_count <= 1
}
Expand All @@ -32,8 +37,13 @@ impl LanguageProfile for Groovy {
fn should_collect_source_supplement(source: &[u8], declaration_count: usize) -> bool {
Self::has_source_supplement(declaration_count)
|| std::str::from_utf8(source).is_ok_and(|text| {
text.lines()
.any(|line| groovy_spock_feature_declaration(line.trim()).is_some())
text.lines().any(|line| {
let line = line.trim();
line.starts_with("import ")
|| line.contains(" extends ")
|| line.contains(" implements ")
|| groovy_spock_feature_declaration(line).is_some()
})
})
}

Expand All @@ -60,6 +70,64 @@ pub(super) fn emit_tree_evidence(
shared::emit_tree_evidence::<Groovy>(path, source_file, source, root)
}

fn parse_groovy_import(statement: &str) -> Vec<ParsedImport> {
let trimmed = statement.trim().trim_end_matches(';').trim();
let Some(rest) = trimmed.strip_prefix("import") else {
return Vec::new();
};
let mut rest = rest.trim();
let _is_static = if let Some(value) = rest.strip_prefix("static") {
rest = value.trim();
true
} else {
false
};
let tokens = rest.split_whitespace().collect::<Vec<_>>();
let Some(target_token) = tokens.first() else {
return Vec::new();
};
let target = target_token.trim_matches(['\'', '"']).trim_end_matches(';');
if target.is_empty()
|| !target
.split('.')
.all(|part| part == "*" || shared::valid_name(part))
{
return Vec::new();
}
let alias = tokens
.windows(2)
.find(|pair| pair[0] == "as")
.map(|pair| pair[1].trim_matches(['\'', '"']).trim_end_matches(';'))
.filter(|value| shared::valid_name(value));
if target.ends_with(".*") {
let prefix = target.trim_end_matches(".*");
return vec![ParsedImport {
target: prefix.to_owned(),
binding_spelling: format!("{prefix}.*"),
local_spelling: alias.unwrap_or("*").to_owned(),
qualifier: Some(prefix.to_owned()),
alias: alias.is_some(),
prefix: true,
reexport: false,
}];
}
let Some(spelling) = alias
.map(str::to_owned)
.or_else(|| target.rsplit('.').next().map(str::to_owned))
else {
return Vec::new();
};
vec![ParsedImport {
target: target.to_owned(),
binding_spelling: spelling.clone(),
local_spelling: spelling,
qualifier: None,
alias: alias.is_some(),
prefix: false,
reexport: false,
}]
}

/// The pinned Groovy grammar intentionally exposes each top-level form as a
/// bounded `command` node. Keep Groovy on the universal evidence route by
/// extracting declaration/call spans from that command text rather than
Expand Down Expand Up @@ -98,6 +166,19 @@ fn collect_groovy_source<'source>(state: &mut State<'source, Groovy>) -> Result<
method = None;
}

if trimmed.starts_with("import ") {
let trim_offset = line_without_newline
.len()
.saturating_sub(line_without_newline.trim_start().len());
let range = range_for_byte_span(
state.source_file,
state.source,
line_start.saturating_add(trim_offset),
line_end,
);
state.emit_imports(parse_groovy_import(trimmed), range)?;
}

if let Some((kind, name, name_offset)) = groovy_type_declaration(trimmed) {
let parent = classes.last().map(|(index, _, _)| *index);
let parent_scope = parent
Expand Down Expand Up @@ -366,6 +447,13 @@ fn valid_groovy_type_name(value: &str) -> bool {
fn groovy_method_declaration(line: &str) -> Option<(String, bool, usize)> {
let open = line.find('(')?;
let before = line.get(..open)?.trim_end();
// A property initializer such as `List<String> items = empty()` contains
// parentheses but is not a method declaration. Treating the call as a
// declaration invents a method target and makes imported static calls
// ambiguous.
if before.contains('=') {
return None;
}
let name_end = before.len();
let name_start = before
.char_indices()
Expand Down
Loading
Loading