瀏覽代碼

feat(impact): Java annotation coverage — index @interface defs, link @Foo usages

Java was otherwise healthy (FQN imports, new/extends/implements all resolve); the
systematic gap was annotations, and both halves were missing:

- Annotation USAGES (`@Foo` on a class/method/field) live inside the
  declaration's `modifiers` node, which `extractDecoratorsFor` never descended
  into — so every annotation usage was dropped. Now it descends into `modifiers`
  (also fixes C#/Kotlin annotations, which use the same shape).
- Annotation DEFINITIONS (`@interface Foo` = `annotation_type_declaration`)
  weren't in Java's interfaceTypes, so they weren't nodes and the usages had
  nothing to resolve to.

Now `@SerializedName` / `@GetMapping` / `@Entity` style annotations link to their
definition (a `decorates` edge), so the annotation file shows its real users and
the annotated class depends on it. gson 78%->85%, retrofit 80.5%. New test +
CHANGELOG; full suite green (1150).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Colby McHenry 2 周之前
父節點
當前提交
badb124
共有 4 個文件被更改,包括 38 次插入2 次删除
  1. 1 0
      CHANGELOG.md
  2. 21 0
      __tests__/extraction.test.ts
  3. 5 1
      src/extraction/languages/java.ts
  4. 11 1
      src/extraction/tree-sitter.ts

+ 1 - 0
CHANGELOG.md

@@ -19,6 +19,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 - Blast radius, callers, and `codegraph affected` now recognize far more of the dependencies that were already in your code. A symbol now counts as a dependency whether it's called, used only in a type annotation inside a function body (`const items: Foo[] = []`), imported and placed in a registry array or passed as an argument, used as a JSX component, simply re-exported from a barrel (`export { X } from './x'`), or pulled in as a namespace (`import * as ns from '@/x'`) — including through tsconfig path aliases like `@/`. Previously only called, instantiated, or signature-typed symbols created a cross-file link, so a file that used a dependency in any other way could look like it depended on nothing — and the file that defined a widely-used symbol could look like nothing depended on it. The graph still indexes exactly the same symbols; it just connects the ones that were already there. (TypeScript/JavaScript)
 - The same completeness fix now applies to **Python**: a name brought in with `from module import X` is recorded as a dependency on that module even when `X` is only stored in a list/dict, passed as an argument, used as a decorator, or re-exported through an `__init__.py`. Previously Python linked only imports that were called or instantiated, so a module consumed purely by value — or only re-exported — looked like nothing depended on it.
 - Rust impact and `codegraph affected` now connect far more of the module graph. Struct literals (`Widget { n: 1 }`) are recorded as instantiations; a `use` / `pub use` brings its item into the dependency graph — so a `pub use` re-export hub (a `mod.rs` re-exporting its submodules) depends on the modules it re-exports — resolved by Rust module path (`crate::`/`self::`/`super::`), so a re-export of a common name like `read` links to the right module instead of a same-named symbol elsewhere; and trait dispatch reaches implementations — a struct whose methods cover a trait's is treated as implementing it, and a call through `&dyn Trait` resolves to the concrete method. Previously a Rust type linked only when called or used in a type position, so structs built by literal, modules surfaced only through `pub use`, and trait-only implementations looked like they had no dependents. (#584 for Rust traits)
+- Java annotations are now connected. Annotation definitions (`@interface Foo`) are indexed as types, and every `@Foo` usage on a class, method, or field is recorded as a dependency on it. Previously neither side was captured — annotation usages were dropped (they live inside the declaration's modifiers) and `@interface` types weren't indexed at all — so annotation-driven code (Spring `@GetMapping`, JPA `@Entity`, Gson `@SerializedName`, …) showed the annotation as having no users and the annotated class as not depending on it.
 - C# `record` types are now indexed. `record`, `record class`, and `record struct` declarations (everywhere in modern C# — DTOs, value objects, CQRS messages, MediatR notifications) were previously skipped entirely, so every reference, generic type argument (`IEnumerable<MyRecord>`), and `new MyRecord(...)` pointed at nothing and the file defining a record looked like it had no callers or dependents. (#237)
 - Go interfaces now connect to their implementations. Go has no `implements` keyword — a type satisfies an interface just by having the right methods — so CodeGraph now infers that link: a struct whose methods cover an interface's method set is treated as implementing it, and a call through the interface (`API.Marshal(...)`) reaches every concrete implementation. This means a type used only via an interface (the common plugin/strategy pattern — e.g. JSON-codec or renderer implementations selected at runtime) is no longer reported as having no callers or no dependents, and impact now flows from an interface method to the implementations behind it. (#584)
 - Go now records cross-package struct creation. A composite literal like `render.XML{...}` or `pkga.Widget{...}` — including ones registered in a package-level `var registry = map[string]R{...}` — now links to the package that defines the type. Cross-package function calls and type references already resolved; this closes struct instantiation, so a package whose types are only *constructed* elsewhere (a common pattern for interface implementations) is no longer reported as having no dependents. Go type conversions such as `(*Wrapped)(x)` now link to the converted-to type as well.

+ 21 - 0
__tests__/extraction.test.ts

@@ -4859,3 +4859,24 @@ describe('Rust cross-module recall', () => {
     } finally { cleanupTempDir(dir); }
   });
 });
