mirror of
https://github.com/giancarloerra/socraticode.git
synced 2026-06-02 06:23:43 +02:00
Merge pull request #40 from a5345534/fix/jvm-symbol-extraction
fix: extract JVM symbol names from declarations
This commit is contained in:
@@ -470,10 +470,8 @@ function extractFromJvm(
|
||||
: ["class_declaration", "interface_declaration", "enum_declaration", "object_declaration"];
|
||||
for (const k of classKinds) {
|
||||
for (const cls of safeFindAll(root, k)) {
|
||||
const nameNode = cls.find({ rule: { kind: "type_identifier" } })
|
||||
?? cls.find({ rule: { kind: "identifier" } });
|
||||
if (!nameNode) continue;
|
||||
const name = nameNode.text();
|
||||
const name = extractJvmTypeName(cls.text(), langKey);
|
||||
if (!name) continue;
|
||||
const r = cls.range();
|
||||
const startLine = r.start.line + 1;
|
||||
const endLine = r.end.line + 1;
|
||||
@@ -496,10 +494,8 @@ function extractFromJvm(
|
||||
: ["method_declaration", "constructor_declaration"];
|
||||
for (const k of methodKinds) {
|
||||
for (const m of safeFindAll(root, k)) {
|
||||
const nameNode = m.find({ rule: { kind: "identifier" } })
|
||||
?? m.find({ rule: { kind: "simple_identifier" } });
|
||||
if (!nameNode) continue;
|
||||
const name = nameNode.text();
|
||||
const name = extractJvmCallableName(m.text());
|
||||
if (!name) continue;
|
||||
const r = m.range();
|
||||
const startLine = r.start.line + 1;
|
||||
const endLine = r.end.line + 1;
|
||||
@@ -533,6 +529,38 @@ function extractFromJvm(
|
||||
return { symbols, rawCalls };
|
||||
}
|
||||
|
||||
function stripJvmAnnotations(text: string): string {
|
||||
return text
|
||||
.split("\n")
|
||||
.map((line) =>
|
||||
line.replace(/^\s*(?:@(?:[\w$]+:)?[\w$.]+(?:\([^)]*\))?\s*)+/, "")
|
||||
)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
function extractJvmTypeName(text: string, langKey: string): string | null {
|
||||
const withoutAnnotations = stripJvmAnnotations(text);
|
||||
const header = withoutAnnotations.split("{", 1)[0] ?? withoutAnnotations;
|
||||
const pattern = langKey === "scala"
|
||||
? /\b(?:class|object|trait)\s+([A-Za-z_$][\w$]*)\b/
|
||||
: /\b(?:class|interface|enum|object)\s+([A-Za-z_$][\w$]*)\b/;
|
||||
return header.match(pattern)?.[1] ?? null;
|
||||
}
|
||||
|
||||
function extractJvmCallableName(text: string): string | null {
|
||||
const withoutAnnotations = stripJvmAnnotations(text);
|
||||
const signature = withoutAnnotations
|
||||
.split("{", 1)[0]
|
||||
.split("=", 1)[0]
|
||||
.trim();
|
||||
const scalaDefMatches = Array.from(signature.matchAll(/\bdef\s+([A-Za-z_$][\w$]*)\b/g));
|
||||
if (scalaDefMatches.length > 0) {
|
||||
return scalaDefMatches[scalaDefMatches.length - 1][1];
|
||||
}
|
||||
const matches = Array.from(signature.matchAll(/([A-Za-z_$][\w$]*)\s*\(/g));
|
||||
return matches.length > 0 ? matches[matches.length - 1][1] : null;
|
||||
}
|
||||
|
||||
// ── C# ──────────────────────────────────────────────────────────────────
|
||||
|
||||
function extractFromCSharp(
|
||||
|
||||
@@ -149,6 +149,58 @@ public class Foo {
|
||||
expect(names).toContain("baz");
|
||||
});
|
||||
|
||||
it("prefers the declared Java class name over parameter types in Spring Boot entrypoints", () => {
|
||||
const src = `
|
||||
@SpringBootApplication
|
||||
public class WorkflowFlowableApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(WorkflowFlowableApplication.class, args);
|
||||
}
|
||||
}
|
||||
`;
|
||||
const out = extractSymbolsAndCalls(src, "java" as unknown as Lang, ".java", "WorkflowFlowableApplication.java");
|
||||
const names = out.symbols.map((s) => s.name);
|
||||
expect(names).toContain("WorkflowFlowableApplication");
|
||||
expect(names).not.toContain("String");
|
||||
expect(names).toContain("main");
|
||||
});
|
||||
|
||||
it("does not treat Java test annotations as method names", () => {
|
||||
const src = `
|
||||
class SecurityAuthClientRequireSubjectTest {
|
||||
@AfterEach
|
||||
void cleanup() {}
|
||||
|
||||
@Test
|
||||
void requireSubjectThrows() {}
|
||||
|
||||
@Test(timeout = 1000)
|
||||
void fastTest() {}
|
||||
}
|
||||
`;
|
||||
const out = extractSymbolsAndCalls(src, "java" as unknown as Lang, ".java", "SecurityAuthClientRequireSubjectTest.java");
|
||||
const names = out.symbols.map((s) => s.name);
|
||||
expect(names).toContain("SecurityAuthClientRequireSubjectTest");
|
||||
expect(names).toContain("cleanup");
|
||||
expect(names).toContain("requireSubjectThrows");
|
||||
expect(names).toContain("fastTest");
|
||||
expect(names).not.toContain("AfterEach");
|
||||
expect(names).not.toContain("Test");
|
||||
});
|
||||
|
||||
it("preserves Java declarations when annotations share the same line", () => {
|
||||
const src = `
|
||||
class InlineAnnotationTest {
|
||||
@Test void cleanup() {}
|
||||
}
|
||||
`;
|
||||
const out = extractSymbolsAndCalls(src, "java" as unknown as Lang, ".java", "InlineAnnotationTest.java");
|
||||
const names = out.symbols.map((s) => s.name);
|
||||
expect(names).toContain("InlineAnnotationTest");
|
||||
expect(names).toContain("cleanup");
|
||||
expect(names).not.toContain("Test");
|
||||
});
|
||||
|
||||
it("extracts Kotlin top-level fun and class methods", () => {
|
||||
const src = `
|
||||
fun greet(name: String): String = "Hi"
|
||||
@@ -168,6 +220,8 @@ class Bar {
|
||||
const src = `
|
||||
class Foo {
|
||||
def bar(): Int = 1
|
||||
def size: Int = 1
|
||||
def now = Instant.now()
|
||||
}
|
||||
|
||||
object Main {
|
||||
@@ -177,6 +231,8 @@ object Main {
|
||||
const out = extractSymbolsAndCalls(src, "scala" as unknown as Lang, ".scala", "Main.scala");
|
||||
const names = out.symbols.map((s) => s.name);
|
||||
expect(names).toContain("bar");
|
||||
expect(names).toContain("size");
|
||||
expect(names).toContain("now");
|
||||
expect(names).toContain("main");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user