Skip to content

Commit 773de1b

Browse files
authored
Merge pull request #21 from Eilodon/claude/ci-action-failures-1angzz
Claude/ci action failures 1angzz
2 parents d09cda1 + 9de1ffd commit 773de1b

10 files changed

Lines changed: 290 additions & 24 deletions

File tree

Cargo.lock

Lines changed: 13 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ tree-sitter-stack-graphs-python = "0.3"
5151
# =0.23.2 pin (tree-sitter-typescript) comes from this crate's own Cargo.toml —
5252
# already the exact version tree-sitter-typescript resolves to workspace-wide.
5353
tree-sitter-stack-graphs-typescript = "0.4"
54+
tree-sitter-stack-graphs-java = "0.5"
5455
serde = { version = "1", features = ["derive"] }
5556
serde_json = "1"
5657
toml = "0.8"

crates/calm-core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ stack-graphs = { workspace = true }
2727
tree-sitter-stack-graphs = { workspace = true }
2828
tree-sitter-stack-graphs-python = { workspace = true }
2929
tree-sitter-stack-graphs-typescript = { workspace = true }
30+
tree-sitter-stack-graphs-java = { workspace = true }
3031
model2vec-rs = { version = "0.2.1", optional = true }
3132
tree-sitter-c = { workspace = true, optional = true }
3233
tree-sitter-cpp = { workspace = true, optional = true }

