mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-18 22:02:49 +08:00
fix(kotlin): read a function's signature positionally (#1495)
tree-sitter-kotlin exposes no field names, so getSignature's getChildByField reads always missed and every Kotlin function and method was indexed without a signature. Find the parameter list and the return type by position, the way extractKotlinReturnType already does — in the wasm extractor and the kernel together.
This commit is contained in:
@@ -201,6 +201,8 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||||||
|
|
||||||
#### Symbols, tests and the viewer
|
#### 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.
|
- **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.
|
- **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.
|
||||||
|
|||||||
@@ -492,6 +492,39 @@ impl<'t> Walker<'t> {
|
|||||||
/// extractKotlinReturnType — positional: the first user_type/nullable_type
|
/// extractKotlinReturnType — positional: the first user_type/nullable_type
|
||||||
/// AFTER function_value_parameters; function_body/type_constraints first →
|
/// AFTER function_value_parameters; function_body/type_constraints first →
|
||||||
/// None; Unit/Nothing → None; `: T` generic params leak (preserve).
|
/// 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> {
|
fn return_type_of(&self, node: Node) -> Option<String> {
|
||||||
let mut seen_params = false;
|
let mut seen_params = false;
|
||||||
for i in 0..node.named_child_count() {
|
for i in 0..node.named_child_count() {
|
||||||
@@ -795,7 +828,7 @@ impl<'t> Walker<'t> {
|
|||||||
}
|
}
|
||||||
let extra = Extra {
|
let extra = Extra {
|
||||||
docstring: preceding_docstring(node, self.src),
|
docstring: preceding_docstring(node, self.src),
|
||||||
signature: None, // dead hook (zero fields)
|
signature: self.signature_of(node),
|
||||||
visibility: Some(self.visibility_of(node)),
|
visibility: Some(self.visibility_of(node)),
|
||||||
is_async: Some(self.is_async(node)),
|
is_async: Some(self.is_async(node)),
|
||||||
is_static: Some(false), // kotlin isStatic is always false
|
is_static: Some(false), // kotlin isStatic is always false
|
||||||
@@ -820,7 +853,7 @@ impl<'t> Walker<'t> {
|
|||||||
let qualified_override = receiver.as_ref().map(|r| format!("{r}::{name}"));
|
let qualified_override = receiver.as_ref().map(|r| format!("{r}::{name}"));
|
||||||
let extra = Extra {
|
let extra = Extra {
|
||||||
docstring: preceding_docstring(node, self.src),
|
docstring: preceding_docstring(node, self.src),
|
||||||
signature: None,
|
signature: self.signature_of(node),
|
||||||
visibility: Some(self.visibility_of(node)),
|
visibility: Some(self.visibility_of(node)),
|
||||||
is_async: Some(self.is_async(node)),
|
is_async: Some(self.is_async(node)),
|
||||||
is_static: Some(false),
|
is_static: Some(false),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Node as SyntaxNode } from 'web-tree-sitter';
|
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';
|
import type { LanguageExtractor } from '../tree-sitter-types';
|
||||||
|
|
||||||
/** Kotlin return types that can't be a chained-call receiver (no class to chain on). */
|
/** Kotlin return types that can't be a chained-call receiver (no class to chain on). */
|
||||||
@@ -275,9 +275,26 @@ export const kotlinExtractor: LanguageExtractor = {
|
|||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
getSignature: (node, source) => {
|
getSignature: (node, source) => {
|
||||||
// Kotlin function signature: fun name(params): ReturnType
|
// Kotlin function signature: fun name(params): ReturnType. tree-sitter-kotlin
|
||||||
const params = getChildByField(node, 'function_value_parameters');
|
// exposes no field names, so both parts are found positionally, the way
|
||||||
const returnType = getChildByField(node, 'type');
|
// 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;
|
if (!params) return undefined;
|
||||||
let sig = getNodeText(params, source);
|
let sig = getNodeText(params, source);
|
||||||
if (returnType) {
|
if (returnType) {
|
||||||
|
|||||||
Reference in New Issue
Block a user