дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-08 19:02:17 +02:00
fix(run): offer Run for classes whose superclass declares main (#1204)
The JVM run-target scanner only reads the file's own text, so a class like `class App extends AtomStarter {}`, whose superclass (in the project or a library JAR) declares `static main`, got no gutter Run, no Ctrl+Shift+R and no file-level Run, although the JVM launches it. Classes with an `extends` clause and no main of their own are now candidates: the superclass chain is walked through the language server's type hierarchy, each class supertype's source (file, decompiled or source-jar document) is checked for a static main, and the answer is cached per class and `extends` clause, so the gutter, Run from context, launch and debug agree. Interfaces are skipped (a static interface main is not inherited); negative answers are asked again when the server settles. `main(java.lang.String[] a)` is accepted as well.
Co-authored-by: ntbang0901 <[email protected]>
Цей коміт міститься в:
1 джерело
8f77d7077c
коміт
d032addb17
7 files changed
+431
-9
No files matched your search
@@ -11,7 +11,7 @@ import { useProject } from "@/platform/project";
|
||||
import { applicationConfig, jvmModuleDir, jvmTestConfig } from "./application";
|
||||
import { buildRows } from "./ConfigChooser";
|
||||
import { jdwpLaunch } from "./javaDebug";
|
||||
import { scanJvmTargets } from "./jvmTargets";
|
||||
import { jvmMainCandidates, scanJvmTargets } from "./jvmTargets";
|
||||
import { adoptTemporary, MAX_TEMPORARY, saveTemporary, testReportEvents, useRun } from "./store";
|
||||
import { detectRunTargets, targetConfig } from "./targets";
|
||||
|
||||
@@ -436,3 +436,35 @@ describe("temporary configurations", () => {
|
||||
expect(useRun.getState().temporary).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("jvmMainCandidates", () => {
|
||||
const names = (src: string) => jvmMainCandidates(src, "a/B.java").map((c) => c.className);
|
||||
|
||||
it("finds the class from the report, at its name", () => {
|
||||
const src = "package demo;\n\n@SpringBootApplication\npublic class AiChatApplication extends AtomStarter {}\n";
|
||||
expect(jvmMainCandidates(src, "a/B.java")).toEqual([
|
||||
{ line: 3, col: 13, className: "demo.AiChatApplication", base: "AtomStarter", spring: true },
|
||||
]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["static nested class", "class A { static class B extends Base {} }", ["A$B"]],
|
||||
["inner (non-static) class", "class A { class B extends Base {} }", []],
|
||||
["abstract class", "abstract class A extends Base {}", ["A"]],
|
||||
["own main", "class A extends Base { public static void main(String[] a) {} }", []],
|
||||
["implements only", "class A implements Runnable {}", []],
|
||||
["interface extends", "interface A extends Base {}", []],
|
||||
["enum and record", "enum E implements I {}\nrecord R() implements I {}", []],
|
||||
["multi-line generic header", "class A<T extends Number>\n extends Base<T>\n{}", ["A"]],
|
||||
["type bound only", "class A<T extends Number> {}", []],
|
||||
["extends in comment and string", '// class A extends B\nclass C { String s = "class D extends E {"; }', []],
|
||||
])("%s", (_, src, expected) => expect(names(src)).toEqual(expected));
|
||||
|
||||
it("accepts java.lang.String[] and flags instance main", () => {
|
||||
const [fq] = scanJvmTargets("class A { public static void main(java.lang.String[] a) {} }", "A.java", false);
|
||||
expect(fq).toMatchObject({ kind: "main", className: "A" });
|
||||
const [inst] = scanJvmTargets("class A { void main() {} }", "A.java", false);
|
||||
expect(inst).toMatchObject({ kind: "main", instance: true });
|
||||
expect(fq?.instance).toBeUndefined();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Заблокувати користувача