diff --git a/src/languageModelTool.ts b/src/languageModelTool.ts index 2ef6b7b9..0950aec3 100644 --- a/src/languageModelTool.ts +++ b/src/languageModelTool.ts @@ -58,6 +58,26 @@ const CONSTANTS = { MAX_FILE_SEARCH_DEPTH: 10 }; +const SEARCH_SKIPPED_DIRECTORIES = new Set([ + '.git', + '.gradle', + '.idea', + '.vscode', + 'bin', + 'build', + 'classes', + 'dist', + 'generated', + 'generated-sources', + 'node_modules', + 'out', + 'target', +]); + +function shouldSkipSearchDirectory(name: string): boolean { + return SEARCH_SKIPPED_DIRECTORIES.has(name); +} + const LAUNCH_FAILURE_GUIDANCE = '\n\nDo not automatically retry debug_java_application or start the program again ' + 'through a terminal command. Report the result and diagnose the cause first. ' + 'After a timeout, you may check get_debug_session_info once and inspect existing terminal output; ' @@ -923,14 +943,14 @@ function findJavaFile(dir: string, className: string, depth: number = 0): string const files = fs.readdirSync(dir); for (const file of files) { + if (shouldSkipSearchDirectory(file)) { + continue; + } + const filePath = path.join(dir, file); const stat = fs.statSync(filePath); if (stat.isDirectory()) { - // Skip common non-source directories - if (file === 'node_modules' || file === '.git' || file === 'target' || file === 'build') { - continue; - } const found = findJavaFile(filePath, className, depth + 1); if (found) { return found; @@ -972,6 +992,10 @@ function hasClassFiles(dir: string, depth: number = 0): boolean { try { const files = fs.readdirSync(dir); for (const file of files) { + if (shouldSkipSearchDirectory(file)) { + continue; + } + const filePath = path.join(dir, file); const stat = fs.statSync(filePath); diff --git a/test/languageModelToolLaunchPolicy.test.ts b/test/languageModelToolLaunchPolicy.test.ts index 4af9baa0..c616d16a 100644 --- a/test/languageModelToolLaunchPolicy.test.ts +++ b/test/languageModelToolLaunchPolicy.test.ts @@ -3,6 +3,7 @@ import * as assert from "assert"; import * as fs from "fs"; +import * as os from "os"; import * as path from "path"; import * as vscode from "vscode"; import * as telemetry from "vscode-extension-telemetry-wrapper"; @@ -124,6 +125,33 @@ suite("Language Model Tool launch retry policy", () => { assert.strictEqual(text.includes("Call debug_java_application again"), false); } + test("ignores generated build directories when resolving simple class names", async () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "java-debug-class-detection-")); + const sourceFile = path.join(tempRoot, "src", "main", "java", "com", "example", "App.java"); + const generatedFile = path.join(tempRoot, "build", "generated", "App.java"); + + fs.mkdirSync(path.dirname(sourceFile), { recursive: true }); + fs.mkdirSync(path.dirname(generatedFile), { recursive: true }); + + fs.writeFileSync(sourceFile, "package com.example;\npublic class App {}\n"); + fs.writeFileSync(generatedFile, "public class App {}\n"); + + try { + const text = await invoke({ + target: "App", + workspacePath: tempRoot, + skipBuild: true, + classpath: tempRoot, + }); + + assert.ok(text.includes("Debug session started") || text.includes("Startup is unconfirmed, not necessarily failed")); + assert.ok(!text.includes("Could not auto-detect package name")); + assert.strictEqual(commandsSent, 1); + } finally { + fs.rmSync(tempRoot, { recursive: true, force: true }); + } + }); + for (const waitForSession of [false, true]) { test(`reports unconfirmed startup without relaunching (waitForSession=${waitForSession})`, async () => { const text = await invoke({