+
+describe('Java annotations (blast-radius recall)', () => {
+  it('indexes @interface definitions and links @Annotation usages to them', async () => {
+    const dir = createTempDir();
+    try {
+      fs.mkdirSync(path.join(dir, 'p'), { recursive: true });
+      // The annotation DEFINITION must be a node, and the @MyAnno usages (which
+      // live inside a `modifiers` node on the class/field/method) must extract.
+      fs.writeFileSync(path.join(dir, 'p', 'MyAnno.java'), `package p;\npublic @interface MyAnno { String value() default ""; }\n`);
+      fs.writeFileSync(
+        path.join(dir, 'p', 'User.java'),
+        `package p;\n@MyAnno("c")\npublic class User {\n  @MyAnno("f") int field;\n  @MyAnno("m") void go() {}\n}\n`
+      );
+      const cg = CodeGraph.initSync(dir, { config: { include: ['**/*.java'], exclude: [] } });
+      await cg.indexAll();
+      cg.resolveReferences();
+      expect(cg.getFileDependents('p/MyAnno.java')).toContain('p/User.java');
+      cg.destroy();
+    } finally { cleanupTempDir(dir); }
+  });
+});

+ 5 - 1
src/extraction/languages/java.ts

@@ -6,7 +6,11 @@ export const javaExtractor: LanguageExtractor = {
   functionTypes: [],
   classTypes: ['class_declaration'],
   methodTypes: ['method_declaration', 'constructor_declaration'],
-  interfaceTypes: ['interface_declaration'],
+  // `annotation_type_declaration` is `@interface Foo { … }` — an annotation
+  // definition. Without it, annotation types (`@SerializedName`, `@GetMapping`,
+  // JPA/Spring annotations) aren't nodes, so the `@Foo` usages that DO get
+  // extracted can't resolve and the annotation file shows zero dependents.
+  interfaceTypes: ['interface_declaration', 'annotation_type_declaration'],
   structTypes: [],
   enumTypes: ['enum_declaration'],
   enumMemberTypes: ['enum_constant'],

+ 11 - 1
src/extraction/tree-sitter.ts

@@ -2363,7 +2363,17 @@ export class TreeSitterExtractor {
     // 1. Decorators that are direct children of the declaration
     //    (method/property style, also some grammars for class).
     for (let i = 0; i < declNode.namedChildCount; i++) {
-      consider(declNode.namedChild(i));
+      const child = declNode.namedChild(i);
+      consider(child);
+      // Java/Kotlin/C# put annotations INSIDE a `modifiers` node
+      // (`@MyAnno public class X` → class_declaration → modifiers → annotation),
+      // so descend into it — otherwise every annotation usage is silently
+      // dropped and annotation types show zero dependents.
+      if (child && child.type === 'modifiers') {
+        for (let j = 0; j < child.namedChildCount; j++) {
+          consider(child.namedChild(j));
+        }
+      }
     }
 
     // 2. Decorators that are PRECEDING siblings of the declaration