Skip to content
Open
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

#### Symbols, tests and the viewer

- Kotlin functions and methods now carry their signature — `(params): ReturnType` — in `codegraph_explore`, `node` and the viewer, instead of no signature at all. Re-index Kotlin projects after upgrading. (#1495)

- **Files under an `e2e/` directory count as tests.** Their calls no longer appear as production callers in Steps, dead-code and test badges.

- **Production code under a `samples` or `examples` package path is no longer treated as test code.** A Kotlin or Java project whose package path runs through `com/google/samples/…` (Now in Android, for one) had nearly every file counted as a fixture, so the Map opened on `build-logic`, the entry points hid the app, and dead-code and test badges were wrong. Only the project layout above a `src/` folder decides now; the package path below it never does.
Expand Down
37 changes: 35 additions & 2 deletions codegraph-kernel/src/kotlin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,39 @@ impl<'t> Walker<'t> {
/// extractKotlinReturnType — positional: the first user_type/nullable_type
/// AFTER function_value_parameters; function_body/type_constraints first →
/// None; Unit/Nothing → None; `: T` generic params leak (preserve).
/// `(params): ReturnType` — the positional read TreeSitterExtractor's
/// kotlin getSignature does (#1495): the `function_value_parameters` child,
/// then the type node that follows it before the body. Verbatim source text,
/// so it round-trips through parity byte-for-byte.
fn signature_of(&self, node: Node) -> Option<String> {
let mut params: Option<Node> = None;
let mut return_type: Option<Node> = None;
for i in 0..node.named_child_count() {
let Some(child) = node.named_child(i) else { continue };
if child.kind() == "function_value_parameters" {
params = Some(child);
continue;
}
if params.is_none() {
continue;
}
if matches!(child.kind(), "function_body" | "type_constraints") {
break;
}
if matches!(child.kind(), "user_type" | "nullable_type" | "function_type") {
return_type = Some(child);
break;
}
}
let params = params?;
let mut sig = self.text(params).to_string();
if let Some(rt) = return_type {
sig.push_str(": ");
sig.push_str(self.text(rt));
}
Some(sig)
}

fn return_type_of(&self, node: Node) -> Option<String> {
let mut seen_params = false;
for i in 0..node.named_child_count() {
Expand Down Expand Up @@ -795,7 +828,7 @@ impl<'t> Walker<'t> {
}
let extra = Extra {
docstring: preceding_docstring(node, self.src),
signature: None, // dead hook (zero fields)
signature: self.signature_of(node),
visibility: Some(self.visibility_of(node)),
is_async: Some(self.is_async(node)),
is_static: Some(false), // kotlin isStatic is always false
Expand All @@ -820,7 +853,7 @@ impl<'t> Walker<'t> {
let qualified_override = receiver.as_ref().map(|r| format!("{r}::{name}"));
let extra = Extra {
docstring: preceding_docstring(node, self.src),
signature: None,
signature: self.signature_of(node),
visibility: Some(self.visibility_of(node)),
is_async: Some(self.is_async(node)),
is_static: Some(false),
Expand Down
25 changes: 21 additions & 4 deletions src/extraction/languages/kotlin.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Node as SyntaxNode } from 'web-tree-sitter';
import { getNodeText, getChildByField } from '../tree-sitter-helpers';
import { getNodeText } from '../tree-sitter-helpers';
import type { LanguageExtractor } from '../tree-sitter-types';

/** Kotlin return types that can't be a chained-call receiver (no class to chain on). */
Expand Down Expand Up @@ -275,9 +275,26 @@ export const kotlinExtractor: LanguageExtractor = {
return undefined;
},
getSignature: (node, source) => {
// Kotlin function signature: fun name(params): ReturnType
const params = getChildByField(node, 'function_value_parameters');
const returnType = getChildByField(node, 'type');
// Kotlin function signature: fun name(params): ReturnType. tree-sitter-kotlin
// exposes no field names, so both parts are found positionally, the way
// extractKotlinReturnType does (#1495): the `function_value_parameters`
// child, then the type node that follows it before the body.
let params: SyntaxNode | null = null;
let returnType: SyntaxNode | null = null;
for (let i = 0; i < node.namedChildCount; i++) {
const child = node.namedChild(i);
if (!child) continue;
if (child.type === 'function_value_parameters') {
params = child;
continue;
}
if (!params) continue;
if (child.type === 'function_body' || child.type === 'type_constraints') break;
if (child.type === 'user_type' || child.type === 'nullable_type' || child.type === 'function_type') {
returnType = child;
break;
}
}
if (!params) return undefined;
let sig = getNodeText(params, source);
if (returnType) {
Expand Down