crates/calm-core/src/analysis/boundaries.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,13 @@ impl PathMatcher {
4343
/// unaffected and a typo'd glob degrades to its literal prefix rather
4444
/// than silently matching nothing.
4545
fn new(pattern: &str) -> Self {
46-
if pattern.contains(['*', '?', '[']) {
47-
if let Ok(glob) = globset::Glob::new(pattern) {
48-
return PathMatcher::Glob(glob.compile_matcher());
49-
}
46+
if pattern.contains(['*', '?', '['])
47+
&& let Ok(glob) = globset::Glob::new(pattern)
48+
{
49+
return PathMatcher::Glob(glob.compile_matcher());
5050
}
5151
PathMatcher::Prefix(pattern.to_string())
5252
}
53-
5453
fn matches(&self, path: &str) -> bool {
5554
match self {
5655
PathMatcher::Prefix(prefix) => path.starts_with(prefix.as_str()),
@@ -221,7 +220,11 @@ mod tests {
221220
#[test]
222221
fn test_glob_rule_matches_nested_paths() {
223222
let conn = test_conn();
224-
insert_import(&conn, "crates/calm-core/src/indexer/foo.rs", "crates/calm-server/src/tools/orient.rs");
223+
insert_import(
224+
&conn,
225+
"crates/calm-core/src/indexer/foo.rs",
226+
"crates/calm-server/src/tools/orient.rs",
227+
);
225228
let rules = vec![BoundaryRule {
226229
from: "crates/*/src/indexer/**".into(),
227230
to: "crates/*/src/tools/**".into(),
@@ -234,7 +237,11 @@ mod tests {
234237
#[test]
235238
fn test_glob_rule_does_not_match_unrelated_path() {
236239
let conn = test_conn();
237-
insert_import(&conn, "crates/calm-core/src/other/foo.rs", "crates/calm-server/src/tools/orient.rs");
240+
insert_import(
241+
&conn,
242+
"crates/calm-core/src/other/foo.rs",
243+
"crates/calm-server/src/tools/orient.rs",
244+
);
238245
let rules = vec![BoundaryRule {
239246
from: "crates/*/src/indexer/**".into(),
240247
to: "crates/*/src/tools/**".into(),

crates/calm-core/src/db/schema.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,10 @@ mod tests {
666666
|r| r.get(0),
667667
)
668668
.unwrap();
669-
assert_eq!(deleted_count, 0, "deleted note must be removed from the FTS index");
669+
assert_eq!(
670+
deleted_count, 0,
671+
"deleted note must be removed from the FTS index"
672+
);
670673
}
671674

672675
#[test]
@@ -709,7 +712,10 @@ mod tests {
709712
|r| r.get(0),
710713
)
711714
.unwrap();
712-
assert_eq!(count, 1, "pre-existing note must be backfilled by 'rebuild'");
715+
assert_eq!(
716+
count, 1,
717+
"pre-existing note must be backfilled by 'rebuild'"
718+
);
713719

714720
// Triggers sync notes inserted after the migration too.
715721
conn.execute(

crates/calm-core/src/indexer/pipeline.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,6 +1060,7 @@ pub fn run_indexing_pipeline(
10601060
let mut formal = crate::resolver::formal::FormalResolver::new();
10611061
let _ = formal.load_python(); // non-fatal: falls back silently on error
10621062
let _ = formal.load_typescript(); // non-fatal: falls back silently on error
1063+
let _ = formal.load_java(); // non-fatal: falls back silently on error
10631064

10641065
let mut files = Vec::new();
10651066
collect_source_files(project_root, &ignore_patterns, &mut files);
@@ -1151,6 +1152,7 @@ pub fn reindex_changed(
11511152
let mut formal = crate::resolver::formal::FormalResolver::new();
11521153
let _ = formal.load_python();
11531154
let _ = formal.load_typescript();
1155+
let _ = formal.load_java();
11541156

11551157
let existing: HashMap<String, String> = {
11561158
let mut stmt = conn.prepare("SELECT path, hash FROM file_index")?;

crates/calm-core/src/resolver/formal.rs

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,98 @@ fn build_python_builtins_graph(sgl: &StackGraphLanguage) -> anyhow::Result<Stack
173173
Ok(graph)
174174
}
175175

176+
/// Stub source for a virtual, unpackaged Java compilation unit, standing in
177+
/// for `tree-sitter-stack-graphs-java` 0.5.0's own bundled `src/builtins.java`
178+
/// (which ships empty, like Python's did — see `build_python_builtins_graph`).
179+
/// Unlike Python, no `<builtins>`/`FILE_PATH` naming trick is needed: the
180+
/// Java TSG's own `(program (package_declaration)? @package) @prog` rule
181+
/// wires `ROOT_NODE -> @prog.defs` directly whenever `@package` is absent, so
182+
/// an unpackaged file's top-level declarations become reachable from any
183+
/// other file's plain-identifier reference (which always chains up through
184+
/// `lexical_scope` to `ROOT_NODE`, regardless of that other file's own
185+
/// package). Covers the bulk of `java.lang` (auto-imported in every `.java`
186+
/// file, no `import` needed) — not exhaustive, but the names most likely to
187+
/// show up in real code. Bodies are empty (`{}`) — only the *names* need to
188+
/// exist as top-level definitions for references to resolve to.
189+
const JAVA_BUILTINS_STUB: &str = r#"
190+
class Object {}
191+
class String {}
192+
class StringBuilder {}
193+
class StringBuffer {}
194+
class Boolean {}
195+
class Byte {}
196+
class Short {}
197+
class Character {}
198+
class Integer {}
199+
class Long {}
200+
class Float {}
201+
class Double {}
202+
class Number {}
203+
class Math {}
204+
class System {}
205+
class Class {}
206+
class Void {}
207+
class Thread {}
208+
class ThreadLocal {}
209+
class Runtime {}
210+
class Enum {}
211+
class Record {}
212+
class Throwable {}
213+
class Exception {}
214+
class RuntimeException {}
215+
class Error {}
216+
class NullPointerException {}
217+
class IllegalArgumentException {}
218+
class IllegalStateException {}
219+
class IndexOutOfBoundsException {}
220+
class ArrayIndexOutOfBoundsException {}
221+
class StringIndexOutOfBoundsException {}
222+
class ClassCastException {}
223+
class NumberFormatException {}
224+
class UnsupportedOperationException {}
225+
class ArithmeticException {}
226+
class CloneNotSupportedException {}
227+
class InterruptedException {}
228+
class SecurityException {}
229+
class OutOfMemoryError {}
230+
class StackOverflowError {}
231+
class AssertionError {}
232+
interface Runnable {}
233+
interface Comparable {}
234+
interface CharSequence {}
235+
interface Iterable {}
236+
interface AutoCloseable {}
237+
interface Cloneable {}
238+
interface Appendable {}
239+
@interface Override {}
240+
@interface Deprecated {}
241+
@interface SuppressWarnings {}
242+
@interface FunctionalInterface {}
243+
@interface SafeVarargs {}
244+
"#;
245+
246+
/// Builds a `StackGraph` holding definitions for `JAVA_BUILTINS_STUB`, reusing
247+
/// the same compiled TSG rules (`sgl`) the upstream crate uses for ordinary
248+
/// files. The virtual file has no package declaration, so its top-level
249+
/// classes/interfaces/annotations attach directly to `ROOT_NODE` — see
250+
/// `JAVA_BUILTINS_STUB`'s doc comment for why that alone makes them visible
251+
/// from any other file's unqualified references.
252+
fn build_java_builtins_graph(sgl: &StackGraphLanguage) -> anyhow::Result<StackGraph> {
253+
let mut graph = StackGraph::new();
254+
let file = graph.get_or_create_file("<builtins>.java");
255+
256+
let mut globals = Variables::new();
257+
globals
258+
.add("FILE_PATH".into(), "<builtins>.java".into())
259+
.map_err(|_| anyhow::anyhow!("Failed to set FILE_PATH global for builtins"))?;
260+
261+
let deadline = TsgCancelAfterDuration::new(RESOLVE_TIMEOUT);
262+
sgl.build_stack_graph_into(&mut graph, file, JAVA_BUILTINS_STUB, &globals, &deadline)
263+
.map_err(|e| anyhow::anyhow!("Failed to build Java builtins stack graph: {e:?}"))?;
264+
265+
Ok(graph)
266+
}
267+
176268
/// Same as `ForwardPartialPathStitcher::find_minimal_partial_path_set_in_file`
177269
/// (stack-graphs 0.14), but with `max_work_per_phase` bounded — see
178270
/// `MAX_WORK_PER_PHASE` for why.
@@ -315,6 +407,30 @@ impl FormalResolver {
315407
Ok(())
316408
}
317409

410+
/// Java formal resolution. Unlike Python (`load_python`), no dialect
411+
/// variant is needed — Java has no `.tsx`-style split, matching
412+
/// TypeScript's own primary (non-tsx) shape. Like Python, upstream's
413+
/// bundled builtins source (`tree-sitter-stack-graphs-java`'s
414+
/// `src/builtins.java`) ships empty, so `lc.builtins` is replaced with
415+
/// `build_java_builtins_graph`'s output — see that function's doc comment
416+
/// for why no `<builtins>` path/FILE_PATH trick is needed here, unlike
417+
/// Python.
418+
pub fn load_java(&mut self) -> anyhow::Result<()> {
419+
let lc = tree_sitter_stack_graphs_java::try_language_configuration(cancellation_flag())
420+
.map_err(|e| anyhow::anyhow!("Failed to load Java stack-graphs config: {e}"))?;
421+
let builtins = build_java_builtins_graph(&lc.sgl)?;
422+
self.configs.insert(
423+
"java".to_string(),
424+
FormalLanguageConfig {
425+
sgl: lc.sgl,
426+
builtins,
427+
no_similar_paths_in_file: lc.no_similar_paths_in_file,
428+
tsx: None,
429+
},
430+
);
431+
Ok(())
432+
}
433+
318434
pub fn resolve_file(
319435
&self,
320436
language: &str,
@@ -780,4 +896,115 @@ function Bar() {
780896
"a genuinely undefined name must not resolve. Edges: {edges:?}"
781897
);
782898
}
899+
900+
#[test]
901+
fn test_load_java() {
902+
let mut resolver = FormalResolver::new();
903+
resolver.load_java().unwrap();
904+
assert!(resolver.has_language("java"));
905+
}
906+
907+
#[test]
908+
fn test_resolve_simple_java_def_ref() {
909+
let mut resolver = FormalResolver::new();
910+
resolver.load_java().unwrap();
911+
let source = r#"
912+
class Foo {
913+
static void greet() {}
914+
915+
static void run() {
916+
greet();
917+
}
918+
}
919+
"#;
920+
let edges = resolver.resolve_file("java", "Foo.java", source).unwrap();
921+
let has_greet_edge = edges
922+
.iter()
923+
.any(|e| e.definition_symbol == "greet" || e.reference_symbol == "greet");
924+
assert!(
925+
has_greet_edge,
926+
"Should resolve greet() call to greet definition. Edges: {edges:?}"
927+
);
928+
}
929+
930+
#[test]
931+
fn test_resolve_java_class() {
932+
let mut resolver = FormalResolver::new();
933+
resolver.load_java().unwrap();
934+
let source = r#"
935+
class MyClass {
936+
void method() {}
937+
}
938+
939+
class UseClass {
940+
void useClass() {
941+
MyClass obj = new MyClass();
942+
}
943+
}
944+
"#;
945+
let edges = resolver
946+
.resolve_file("java", "MyClass.java", source)
947+
.unwrap();
948+
let has_class_edge = edges
949+
.iter()
950+
.any(|e| e.definition_symbol == "MyClass" || e.reference_symbol == "MyClass");
951+
assert!(
952+
has_class_edge,
953+
"Should resolve MyClass reference to class definition. Edges: {edges:?}"
954+
);
955+
}
956+
957+
/// Mirrors `test_resolve_file_resolves_python_builtins`: `System` and
958+
/// `Object` (both `java.lang`, implicitly visible with no `import`) must
959+
/// resolve through the formal tier via `JAVA_BUILTINS_STUB`, not just
960+
/// merge into the graph without crashing.
961+
#[test]
962+
fn test_resolve_file_resolves_java_builtins() {
963+
let mut resolver = FormalResolver::new();
964+
resolver.load_java().unwrap();
965+
966+
let edges = resolver
967+
.resolve_file(
968+
"java",
969+
"Foo.java",
970+
"class Foo {\n void bar() {\n Object o = new Object();\n System.out.println(o);\n }\n}\n",
971+
)
972+
.unwrap();
973+
974+
assert!(
975+
edges
976+
.iter()
977+
.any(|e| e.reference_symbol == "Object" && e.definition_symbol == "Object"),
978+
"Object must resolve through the formal tier. Edges: {edges:?}"
979+
);
980+
assert!(
981+
edges
982+
.iter()
983+
.any(|e| e.reference_symbol == "System" && e.definition_symbol == "System"),
984+
"System must resolve through the formal tier. Edges: {edges:?}"
985+
);
986+
}
987+
988+
/// A genuinely undefined name must still fail to resolve — the builtins
989+
/// fix must not make FormalResolver resolve *everything*.
990+
#[test]
991+
fn test_resolve_java_does_not_resolve_undefined_name() {
992+
let mut resolver = FormalResolver::new();
993+
resolver.load_java().unwrap();
994+
995+
let edges = resolver
996+
.resolve_file(
997+
"java",
998+
"Foo.java",
999+
"class Foo {\n void bar() {\n totallyUndefinedXyz();\n }\n}\n",
1000+
)
1001+
.unwrap();
1002+
1003+
assert!(
1004+
!edges
1005+
.iter()
1006+
.any(|e| e.reference_symbol == "totallyUndefinedXyz"),
1007+
"a genuinely undefined name must not resolve. Edges: {edges:?}"
1008+
);
1009+
}
7831010
}

0 commit comments

Comments
 (0)