From 0078d11897d0b32d7b6ceb8b22355ce1c33561d3 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Mon, 20 Oct 2025 21:37:59 +0800 Subject: [PATCH 1/9] feat: release for manager --- .../jdtls/ext/core/ProjectCommand.java | 42 +++ .../ext/core/parser/ContextResolver.java | 289 +++++++++++++++++- package-lock.json | 172 +++++++++++ package.json | 1 + src/copilot/contextProvider.ts | 201 ++++++++++++ src/copilot/copilotHelper.ts | 255 ++++++++++++++++ src/copilot/utils.ts | 154 ++++++++++ src/extension.ts | 2 + 8 files changed, 1107 insertions(+), 9 deletions(-) create mode 100644 src/copilot/contextProvider.ts create mode 100644 src/copilot/copilotHelper.ts create mode 100644 src/copilot/utils.ts diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java index df41a432..257c908e 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java @@ -341,9 +341,20 @@ public static boolean checkImportStatus() { return hasError; } + // Threshold for triggering external dependency resolution + // If project source classes < this value, supplement with external dependencies + private static final int MIN_SOURCE_CLASSES_THRESHOLD = 5; + + // Maximum number of external dependency classes to include + private static final int MAX_DEPENDENCY_CLASSES = 10; + + // Maximum methods to display for binary (external) classes + private static final int MAX_METHODS_FOR_BINARY = 5; + /** * Get import class content for Copilot integration. * This method extracts information about imported classes from a Java file. + * Uses an adaptive strategy: only includes external dependencies when project sources are sparse. * * @param arguments List containing the file URI as the first element * @param monitor Progress monitor for cancellation support @@ -395,6 +406,7 @@ public static List getImportClassContent(List arguments org.eclipse.jdt.core.IImportDeclaration[] imports = compilationUnit.getImports(); Set processedTypes = new HashSet<>(); + // Phase 1: Resolve project source classes only for (org.eclipse.jdt.core.IImportDeclaration importDecl : imports) { if (monitor.isCanceled()) { break; @@ -416,6 +428,36 @@ public static List getImportClassContent(List arguments } } + // Phase 2: Adaptive external dependency resolution + // Only trigger if project source classes are sparse (< threshold) + if (classInfoList.size() < MIN_SOURCE_CLASSES_THRESHOLD && !monitor.isCanceled()) { + // Track external classes separately to apply limits + List externalClasses = new ArrayList<>(); + + for (org.eclipse.jdt.core.IImportDeclaration importDecl : imports) { + if (monitor.isCanceled() || externalClasses.size() >= MAX_DEPENDENCY_CLASSES) { + break; + } + + String importName = importDecl.getElementName(); + boolean isStatic = (importDecl.getFlags() & org.eclipse.jdt.core.Flags.AccStatic) != 0; + + // Skip package imports (*.* ) - too broad for external dependencies + if (importName.endsWith(".*")) { + continue; + } + + // Resolve external (binary) types with simplified content + if (!isStatic) { + ContextResolver.resolveBinaryType(javaProject, importName, externalClasses, + processedTypes, MAX_METHODS_FOR_BINARY, monitor); + } + } + + // Append external classes after project sources + classInfoList.addAll(externalClasses); + } + return classInfoList; } catch (Exception e) { diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ContextResolver.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ContextResolver.java index e0218b0c..3762626c 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ContextResolver.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ContextResolver.java @@ -49,11 +49,11 @@ public class ContextResolver { */ public static class ImportClassInfo { public String uri; // File URI (required) - public String className; // Human-readable class description with JavaDoc appended (required) + public String value; // Human-readable class description with JavaDoc appended (required) - public ImportClassInfo(String uri, String className) { + public ImportClassInfo(String uri, String value) { this.uri = uri; - this.className = className; + this.value = value; } } @@ -63,15 +63,16 @@ public ImportClassInfo(String uri, String className) { public static void resolveSingleType(IJavaProject javaProject, String typeName, List classInfoList, Set processedTypes, IProgressMonitor monitor) { try { + // Check if already processed to avoid duplicates if (processedTypes.contains(typeName)) { return; } - processedTypes.add(typeName); // Extract package and simple name from the fully qualified type name int lastDotIndex = typeName.lastIndexOf('.'); if (lastDotIndex == -1) { - // Default package or invalid type name + // Default package or invalid type name - mark as processed to avoid retry + processedTypes.add(typeName); return; } @@ -87,12 +88,14 @@ public static void resolveSingleType(IJavaProject javaProject, String typeName, if (type != null && type.exists()) { // Found type - check if it's a source type we want to process if (!type.isBinary()) { - // Source type found - extract information and return + // Source type found - mark as processed and extract information + processedTypes.add(typeName); extractTypeInfo(type, classInfoList, monitor); return; } - // Note: Binary types (from JARs/JRE) are intentionally ignored - // as they don't provide useful context for code completion + // Binary types (from JARs/JRE) found but not processed in Phase 1 + // Do NOT mark as processed - let Phase 2 handle them if triggered + return; } } catch (JavaModelException e) { JdtlsExtActivator.logException("Error in primary type search: " + typeName, e); @@ -114,6 +117,7 @@ public static void resolveSingleType(IJavaProject javaProject, String typeName, if (primaryType != null && primaryType.exists() && typeName.equals(primaryType.getFullyQualifiedName())) { // Found local project source type via fallback method + processedTypes.add(typeName); extractTypeInfo(primaryType, classInfoList, monitor); return; } @@ -122,6 +126,7 @@ public static void resolveSingleType(IJavaProject javaProject, String typeName, org.eclipse.jdt.core.IType[] allTypes = cu.getAllTypes(); for (org.eclipse.jdt.core.IType type : allTypes) { if (typeName.equals(type.getFullyQualifiedName())) { + processedTypes.add(typeName); extractTypeInfo(type, classInfoList, monitor); return; } @@ -130,9 +135,14 @@ public static void resolveSingleType(IJavaProject javaProject, String typeName, } } } + + // Type not found - mark as processed to avoid repeated failed lookups + processedTypes.add(typeName); + } catch (JavaModelException e) { - // Log but continue processing other types + // Log and mark as processed even on error to avoid repeated failures JdtlsExtActivator.logException("Error resolving type: " + typeName, e); + processedTypes.add(typeName); } } @@ -284,6 +294,47 @@ public static void resolveStaticMemberFromClass(IJavaProject javaProject, String } } + /** + * Resolve a binary type (from external JAR/JRE) with simplified content. + * This method is used for external dependencies when project sources are sparse. + * + * @param javaProject The Java project context + * @param typeName Fully qualified type name (e.g., "java.util.ArrayList") + * @param classInfoList List to append resolved class information + * @param processedTypes Set tracking already processed types to avoid duplicates + * @param maxMethods Maximum number of methods to include (to limit token usage) + * @param monitor Progress monitor for cancellation + */ + public static void resolveBinaryType(IJavaProject javaProject, String typeName, + List classInfoList, Set processedTypes, + int maxMethods, IProgressMonitor monitor) { + try { + if (processedTypes.contains(typeName)) { + return; + } + + // Use JDT's findType which searches all sources and dependencies + org.eclipse.jdt.core.IType type = javaProject.findType(typeName); + if (type == null || !type.exists()) { + return; + } + + // Only process binary types (from JARs/JRE) + if (!type.isBinary()) { + return; // Skip source types - they should be handled by resolveSingleType + } + + processedTypes.add(typeName); + + // Extract simplified information for binary types + extractBinaryTypeInfo(type, classInfoList, maxMethods, monitor); + + } catch (JavaModelException e) { + // Log but continue processing other types + JdtlsExtActivator.logException("Error resolving binary type: " + typeName, e); + } + } + /** * Resolve all types in a package (for wildcard imports) */ @@ -355,6 +406,225 @@ public static void extractTypeInfo(org.eclipse.jdt.core.IType type, List classInfoList, int maxMethods, IProgressMonitor monitor) { + try { + // Use a placeholder URI for binary types (they don't have local file paths) + String uri = "jar://" + type.getFullyQualifiedName().replace('.', '/') + ".class"; + + // Generate simplified class description for binary types + StringBuilder sb = new StringBuilder(); + + // 1. Extract class-level JavaDoc (brief summary only) + String javadoc = extractBriefJavaDoc(type); + if (javadoc != null && !javadoc.isEmpty()) { + sb.append("/**\n * ").append(javadoc).append("\n */\n"); + } + + // 2. Class signature (modifiers, name, generics, inheritance) + sb.append(generateClassSignature(type)); + sb.append(" {\n\n"); + + // 3. Public fields (limit to first 5) + org.eclipse.jdt.core.IField[] fields = type.getFields(); + int fieldCount = 0; + for (org.eclipse.jdt.core.IField field : fields) { + if (fieldCount >= 5) break; + if (org.eclipse.jdt.core.Flags.isPublic(field.getFlags())) { + sb.append(" ").append(generateBinaryFieldSignature(field)).append("\n"); + fieldCount++; + } + } + if (fieldCount > 0) { + sb.append("\n"); + } + + // 4. Public methods (limited by maxMethods parameter) + org.eclipse.jdt.core.IMethod[] methods = type.getMethods(); + int methodCount = 0; + for (org.eclipse.jdt.core.IMethod method : methods) { + if (methodCount >= maxMethods) break; + if (org.eclipse.jdt.core.Flags.isPublic(method.getFlags())) { + sb.append(" ").append(generateBinaryMethodSignature(method)).append("\n"); + methodCount++; + } + } + + sb.append("}\n"); + + // Add note indicating this is simplified external dependency info + sb.append("// Note: External dependency - showing simplified signature only\n"); + + // Create ImportClassInfo + ImportClassInfo info = new ImportClassInfo(uri, sb.toString()); + classInfoList.add(info); + + } catch (JavaModelException e) { + JdtlsExtActivator.logException("Error extracting binary type info for: " + type.getElementName(), e); + } + } + + /** + * Generate class signature with modifiers, name, generics, and inheritance + */ + private static String generateClassSignature(org.eclipse.jdt.core.IType type) throws JavaModelException { + StringBuilder sb = new StringBuilder(); + + // Modifiers + int flags = type.getFlags(); + if (org.eclipse.jdt.core.Flags.isPublic(flags)) sb.append("public "); + if (org.eclipse.jdt.core.Flags.isAbstract(flags) && !type.isInterface()) sb.append("abstract "); + if (org.eclipse.jdt.core.Flags.isFinal(flags)) sb.append("final "); + + // Type kind + if (type.isInterface()) { + sb.append("interface "); + } else if (type.isEnum()) { + sb.append("enum "); + } else if (type.isAnnotation()) { + sb.append("@interface "); + } else { + sb.append("class "); + } + + // Simple name + sb.append(type.getElementName()); + + // Type parameters + org.eclipse.jdt.core.ITypeParameter[] typeParams = type.getTypeParameters(); + if (typeParams != null && typeParams.length > 0) { + sb.append("<"); + for (int i = 0; i < typeParams.length; i++) { + if (i > 0) sb.append(", "); + sb.append(typeParams[i].getElementName()); + } + sb.append(">"); + } + + // Superclass + String superclass = type.getSuperclassName(); + if (superclass != null && !superclass.equals("Object") && !type.isInterface()) { + sb.append(" extends ").append(simplifyTypeName(superclass)); + } + + // Interfaces + String[] interfaces = type.getSuperInterfaceNames(); + if (interfaces != null && interfaces.length > 0) { + if (type.isInterface()) { + sb.append(" extends "); + } else { + sb.append(" implements "); + } + for (int i = 0; i < interfaces.length; i++) { + if (i > 0) sb.append(", "); + sb.append(simplifyTypeName(interfaces[i])); + } + } + + return sb.toString(); + } + + /** + * Extract brief JavaDoc summary for binary types (first sentence only) + */ + private static String extractBriefJavaDoc(org.eclipse.jdt.core.IType type) { + try { + String javadoc = type.getAttachedJavadoc(null); + if (javadoc == null || javadoc.isEmpty()) { + return null; + } + + // Extract first sentence (up to first period followed by space or newline) + int endIndex = javadoc.indexOf(". "); + if (endIndex == -1) { + endIndex = javadoc.indexOf(".\n"); + } + if (endIndex != -1) { + return javadoc.substring(0, endIndex + 1).trim(); + } + + // If no sentence boundary found, limit to 120 characters + return javadoc.length() > 120 ? javadoc.substring(0, 120) + "..." : javadoc; + + } catch (Exception e) { + return null; + } + } + + /** + * Generate simplified field signature for binary types + */ + private static String generateBinaryFieldSignature(org.eclipse.jdt.core.IField field) { + try { + StringBuilder sb = new StringBuilder(); + + // Modifiers + int flags = field.getFlags(); + if (org.eclipse.jdt.core.Flags.isPublic(flags)) sb.append("public "); + if (org.eclipse.jdt.core.Flags.isStatic(flags)) sb.append("static "); + if (org.eclipse.jdt.core.Flags.isFinal(flags)) sb.append("final "); + + // Type and name + sb.append(simplifyTypeName(org.eclipse.jdt.core.Signature.toString(field.getTypeSignature()))); + sb.append(" ").append(field.getElementName()).append(";"); + + return sb.toString(); + } catch (JavaModelException e) { + return "// Error generating field signature"; + } + } + + /** + * Generate simplified method signature for binary types (no implementation) + */ + private static String generateBinaryMethodSignature(org.eclipse.jdt.core.IMethod method) { + try { + StringBuilder sb = new StringBuilder(); + + // Modifiers + int flags = method.getFlags(); + if (org.eclipse.jdt.core.Flags.isPublic(flags)) sb.append("public "); + if (org.eclipse.jdt.core.Flags.isStatic(flags)) sb.append("static "); + if (org.eclipse.jdt.core.Flags.isFinal(flags)) sb.append("final "); + if (org.eclipse.jdt.core.Flags.isAbstract(flags)) sb.append("abstract "); + + // Return type (skip for constructors) + if (!method.isConstructor()) { + sb.append(simplifyTypeName(org.eclipse.jdt.core.Signature.toString(method.getReturnType()))); + sb.append(" "); + } + + // Method name + sb.append(method.getElementName()).append("("); + + // Parameters (simplified - just types, no names) + String[] paramTypes = method.getParameterTypes(); + for (int i = 0; i < paramTypes.length; i++) { + if (i > 0) sb.append(", "); + sb.append(simplifyTypeName(org.eclipse.jdt.core.Signature.toString(paramTypes[i]))); + } + + sb.append(");"); + + return sb.toString(); + } catch (JavaModelException e) { + return "// Error generating method signature"; + } + } + /** * Get file URI/path for the type (instead of fully qualified class name) */ @@ -827,6 +1097,7 @@ public static String generateMethodSignature(IMethod method) { appendOtherModifiers(sb, flags, true); // Type parameters (if any) + @SuppressWarnings("deprecation") String[] typeParameters = method.getTypeParameterSignatures(); if (typeParameters != null && typeParameters.length > 0) { sb.append("<"); diff --git a/package-lock.json b/package-lock.json index 5863a3aa..03b7b9bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.26.1", "license": "MIT", "dependencies": { + "@github/copilot-language-server": "^1.316.0", "await-lock": "^2.2.2", "fmtr": "^1.1.4", "fs-extra": "^10.1.0", @@ -158,6 +159,90 @@ "node": ">=10.0.0" } }, + "node_modules/@github/copilot-language-server": { + "version": "1.385.0", + "resolved": "https://registry.npmjs.org/@github/copilot-language-server/-/copilot-language-server-1.385.0.tgz", + "integrity": "sha512-CB61VtJSmuDhSb7IKzYv5+hIsuIye6fg27wZaUYizw704m6qOdgevnCMiWzAMUD+2J7RfmBY0/VRGf/AVL22Pw==", + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "^3.17.5" + }, + "bin": { + "copilot-language-server": "dist/language-server.js" + }, + "optionalDependencies": { + "@github/copilot-language-server-darwin-arm64": "1.385.0", + "@github/copilot-language-server-darwin-x64": "1.385.0", + "@github/copilot-language-server-linux-arm64": "1.385.0", + "@github/copilot-language-server-linux-x64": "1.385.0", + "@github/copilot-language-server-win32-x64": "1.385.0" + } + }, + "node_modules/@github/copilot-language-server-darwin-arm64": { + "version": "1.385.0", + "resolved": "https://registry.npmjs.org/@github/copilot-language-server-darwin-arm64/-/copilot-language-server-darwin-arm64-1.385.0.tgz", + "integrity": "sha512-HCSffo9wzNTNK7nvyUoxcCZmotZYRnt7hq046wcVjcwWLvr3I+SjdskGeIbuxob1l7eeBkodHAqbVyJ2j5qAUA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@github/copilot-language-server-darwin-x64": { + "version": "1.385.0", + "resolved": "https://registry.npmjs.org/@github/copilot-language-server-darwin-x64/-/copilot-language-server-darwin-x64-1.385.0.tgz", + "integrity": "sha512-fIlpPZEQqRClq2PHL7yQh0BNQmRFnF1mg4m78rPoRV+CW5YU98U10bh9wo2lh9SomKuc+Tjtm5rmGYtIkDQapA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@github/copilot-language-server-linux-arm64": { + "version": "1.385.0", + "resolved": "https://registry.npmjs.org/@github/copilot-language-server-linux-arm64/-/copilot-language-server-linux-arm64-1.385.0.tgz", + "integrity": "sha512-CvDTrdig/5woAHEs1eyAkv0pQaCtTgFQIjMT5K1cI3keo2Cf1sC5ya4nx0zSvfQhVkBLgSXf/DPhlfneXdxxxw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@github/copilot-language-server-linux-x64": { + "version": "1.385.0", + "resolved": "https://registry.npmjs.org/@github/copilot-language-server-linux-x64/-/copilot-language-server-linux-x64-1.385.0.tgz", + "integrity": "sha512-fFkReJRjdjpCkSK89+2mumM0F2wr7nrGyfxVhq8ex47l2H66GbDVRp4tvVYrhD1nXL1+9WSut+LdVJjIHjHBcg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@github/copilot-language-server-win32-x64": { + "version": "1.385.0", + "resolved": "https://registry.npmjs.org/@github/copilot-language-server-win32-x64/-/copilot-language-server-win32-x64-1.385.0.tgz", + "integrity": "sha512-bm0l6eK9auuPlUqVnbFb2EwIN4G3nAVWSfiCiY5xVJeExiQsEqfdAsEV56zawV/9RIVaD4x/kGjgI9T8imaalg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -5735,6 +5820,31 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" + }, "node_modules/vscode-tas-client": { "version": "0.1.75", "resolved": "https://registry.npmjs.org/vscode-tas-client/-/vscode-tas-client-0.1.75.tgz", @@ -6232,6 +6342,49 @@ "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", "dev": true }, + "@github/copilot-language-server": { + "version": "1.385.0", + "resolved": "https://registry.npmjs.org/@github/copilot-language-server/-/copilot-language-server-1.385.0.tgz", + "integrity": "sha512-CB61VtJSmuDhSb7IKzYv5+hIsuIye6fg27wZaUYizw704m6qOdgevnCMiWzAMUD+2J7RfmBY0/VRGf/AVL22Pw==", + "requires": { + "@github/copilot-language-server-darwin-arm64": "1.385.0", + "@github/copilot-language-server-darwin-x64": "1.385.0", + "@github/copilot-language-server-linux-arm64": "1.385.0", + "@github/copilot-language-server-linux-x64": "1.385.0", + "@github/copilot-language-server-win32-x64": "1.385.0", + "vscode-languageserver-protocol": "^3.17.5" + } + }, + "@github/copilot-language-server-darwin-arm64": { + "version": "1.385.0", + "resolved": "https://registry.npmjs.org/@github/copilot-language-server-darwin-arm64/-/copilot-language-server-darwin-arm64-1.385.0.tgz", + "integrity": "sha512-HCSffo9wzNTNK7nvyUoxcCZmotZYRnt7hq046wcVjcwWLvr3I+SjdskGeIbuxob1l7eeBkodHAqbVyJ2j5qAUA==", + "optional": true + }, + "@github/copilot-language-server-darwin-x64": { + "version": "1.385.0", + "resolved": "https://registry.npmjs.org/@github/copilot-language-server-darwin-x64/-/copilot-language-server-darwin-x64-1.385.0.tgz", + "integrity": "sha512-fIlpPZEQqRClq2PHL7yQh0BNQmRFnF1mg4m78rPoRV+CW5YU98U10bh9wo2lh9SomKuc+Tjtm5rmGYtIkDQapA==", + "optional": true + }, + "@github/copilot-language-server-linux-arm64": { + "version": "1.385.0", + "resolved": "https://registry.npmjs.org/@github/copilot-language-server-linux-arm64/-/copilot-language-server-linux-arm64-1.385.0.tgz", + "integrity": "sha512-CvDTrdig/5woAHEs1eyAkv0pQaCtTgFQIjMT5K1cI3keo2Cf1sC5ya4nx0zSvfQhVkBLgSXf/DPhlfneXdxxxw==", + "optional": true + }, + "@github/copilot-language-server-linux-x64": { + "version": "1.385.0", + "resolved": "https://registry.npmjs.org/@github/copilot-language-server-linux-x64/-/copilot-language-server-linux-x64-1.385.0.tgz", + "integrity": "sha512-fFkReJRjdjpCkSK89+2mumM0F2wr7nrGyfxVhq8ex47l2H66GbDVRp4tvVYrhD1nXL1+9WSut+LdVJjIHjHBcg==", + "optional": true + }, + "@github/copilot-language-server-win32-x64": { + "version": "1.385.0", + "resolved": "https://registry.npmjs.org/@github/copilot-language-server-win32-x64/-/copilot-language-server-win32-x64-1.385.0.tgz", + "integrity": "sha512-bm0l6eK9auuPlUqVnbFb2EwIN4G3nAVWSfiCiY5xVJeExiQsEqfdAsEV56zawV/9RIVaD4x/kGjgI9T8imaalg==", + "optional": true + }, "@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -10355,6 +10508,25 @@ "dev": true, "requires": {} }, + "vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==" + }, + "vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "requires": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" + }, "vscode-tas-client": { "version": "0.1.75", "resolved": "https://registry.npmjs.org/vscode-tas-client/-/vscode-tas-client-0.1.75.tgz", diff --git a/package.json b/package.json index 217d60d7..cb859508 100644 --- a/package.json +++ b/package.json @@ -1110,6 +1110,7 @@ "webpack-cli": "^4.10.0" }, "dependencies": { + "@github/copilot-language-server": "^1.316.0", "await-lock": "^2.2.2", "fmtr": "^1.1.4", "fs-extra": "^10.1.0", diff --git a/src/copilot/contextProvider.ts b/src/copilot/contextProvider.ts new file mode 100644 index 00000000..1709a44b --- /dev/null +++ b/src/copilot/contextProvider.ts @@ -0,0 +1,201 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { + ResolveRequest, + SupportedContextItem, + type ContextProvider, +} from '@github/copilot-language-server'; +import * as vscode from 'vscode'; +import { CopilotHelper } from './copilotHelper'; +import { sendInfo } from "vscode-extension-telemetry-wrapper"; +import { + JavaContextProviderUtils, + CancellationError, + InternalCancellationError, + CopilotCancellationError, + ContextResolverFunction, + CopilotApi +} from './utils'; + +export async function registerCopilotContextProviders( + context: vscode.ExtensionContext +) { + try { + const apis = await JavaContextProviderUtils.getCopilotApis(); + if (!apis.clientApi || !apis.chatApi) { + return; + } + + // Register the Java completion context provider + const provider: ContextProvider = { + id: "vscjava.vscode-java-dependency", // use extension id as provider id for now + selector: [{ language: "java" }], + resolver: { resolve: createJavaContextResolver() } + }; + + const installCount = await JavaContextProviderUtils.installContextProviderOnApis(apis, provider, context, installContextProvider); + + if (installCount === 0) { + return; + } + console.log('======= register context provider =======') + + sendInfo("", { + "action": "registerCopilotContextProvider", + "extension": "vscjava.vscode-java-dependency", + "status": "succeeded", + "installCount": installCount + }); + } + catch (error) { + console.error('Error occurred while registering Java context provider for GitHub Copilot extension:', error); + } +} + +/** + * Create the Java context resolver function + */ +function createJavaContextResolver(): ContextResolverFunction { + return async (request: ResolveRequest, copilotCancel: vscode.CancellationToken): Promise => { + const resolveStartTime = performance.now(); + let logMessage = `Java Context Provider: resolve(${request.documentContext.uri}:${request.documentContext.offset}):`; + + try { + // Check for immediate cancellation + JavaContextProviderUtils.checkCancellation(copilotCancel); + + return await resolveJavaContext(request, copilotCancel); + } catch (error: any) { + // This should never be reached due to handleError throwing, but TypeScript requires it + return []; + } finally { + const duration = Math.round(performance.now() - resolveStartTime); + if (!logMessage.includes('cancellation')) { + logMessage += `(completed in ${duration}ms)`; + console.info(logMessage); + } + } + }; +} + +/** + * Send telemetry data for Java context resolution + */ +function sendContextTelemetry(request: ResolveRequest, start: number, itemCount: number, status: string, error?: string) { + const duration = Math.round(performance.now() - start); + const telemetryData: any = { + "action": "resolveJavaContext", + "completionId": request.completionId, + "duration": duration, + "itemCount": itemCount, + "status": status + }; + + if (error) { + telemetryData.error = error; + } + + sendInfo("", telemetryData); +} + +async function resolveJavaContext(request: ResolveRequest, copilotCancel: vscode.CancellationToken): Promise { + const items: SupportedContextItem[] = []; + const start = performance.now(); + const documentUri = request.documentContext.uri; + const caretOffset = request.documentContext.offset; + + try { + // Check for cancellation before starting + JavaContextProviderUtils.checkCancellation(copilotCancel); + + // Get current document and position information + const activeEditor = vscode.window.activeTextEditor; + if (!activeEditor || activeEditor.document.languageId !== 'java') { + return items; + } + + const document = activeEditor.document; + + // Resolve imports directly without caching + const importClass = await CopilotHelper.resolveLocalImports(document.uri, copilotCancel); + console.trace('Resolved imports count:', importClass?.length || 0); + + // Check for cancellation after resolution + JavaContextProviderUtils.checkCancellation(copilotCancel); + + // Check for cancellation before processing results + JavaContextProviderUtils.checkCancellation(copilotCancel); + + if (importClass) { + // Process imports in batches to reduce cancellation check overhead + const contextItems = JavaContextProviderUtils.createContextItemsFromImports(importClass); + + // Check cancellation once after creating all items + JavaContextProviderUtils.checkCancellation(copilotCancel); + + items.push(...contextItems); + } + console.log('------ d1'); + + // Get workspace folder from document URI to get project URI + const workspaceFolder = vscode.workspace.getWorkspaceFolder(document.uri); + if (workspaceFolder) { + // Check for cancellation before calling external libraries metadata + JavaContextProviderUtils.checkCancellation(copilotCancel); + console.log('----------- d2') + // Get external libraries metadata as an array of Trait objects + const metadata = await CopilotHelper.getExternalLibrariesMetadata(workspaceFolder.uri, copilotCancel); + console.trace('Resolved external libraries metadata count:', metadata.length); + console.log('--------- d3') + // Check for cancellation after resolution + JavaContextProviderUtils.checkCancellation(copilotCancel); + + // The metadata is already in Trait format (array of {name, value} objects) + if (metadata && metadata.length > 0) { + // Check cancellation once after receiving all traits + JavaContextProviderUtils.checkCancellation(copilotCancel); + + items.push(...metadata); + } + } + } catch (error: any) { + if (error instanceof CopilotCancellationError) { + sendContextTelemetry(request, start, items.length, "cancelled_by_copilot"); + throw error; + } + if (error instanceof vscode.CancellationError || error.message === CancellationError.Canceled) { + sendContextTelemetry(request, start, items.length, "cancelled_internally"); + throw new InternalCancellationError(); + } + + // Send telemetry for general errors (but continue with partial results) + sendContextTelemetry(request, start, items.length, "error_partial_results", error.message || "unknown_error"); + + console.error(`Error resolving Java context for ${documentUri}:${caretOffset}:`, error); + + // Return partial results and log completion for error case + return items; + } + + // Send telemetry data once at the end for success case + sendContextTelemetry(request, start, items.length, "succeeded"); + console.log('========= Finish context provider, time:', performance.now() - start, " item.length", items.length); + console.dir(items) + return items; +} + +export async function installContextProvider( + copilotAPI: CopilotApi, + contextProvider: ContextProvider +): Promise { + const hasGetContextProviderAPI = typeof copilotAPI.getContextProviderAPI === 'function'; + if (hasGetContextProviderAPI) { + const contextAPI = await copilotAPI.getContextProviderAPI('v1'); + if (contextAPI) { + return contextAPI.registerContextProvider(contextProvider); + } + } + return undefined; +} diff --git a/src/copilot/copilotHelper.ts b/src/copilot/copilotHelper.ts new file mode 100644 index 00000000..90f0806a --- /dev/null +++ b/src/copilot/copilotHelper.ts @@ -0,0 +1,255 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { commands, Uri, CancellationToken } from "vscode"; + +export interface INodeImportClass { + uri: string; + className: string; // Changed from 'class' to 'className' to match Java code +} + +interface INodeData { + displayName?: string; + name: string; + moduleName?: string; + path?: string; + handlerIdentifier?: string; + uri?: string; + kind: number; // NodeKind enum value + children?: any[]; + metaData?: { [id: string]: any }; +} + +enum NodeKind { + Workspace = 1, + Project = 2, + PackageRoot = 3, + Package = 4, + PrimaryType = 5, + CompilationUnit = 6, + ClassFile = 7, + Container = 8, + Folder = 9, + File = 10, +} +/** + * Helper class for Copilot integration to analyze Java project dependencies + */ +export namespace CopilotHelper { + /** + * Resolves all local project types imported by the given file + * @param fileUri The URI of the Java file to analyze + * @param cancellationToken Optional cancellation token to abort the operation + * @returns Array of strings in format "type:fully.qualified.name" where type is class|interface|enum|annotation + */ + export async function resolveLocalImports(fileUri: Uri, cancellationToken?: CancellationToken): Promise { + if (cancellationToken?.isCancellationRequested) { + return []; + } + + if (cancellationToken?.isCancellationRequested) { + return []; + } + + try { + // Create a promise that can be cancelled + const commandPromise = commands.executeCommand("java.execute.workspaceCommand", "java.project.getImportClassContent", fileUri.toString()) as Promise; + + if (cancellationToken) { + const result = await Promise.race([ + commandPromise, + new Promise((_, reject) => { + cancellationToken.onCancellationRequested(() => { + reject(new Error('Operation cancelled')); + }); + }) + ]); + return result || []; + } else { + const result = await commandPromise; + return result || []; + } + } catch (error: any) { + if (error.message === 'Operation cancelled') { + return []; + } + return []; + } + } + + /** + * Get external libraries metadata for the project (top-level/direct dependencies only) + * @param workspaceFolderUri The URI of the workspace folder + * @param cancellationToken Optional cancellation token to abort the operation + * @returns Array of DependencyMetadata objects containing name-value pairs + */ + export async function getExternalLibrariesMetadata(workspaceFolderUri: Uri, cancellationToken?: CancellationToken): Promise> { + if (cancellationToken?.isCancellationRequested) { + return []; + } + + try { + const metadata: Array<{name: string, value: string}> = []; + + // Step 0: Get all projects in the workspace folder + const projects = await commands.executeCommand( + "java.execute.workspaceCommand", + "java.project.list", + workspaceFolderUri.toString() + ) as INodeData[]; + + + if (!projects || projects.length === 0) { + return []; + } + + // Process the first project (or you can process all projects) + const project = projects[0]; + const projectUri = project.uri; + + if (!projectUri) { + return []; + } + + + if (cancellationToken?.isCancellationRequested) { + return []; + } + + // Step 1: Get project's children to find containers + const projectChildren = await commands.executeCommand( + "java.execute.workspaceCommand", + "java.getPackageData", + { + kind: NodeKind.Project, + projectUri: projectUri + } + ) as INodeData[]; + + + if (cancellationToken?.isCancellationRequested) { + return []; + } + + // Step 2: Find container nodes (Maven Dependencies, JRE System Library, Referenced Libraries) + const containers = projectChildren?.filter(node => node.kind === NodeKind.Container) || []; + + // Also check for PackageRoot nodes directly in project children (these are top-level dependencies) + const directPackageRoots = projectChildren?.filter(node => node.kind === NodeKind.PackageRoot) || []; + + // Process direct package roots first + for (const pkgRoot of directPackageRoots) { + if (cancellationToken?.isCancellationRequested) { + return metadata; + } + + const jarName = pkgRoot.name || pkgRoot.displayName || 'unknown'; + const jarPath = pkgRoot.path || ''; + + // Add dependency trait + metadata.push({ + name: `dependency:${jarName}`, + value: jarPath + }); + + // Add metadata if available + if (pkgRoot.metaData) { + const groupId = pkgRoot.metaData['maven.groupId']; + const artifactId = pkgRoot.metaData['maven.artifactId']; + const version = pkgRoot.metaData['maven.version']; + + if (groupId && artifactId && version) { + metadata.push({ + name: `dependency.coordinates:${jarName}`, + value: `${groupId}:${artifactId}:${version}` + }); + } + } + } + + // Step 3: For containers, only get the top-level (direct) dependencies + for (const container of containers) { + if (cancellationToken?.isCancellationRequested) { + return metadata; + } + + + // Only process Maven Dependencies and Gradle Dependencies containers for direct dependencies + const containerName = container.name?.toLowerCase() || ''; + const isRelevantContainer = containerName.includes('maven') || + containerName.includes('gradle') || + containerName.includes('referenced'); + + if (!isRelevantContainer) { + continue; + } + + const packageRoots = await commands.executeCommand( + "java.execute.workspaceCommand", + "java.getPackageData", + { + kind: NodeKind.Container, + projectUri: projectUri, + path: container.path + } + ) as INodeData[]; + + + if (cancellationToken?.isCancellationRequested) { + return metadata; + } + + // Process each top-level package root (these are direct dependencies) + for (const pkgRoot of packageRoots || []) { + if (pkgRoot.kind === NodeKind.PackageRoot) { + const jarName = pkgRoot.name || pkgRoot.displayName || 'unknown'; + const jarPath = pkgRoot.path || ''; + + + // Add dependency trait + metadata.push({ + name: `dependency:${jarName}`, + value: jarPath + }); + + // Add metadata if available + if (pkgRoot.metaData) { + const groupId = pkgRoot.metaData['maven.groupId']; + const artifactId = pkgRoot.metaData['maven.artifactId']; + const version = pkgRoot.metaData['maven.version']; + + if (groupId && artifactId && version) { + metadata.push({ + name: `dependency.coordinates:${jarName}`, + value: `${groupId}:${artifactId}:${version}` + }); + } + + // Add type + if (containerName.includes('maven')) { + metadata.push({ + name: `dependency.type:${jarName}`, + value: 'maven' + }); + } else if (containerName.includes('gradle')) { + metadata.push({ + name: `dependency.type:${jarName}`, + value: 'gradle' + }); + } + } + } + } + } + + return metadata; + + } catch (error: any) { + if (error.message === 'Operation cancelled') { + return []; + } + console.error('[getExternalLibrariesMetadata] Error getting external libraries metadata:', error); + return []; + } + } +} diff --git a/src/copilot/utils.ts b/src/copilot/utils.ts new file mode 100644 index 00000000..ff8dfd89 --- /dev/null +++ b/src/copilot/utils.ts @@ -0,0 +1,154 @@ +import * as vscode from 'vscode'; +import { + ContextProviderApiV1, + ResolveRequest, + SupportedContextItem, + type ContextProvider, +} from '@github/copilot-language-server'; + +/** + * Error classes for Copilot context provider cancellation handling + */ +export class CancellationError extends Error { + static readonly Canceled = "Canceled"; + constructor() { + super(CancellationError.Canceled); + this.name = this.message; + } +} + +export class InternalCancellationError extends CancellationError { +} + +export class CopilotCancellationError extends CancellationError { +} + +/** + * Type definitions for common patterns + */ +export type ContextResolverFunction = (request: ResolveRequest, token: vscode.CancellationToken) => Promise; + +export interface CopilotApiWrapper { + clientApi?: CopilotApi; + chatApi?: CopilotApi; +} + +export interface CopilotApi { + getContextProviderAPI(version: string): Promise; +} + +/** + * Utility class for handling common operations in Java Context Provider + */ +export class JavaContextProviderUtils { + /** + * Check if operation should be cancelled and throw appropriate error + */ + static checkCancellation(token: vscode.CancellationToken): void { + if (token.isCancellationRequested) { + throw new CopilotCancellationError(); + } + } + + /** + * Create context items from import classes + */ + static createContextItemsFromImports(importClasses: any[]): SupportedContextItem[] { + return importClasses.map((cls: any) => ({ + uri: cls.uri, + value: cls.className, + importance: 70, + origin: 'request' as const + })); + } + + /** + * Create a basic Java version context item + */ + static createJavaVersionItem(javaVersion: string): SupportedContextItem { + return { + name: 'java.version', + value: javaVersion, + importance: 90, + id: 'java-version', + origin: 'request' + }; + } + + + /** + * Get and validate Copilot APIs + */ + static async getCopilotApis(): Promise { + const copilotClientApi = await getCopilotClientApi(); + const copilotChatApi = await getCopilotChatApi(); + return { clientApi: copilotClientApi, chatApi: copilotChatApi }; + } + + /** + * Install context provider on available APIs + */ + static async installContextProviderOnApis( + apis: CopilotApiWrapper, + provider: ContextProvider, + context: vscode.ExtensionContext, + installFn: (api: CopilotApi, provider: ContextProvider) => Promise + ): Promise { + let installCount = 0; + + if (apis.clientApi) { + const disposable = await installFn(apis.clientApi, provider); + if (disposable) { + context.subscriptions.push(disposable); + installCount++; + } + } + + if (apis.chatApi) { + const disposable = await installFn(apis.chatApi, provider); + if (disposable) { + context.subscriptions.push(disposable); + installCount++; + } + } + + return installCount; + } +} + +/** + * Get Copilot client API + */ +export async function getCopilotClientApi(): Promise { + const extension = vscode.extensions.getExtension('github.copilot'); + if (!extension) { + return undefined; + } + try { + return await extension.activate(); + } catch { + return undefined; + } +} + +/** + * Get Copilot chat API + */ +export async function getCopilotChatApi(): Promise { + type CopilotChatApi = { getAPI?(version: number): CopilotApi | undefined }; + const extension = vscode.extensions.getExtension('github.copilot-chat'); + if (!extension) { + return undefined; + } + + let exports: CopilotChatApi | undefined; + try { + exports = await extension.activate(); + } catch { + return undefined; + } + if (!exports || typeof exports.getAPI !== 'function') { + return undefined; + } + return exports.getAPI(1); +} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index d2fe424b..af169c32 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -21,6 +21,7 @@ import { setContextForDeprecatedTasks, updateExportTaskType } from "./tasks/buil import { CodeActionProvider } from "./tasks/buildArtifact/migration/CodeActionProvider"; import { newJavaFile } from "./explorerCommands/new"; import upgradeManager from "./upgrade/upgradeManager"; +import { registerCopilotContextProviders } from "./copilot/contextProvider"; export async function activate(context: ExtensionContext): Promise { contextManager.initialize(context); @@ -37,6 +38,7 @@ export async function activate(context: ExtensionContext): Promise { } }); contextManager.setContextValue(Context.EXTENSION_ACTIVATED, true); + await registerCopilotContextProviders(context); } async function activateExtension(_operationId: string, context: ExtensionContext): Promise { From b81c92f8da22b4470a77e9667392167c014e3689 Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Tue, 21 Oct 2025 13:25:50 +0800 Subject: [PATCH 2/9] feat: add project java deps --- .../com.microsoft.jdtls.ext.core/plugin.xml | 1 + .../jdtls/ext/core/CommandHandler.java | 2 + .../jdtls/ext/core/ProjectCommand.java | 153 ++++++++++++++++++ package.json | 11 ++ src/commands.ts | 4 + src/extension.ts | 128 +++++++++++++++ src/java/jdtls.ts | 9 ++ 7 files changed, 308 insertions(+) diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/plugin.xml b/jdtls.ext/com.microsoft.jdtls.ext.core/plugin.xml index 6643c1a7..a469ec92 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/plugin.xml +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/plugin.xml @@ -11,6 +11,7 @@ + arguments, IProgress return ProjectCommand.checkImportStatus(); case "java.project.getImportClassContent": return ProjectCommand.getImportClassContent(arguments, monitor); + case "java.project.getDependencies": + return ProjectCommand.getProjectDependencies(arguments, monitor); default: break; } diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java index 257c908e..0e97f91e 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java @@ -44,6 +44,7 @@ import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Path; +import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMethod; @@ -87,7 +88,15 @@ public MainClassInfo(String name, String path) { } } + private static class DependencyInfo { + public String key; + public String value; + public DependencyInfo(String key, String value) { + this.key = key; + this.value = value; + } + } private static class Classpath { public String source; @@ -491,6 +500,150 @@ private static String getSeverityString(int severity) { } } + /** + * Get project dependencies information including JDK version. + * + * @param arguments List containing the project URI as the first element + * @param monitor Progress monitor for cancellation support + * @return List of DependencyInfo containing key-value pairs of project information + */ + public static List getProjectDependencies(List arguments, IProgressMonitor monitor) { + List result = new ArrayList<>(); + + if (arguments == null || arguments.isEmpty()) { + return result; + } + + try { + String projectUri = (String) arguments.get(0); + IPath projectPath = ResourceUtils.canonicalFilePathFromURI(projectUri); + + // Find the project + IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); + IProject project = null; + + for (IProject p : ProjectUtils.getAllProjects()) { + if (p.getLocation() != null && p.getLocation().equals(projectPath)) { + project = p; + break; + } + } + + if (project == null || !project.isAccessible()) { + return result; + } + + IJavaProject javaProject = JavaCore.create(project); + if (javaProject == null || !javaProject.exists()) { + return result; + } + + // Add project name + result.add(new DependencyInfo("projectName", project.getName())); + + // Add project location + if (project.getLocation() != null) { + result.add(new DependencyInfo("projectLocation", project.getLocation().toOSString())); + } + + // Add JDK version + String javaVersion = javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true); + if (javaVersion != null) { + result.add(new DependencyInfo("javaVersion", javaVersion)); + } + + // Add source compatibility + String sourceCompliance = javaProject.getOption(JavaCore.COMPILER_SOURCE, true); + if (sourceCompliance != null) { + result.add(new DependencyInfo("sourceCompatibility", sourceCompliance)); + } + + // Add target compatibility + String targetCompliance = javaProject.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true); + if (targetCompliance != null) { + result.add(new DependencyInfo("targetCompatibility", targetCompliance)); + } + + // Add module name if it's a modular project + String moduleName = getModuleName(javaProject); + if (moduleName != null) { + result.add(new DependencyInfo("moduleName", moduleName)); + } + + // Get classpath entries (dependencies) + try { + IClasspathEntry[] classpathEntries = javaProject.getResolvedClasspath(true); + int libCount = 0; + int projectRefCount = 0; + + for (IClasspathEntry entry : classpathEntries) { + if (monitor.isCanceled()) { + break; + } + + switch (entry.getEntryKind()) { + case IClasspathEntry.CPE_LIBRARY: + libCount++; + IPath libPath = entry.getPath(); + if (libPath != null) { + String libName = libPath.lastSegment(); + result.add(new DependencyInfo("library_" + libCount, + libName + " (" + libPath.toOSString() + ")")); + } + break; + case IClasspathEntry.CPE_PROJECT: + projectRefCount++; + IPath projectRefPath = entry.getPath(); + if (projectRefPath != null) { + result.add(new DependencyInfo("projectReference_" + projectRefCount, + projectRefPath.lastSegment())); + } + break; + case IClasspathEntry.CPE_CONTAINER: + String containerPath = entry.getPath().toString(); + if (containerPath.contains("JRE_CONTAINER")) { + result.add(new DependencyInfo("jreContainerPath", containerPath)); + // Try to extract JRE name from container path + try { + IPath containerIPath = entry.getPath(); + String vmInstallName = JavaRuntime.getVMInstallName(containerIPath); + if (vmInstallName != null) { + result.add(new DependencyInfo("jreContainer", vmInstallName)); + } + } catch (Exception e) { + // Ignore if unable to get VM install name + } + } else if (containerPath.contains("MAVEN")) { + result.add(new DependencyInfo("buildTool", "Maven")); + } else if (containerPath.contains("GRADLE")) { + result.add(new DependencyInfo("buildTool", "Gradle")); + } + break; + } + } + + // Add summary counts + result.add(new DependencyInfo("totalLibraries", String.valueOf(libCount))); + result.add(new DependencyInfo("totalProjectReferences", String.valueOf(projectRefCount))); + + } catch (JavaModelException e) { + JdtlsExtActivator.logException("Error getting classpath entries", e); + } + + // Add build tool info by checking for build files + if (project.getFile("pom.xml").exists()) { + result.add(new DependencyInfo("buildTool", "Maven")); + } else if (project.getFile("build.gradle").exists() || project.getFile("build.gradle.kts").exists()) { + result.add(new DependencyInfo("buildTool", "Gradle")); + } + + } catch (Exception e) { + JdtlsExtActivator.logException("Error in getProjectDependencies", e); + } + + return result; + } + private static final class LinkedFolderVisitor implements IResourceVisitor { private boolean belongsToWorkspace; diff --git a/package.json b/package.json index cb859508..e2f549cf 100644 --- a/package.json +++ b/package.json @@ -298,6 +298,12 @@ "command": "_java.upgradeWithCopilot", "title": "%contributes.commands.java.upgradeWithCopilot%", "category": "Java" + }, + { + "command": "java.project.showDependencies", + "title": "Show Project Dependencies", + "category": "Java", + "icon": "$(info)" } ], "configuration": { @@ -739,6 +745,11 @@ "when": "view == javaProjectExplorer && viewItem =~ /java:project(?=.*?\\b\\+java\\b)(?=.*?\\b\\+uri\\b)/", "group": "8_execution@6" }, + { + "command": "java.project.showDependencies", + "when": "view == javaProjectExplorer && viewItem =~ /java:(project|workspace)(?=.*?\\b\\+uri\\b)/", + "group": "8_execution@7" + }, { "submenu": "javaProject.new", "when": "view == javaProjectExplorer && viewItem =~ /java(?!:container)(?!:jar)(?!.*?\\b\\+binary\\b)(?=.*?\\b\\+uri\\b)/", diff --git a/src/commands.ts b/src/commands.ts index f3887ef7..703239ba 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -134,6 +134,10 @@ export namespace Commands { export const JAVA_PROJECT_CHECK_IMPORT_STATUS = "java.project.checkImportStatus"; + export const JAVA_PROJECT_GET_DEPENDENCIES = "java.project.getDependencies"; + + export const JAVA_PROJECT_SHOW_DEPENDENCIES = "java.project.showDependencies"; + export const JAVA_UPGRADE_WITH_COPILOT = "_java.upgradeWithCopilot"; /** diff --git a/src/extension.ts b/src/extension.ts index af169c32..f7d6174d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -22,6 +22,7 @@ import { CodeActionProvider } from "./tasks/buildArtifact/migration/CodeActionPr import { newJavaFile } from "./explorerCommands/new"; import upgradeManager from "./upgrade/upgradeManager"; import { registerCopilotContextProviders } from "./copilot/contextProvider"; +import { Jdtls, IDependencyInfo } from "./java/jdtls"; export async function activate(context: ExtensionContext): Promise { contextManager.initialize(context); @@ -85,6 +86,133 @@ async function activateExtension(_operationId: string, context: ExtensionContext } )); setContextForDeprecatedTasks(); + + // Register command to show project dependencies + context.subscriptions.push(instrumentOperationAsVsCodeCommand( + Commands.JAVA_PROJECT_SHOW_DEPENDENCIES, async (uri?: Uri) => { + try { + let projectUri: string; + + if (uri) { + // If URI is provided, use it + projectUri = uri.toString(); + } else { + // Otherwise, use the first workspace folder + const workspaceFolders = workspace.workspaceFolders; + if (!workspaceFolders || workspaceFolders.length === 0) { + window.showErrorMessage("No workspace folder found. Please open a Java project."); + return; + } + projectUri = workspaceFolders[0].uri.toString(); + } + + // Call the Java command to get dependencies + const start = performance.now(); + const dependencies: IDependencyInfo[] = await Jdtls.getProjectDependencies(projectUri); + const end = performance.now(); + + if (!dependencies || dependencies.length === 0) { + window.showInformationMessage("No dependency information found for this project."); + return; + } + + // Create output channel to display results + const outputChannel = window.createOutputChannel("Java Project Dependencies"); + outputChannel.clear(); + outputChannel.appendLine("=".repeat(80)); + outputChannel.appendLine("Java Project Dependencies Information"); + outputChannel.appendLine("=".repeat(80)); + outputChannel.appendLine(`Time span: ${end - start}ms`); + + // Group dependencies by category + const basicInfo: IDependencyInfo[] = []; + const javaInfo: IDependencyInfo[] = []; + const libraries: IDependencyInfo[] = []; + const projectRefs: IDependencyInfo[] = []; + const others: IDependencyInfo[] = []; + + for (const dep of dependencies) { + if (dep.key === "projectName" || dep.key === "projectLocation") { + basicInfo.push(dep); + } else if (dep.key.includes("java") || dep.key.includes("jre") || + dep.key.includes("Compatibility") || dep.key === "buildTool" || + dep.key === "moduleName") { + javaInfo.push(dep); + } else if (dep.key.startsWith("library_")) { + libraries.push(dep); + } else if (dep.key.startsWith("projectReference_")) { + projectRefs.push(dep); + } else { + others.push(dep); + } + } + + // Display basic information + if (basicInfo.length > 0) { + outputChannel.appendLine("📦 Basic Information:"); + outputChannel.appendLine("-".repeat(80)); + for (const dep of basicInfo) { + outputChannel.appendLine(` ${dep.key}: ${dep.value}`); + } + outputChannel.appendLine(""); + } + + // Display Java/JDK information + if (javaInfo.length > 0) { + outputChannel.appendLine("☕ Java/JDK Information:"); + outputChannel.appendLine("-".repeat(80)); + for (const dep of javaInfo) { + outputChannel.appendLine(` ${dep.key}: ${dep.value}`); + } + outputChannel.appendLine(""); + } + + // Display libraries + if (libraries.length > 0) { + outputChannel.appendLine("📚 Dependencies Libraries:"); + outputChannel.appendLine("-".repeat(80)); + for (const dep of libraries) { + outputChannel.appendLine(` ${dep.value}`); + } + outputChannel.appendLine(""); + } + + // Display project references + if (projectRefs.length > 0) { + outputChannel.appendLine("🔗 Project References:"); + outputChannel.appendLine("-".repeat(80)); + for (const dep of projectRefs) { + outputChannel.appendLine(` ${dep.value}`); + } + outputChannel.appendLine(""); + } + + // Display other information + if (others.length > 0) { + outputChannel.appendLine("ℹ️ Other Information:"); + outputChannel.appendLine("-".repeat(80)); + for (const dep of others) { + outputChannel.appendLine(` ${dep.key}: ${dep.value}`); + } + outputChannel.appendLine(""); + } + + outputChannel.appendLine("=".repeat(80)); + outputChannel.appendLine(`Total entries: ${dependencies.length}`); + outputChannel.appendLine("=".repeat(80)); + + // Show the output channel + outputChannel.show(); + + window.showInformationMessage( + `Successfully retrieved ${dependencies.length} dependency entries. Check the Output panel.` + ); + + } catch (error) { + window.showErrorMessage(`Failed to get project dependencies: ${error}`); + } + } + )); } // this method is called when your extension is deactivated diff --git a/src/java/jdtls.ts b/src/java/jdtls.ts index c1388253..84a372a0 100644 --- a/src/java/jdtls.ts +++ b/src/java/jdtls.ts @@ -82,6 +82,10 @@ export namespace Jdtls { return commands.executeCommand(Commands.EXECUTE_WORKSPACE_COMMAND, Commands.JAVA_PROJECT_CHECK_IMPORT_STATUS) || false; } + export async function getProjectDependencies(projectUri: string): Promise { + return await commands.executeCommand(Commands.EXECUTE_WORKSPACE_COMMAND, Commands.JAVA_PROJECT_GET_DEPENDENCIES, projectUri) || []; + } + export enum CompileWorkspaceStatus { Failed = 0, Succeed = 1, @@ -97,4 +101,9 @@ export namespace Jdtls { interface IPackageDataParam { projectUri: string | undefined; [key: string]: any; +} + +export interface IDependencyInfo { + key: string; + value: string; } \ No newline at end of file From 63d10286be4db7e0041e40fb4fcea7029c4ed87c Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Mon, 20 Oct 2025 21:37:59 +0800 Subject: [PATCH 3/9] feat: release for manager From 32193107ce65199d318da40d6ee5362961bd0d27 Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Tue, 21 Oct 2025 14:46:29 +0800 Subject: [PATCH 4/9] feat: add get deps --- .../ext/core/parser/ContextResolver.java | 123 +++++++++++++++++ package.json | 11 -- src/commands.ts | 2 - src/extension.ts | 128 ------------------ 4 files changed, 123 insertions(+), 141 deletions(-) diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ContextResolver.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ContextResolver.java index 3762626c..d09f95d0 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ContextResolver.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ContextResolver.java @@ -43,6 +43,111 @@ public class ContextResolver { private static final int MAX_STATIC_METHODS_TO_DISPLAY = 10; private static final int MAX_STATIC_FIELDS_TO_DISPLAY = 10; + // Common JDK types to skip (Copilot already has good understanding of these) + // These are well-known classes that don't need to be extracted from JARs + private static final Set SKIP_COMMON_JDK_TYPES = new HashSet<>(); + static { + // java.lang - fundamental types + SKIP_COMMON_JDK_TYPES.add("java.lang.Object"); + SKIP_COMMON_JDK_TYPES.add("java.lang.String"); + SKIP_COMMON_JDK_TYPES.add("java.lang.Integer"); + SKIP_COMMON_JDK_TYPES.add("java.lang.Long"); + SKIP_COMMON_JDK_TYPES.add("java.lang.Double"); + SKIP_COMMON_JDK_TYPES.add("java.lang.Float"); + SKIP_COMMON_JDK_TYPES.add("java.lang.Boolean"); + SKIP_COMMON_JDK_TYPES.add("java.lang.Character"); + SKIP_COMMON_JDK_TYPES.add("java.lang.Byte"); + SKIP_COMMON_JDK_TYPES.add("java.lang.Short"); + SKIP_COMMON_JDK_TYPES.add("java.lang.Number"); + SKIP_COMMON_JDK_TYPES.add("java.lang.Void"); + SKIP_COMMON_JDK_TYPES.add("java.lang.Class"); + SKIP_COMMON_JDK_TYPES.add("java.lang.System"); + SKIP_COMMON_JDK_TYPES.add("java.lang.Math"); + SKIP_COMMON_JDK_TYPES.add("java.lang.Thread"); + SKIP_COMMON_JDK_TYPES.add("java.lang.Runnable"); + SKIP_COMMON_JDK_TYPES.add("java.lang.Exception"); + SKIP_COMMON_JDK_TYPES.add("java.lang.RuntimeException"); + SKIP_COMMON_JDK_TYPES.add("java.lang.Throwable"); + SKIP_COMMON_JDK_TYPES.add("java.lang.Error"); + + // java.util - collections + SKIP_COMMON_JDK_TYPES.add("java.util.List"); + SKIP_COMMON_JDK_TYPES.add("java.util.ArrayList"); + SKIP_COMMON_JDK_TYPES.add("java.util.LinkedList"); + SKIP_COMMON_JDK_TYPES.add("java.util.Map"); + SKIP_COMMON_JDK_TYPES.add("java.util.HashMap"); + SKIP_COMMON_JDK_TYPES.add("java.util.LinkedHashMap"); + SKIP_COMMON_JDK_TYPES.add("java.util.TreeMap"); + SKIP_COMMON_JDK_TYPES.add("java.util.Set"); + SKIP_COMMON_JDK_TYPES.add("java.util.HashSet"); + SKIP_COMMON_JDK_TYPES.add("java.util.LinkedHashSet"); + SKIP_COMMON_JDK_TYPES.add("java.util.TreeSet"); + SKIP_COMMON_JDK_TYPES.add("java.util.Collection"); + SKIP_COMMON_JDK_TYPES.add("java.util.Iterator"); + SKIP_COMMON_JDK_TYPES.add("java.util.Queue"); + SKIP_COMMON_JDK_TYPES.add("java.util.Deque"); + SKIP_COMMON_JDK_TYPES.add("java.util.Stack"); + SKIP_COMMON_JDK_TYPES.add("java.util.Vector"); + SKIP_COMMON_JDK_TYPES.add("java.util.Optional"); + SKIP_COMMON_JDK_TYPES.add("java.util.Arrays"); + SKIP_COMMON_JDK_TYPES.add("java.util.Collections"); + SKIP_COMMON_JDK_TYPES.add("java.util.Comparator"); + + // java.io - I/O + SKIP_COMMON_JDK_TYPES.add("java.io.File"); + SKIP_COMMON_JDK_TYPES.add("java.io.IOException"); + SKIP_COMMON_JDK_TYPES.add("java.io.InputStream"); + SKIP_COMMON_JDK_TYPES.add("java.io.OutputStream"); + SKIP_COMMON_JDK_TYPES.add("java.io.Reader"); + SKIP_COMMON_JDK_TYPES.add("java.io.Writer"); + SKIP_COMMON_JDK_TYPES.add("java.io.BufferedReader"); + SKIP_COMMON_JDK_TYPES.add("java.io.BufferedWriter"); + SKIP_COMMON_JDK_TYPES.add("java.io.FileReader"); + SKIP_COMMON_JDK_TYPES.add("java.io.FileWriter"); + SKIP_COMMON_JDK_TYPES.add("java.io.PrintWriter"); + SKIP_COMMON_JDK_TYPES.add("java.io.Serializable"); + + // java.nio - NIO + SKIP_COMMON_JDK_TYPES.add("java.nio.file.Path"); + SKIP_COMMON_JDK_TYPES.add("java.nio.file.Paths"); + SKIP_COMMON_JDK_TYPES.add("java.nio.file.Files"); + SKIP_COMMON_JDK_TYPES.add("java.nio.ByteBuffer"); + + // java.time - Date/Time API + SKIP_COMMON_JDK_TYPES.add("java.time.LocalDate"); + SKIP_COMMON_JDK_TYPES.add("java.time.LocalDateTime"); + SKIP_COMMON_JDK_TYPES.add("java.time.LocalTime"); + SKIP_COMMON_JDK_TYPES.add("java.time.Instant"); + SKIP_COMMON_JDK_TYPES.add("java.time.Duration"); + SKIP_COMMON_JDK_TYPES.add("java.time.Period"); + SKIP_COMMON_JDK_TYPES.add("java.time.ZonedDateTime"); + + // java.util.concurrent - Concurrency + SKIP_COMMON_JDK_TYPES.add("java.util.concurrent.ExecutorService"); + SKIP_COMMON_JDK_TYPES.add("java.util.concurrent.Future"); + SKIP_COMMON_JDK_TYPES.add("java.util.concurrent.CompletableFuture"); + SKIP_COMMON_JDK_TYPES.add("java.util.concurrent.ConcurrentHashMap"); + + // java.util.stream - Streams + SKIP_COMMON_JDK_TYPES.add("java.util.stream.Stream"); + SKIP_COMMON_JDK_TYPES.add("java.util.stream.Collectors"); + + // java.util.function - Functional interfaces + SKIP_COMMON_JDK_TYPES.add("java.util.function.Function"); + SKIP_COMMON_JDK_TYPES.add("java.util.function.Consumer"); + SKIP_COMMON_JDK_TYPES.add("java.util.function.Supplier"); + SKIP_COMMON_JDK_TYPES.add("java.util.function.Predicate"); + + // java.net - Networking + SKIP_COMMON_JDK_TYPES.add("java.net.URL"); + SKIP_COMMON_JDK_TYPES.add("java.net.URI"); + SKIP_COMMON_JDK_TYPES.add("java.net.HttpURLConnection"); + + // java.util.regex - Regular expressions + SKIP_COMMON_JDK_TYPES.add("java.util.regex.Pattern"); + SKIP_COMMON_JDK_TYPES.add("java.util.regex.Matcher"); + } + /** * ImportClassInfo - Conforms to Copilot CodeSnippet format * Used to provide Java class context information and JavaDoc to Copilot @@ -313,6 +418,13 @@ public static void resolveBinaryType(IJavaProject javaProject, String typeName, return; } + // Performance optimization: Skip common JDK types that Copilot already understands well + // This significantly reduces processing time for external dependencies + if (SKIP_COMMON_JDK_TYPES.contains(typeName)) { + processedTypes.add(typeName); + return; + } + // Use JDT's findType which searches all sources and dependencies org.eclipse.jdt.core.IType type = javaProject.findType(typeName); if (type == null || !type.exists()) { @@ -539,9 +651,20 @@ private static String generateClassSignature(org.eclipse.jdt.core.IType type) th /** * Extract brief JavaDoc summary for binary types (first sentence only) + * Performance optimization: Skip JavaDoc extraction for binary types to avoid expensive I/O */ private static String extractBriefJavaDoc(org.eclipse.jdt.core.IType type) { try { + // Performance optimization: Skip JavaDoc extraction for binary types + // getAttachedJavadoc() is very expensive - it may involve: + // - Reading from JAR files + // - Network downloads from Maven Central + // - Parsing large HTML documents + // For binary types, the method signatures alone provide sufficient context + if (type.isBinary()) { + return null; + } + String javadoc = type.getAttachedJavadoc(null); if (javadoc == null || javadoc.isEmpty()) { return null; diff --git a/package.json b/package.json index e2f549cf..cb859508 100644 --- a/package.json +++ b/package.json @@ -298,12 +298,6 @@ "command": "_java.upgradeWithCopilot", "title": "%contributes.commands.java.upgradeWithCopilot%", "category": "Java" - }, - { - "command": "java.project.showDependencies", - "title": "Show Project Dependencies", - "category": "Java", - "icon": "$(info)" } ], "configuration": { @@ -745,11 +739,6 @@ "when": "view == javaProjectExplorer && viewItem =~ /java:project(?=.*?\\b\\+java\\b)(?=.*?\\b\\+uri\\b)/", "group": "8_execution@6" }, - { - "command": "java.project.showDependencies", - "when": "view == javaProjectExplorer && viewItem =~ /java:(project|workspace)(?=.*?\\b\\+uri\\b)/", - "group": "8_execution@7" - }, { "submenu": "javaProject.new", "when": "view == javaProjectExplorer && viewItem =~ /java(?!:container)(?!:jar)(?!.*?\\b\\+binary\\b)(?=.*?\\b\\+uri\\b)/", diff --git a/src/commands.ts b/src/commands.ts index 703239ba..a7338c0a 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -136,8 +136,6 @@ export namespace Commands { export const JAVA_PROJECT_GET_DEPENDENCIES = "java.project.getDependencies"; - export const JAVA_PROJECT_SHOW_DEPENDENCIES = "java.project.showDependencies"; - export const JAVA_UPGRADE_WITH_COPILOT = "_java.upgradeWithCopilot"; /** diff --git a/src/extension.ts b/src/extension.ts index f7d6174d..af169c32 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -22,7 +22,6 @@ import { CodeActionProvider } from "./tasks/buildArtifact/migration/CodeActionPr import { newJavaFile } from "./explorerCommands/new"; import upgradeManager from "./upgrade/upgradeManager"; import { registerCopilotContextProviders } from "./copilot/contextProvider"; -import { Jdtls, IDependencyInfo } from "./java/jdtls"; export async function activate(context: ExtensionContext): Promise { contextManager.initialize(context); @@ -86,133 +85,6 @@ async function activateExtension(_operationId: string, context: ExtensionContext } )); setContextForDeprecatedTasks(); - - // Register command to show project dependencies - context.subscriptions.push(instrumentOperationAsVsCodeCommand( - Commands.JAVA_PROJECT_SHOW_DEPENDENCIES, async (uri?: Uri) => { - try { - let projectUri: string; - - if (uri) { - // If URI is provided, use it - projectUri = uri.toString(); - } else { - // Otherwise, use the first workspace folder - const workspaceFolders = workspace.workspaceFolders; - if (!workspaceFolders || workspaceFolders.length === 0) { - window.showErrorMessage("No workspace folder found. Please open a Java project."); - return; - } - projectUri = workspaceFolders[0].uri.toString(); - } - - // Call the Java command to get dependencies - const start = performance.now(); - const dependencies: IDependencyInfo[] = await Jdtls.getProjectDependencies(projectUri); - const end = performance.now(); - - if (!dependencies || dependencies.length === 0) { - window.showInformationMessage("No dependency information found for this project."); - return; - } - - // Create output channel to display results - const outputChannel = window.createOutputChannel("Java Project Dependencies"); - outputChannel.clear(); - outputChannel.appendLine("=".repeat(80)); - outputChannel.appendLine("Java Project Dependencies Information"); - outputChannel.appendLine("=".repeat(80)); - outputChannel.appendLine(`Time span: ${end - start}ms`); - - // Group dependencies by category - const basicInfo: IDependencyInfo[] = []; - const javaInfo: IDependencyInfo[] = []; - const libraries: IDependencyInfo[] = []; - const projectRefs: IDependencyInfo[] = []; - const others: IDependencyInfo[] = []; - - for (const dep of dependencies) { - if (dep.key === "projectName" || dep.key === "projectLocation") { - basicInfo.push(dep); - } else if (dep.key.includes("java") || dep.key.includes("jre") || - dep.key.includes("Compatibility") || dep.key === "buildTool" || - dep.key === "moduleName") { - javaInfo.push(dep); - } else if (dep.key.startsWith("library_")) { - libraries.push(dep); - } else if (dep.key.startsWith("projectReference_")) { - projectRefs.push(dep); - } else { - others.push(dep); - } - } - - // Display basic information - if (basicInfo.length > 0) { - outputChannel.appendLine("📦 Basic Information:"); - outputChannel.appendLine("-".repeat(80)); - for (const dep of basicInfo) { - outputChannel.appendLine(` ${dep.key}: ${dep.value}`); - } - outputChannel.appendLine(""); - } - - // Display Java/JDK information - if (javaInfo.length > 0) { - outputChannel.appendLine("☕ Java/JDK Information:"); - outputChannel.appendLine("-".repeat(80)); - for (const dep of javaInfo) { - outputChannel.appendLine(` ${dep.key}: ${dep.value}`); - } - outputChannel.appendLine(""); - } - - // Display libraries - if (libraries.length > 0) { - outputChannel.appendLine("📚 Dependencies Libraries:"); - outputChannel.appendLine("-".repeat(80)); - for (const dep of libraries) { - outputChannel.appendLine(` ${dep.value}`); - } - outputChannel.appendLine(""); - } - - // Display project references - if (projectRefs.length > 0) { - outputChannel.appendLine("🔗 Project References:"); - outputChannel.appendLine("-".repeat(80)); - for (const dep of projectRefs) { - outputChannel.appendLine(` ${dep.value}`); - } - outputChannel.appendLine(""); - } - - // Display other information - if (others.length > 0) { - outputChannel.appendLine("ℹ️ Other Information:"); - outputChannel.appendLine("-".repeat(80)); - for (const dep of others) { - outputChannel.appendLine(` ${dep.key}: ${dep.value}`); - } - outputChannel.appendLine(""); - } - - outputChannel.appendLine("=".repeat(80)); - outputChannel.appendLine(`Total entries: ${dependencies.length}`); - outputChannel.appendLine("=".repeat(80)); - - // Show the output channel - outputChannel.show(); - - window.showInformationMessage( - `Successfully retrieved ${dependencies.length} dependency entries. Check the Output panel.` - ); - - } catch (error) { - window.showErrorMessage(`Failed to get project dependencies: ${error}`); - } - } - )); } // this method is called when your extension is deactivated From 0294a4008e7905e76bf1f8c7d24d923eaafc4c78 Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Thu, 23 Oct 2025 17:09:21 +0800 Subject: [PATCH 5/9] feat: adjust logic --- .../jdtls/ext/core/ProjectCommand.java | 138 +---------- .../ext/core/parser/ContextResolver.java | 151 ++++------- .../ext/core/parser/ProjectResolver.java | 234 ++++++++++++++++++ 3 files changed, 290 insertions(+), 233 deletions(-) create mode 100644 jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ProjectResolver.java diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java index 0e97f91e..0989225a 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java @@ -44,7 +44,6 @@ import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Path; -import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMethod; @@ -72,6 +71,7 @@ import com.google.gson.GsonBuilder; import com.microsoft.jdtls.ext.core.parser.ContextResolver; import com.microsoft.jdtls.ext.core.parser.ContextResolver.ImportClassInfo; +import com.microsoft.jdtls.ext.core.parser.ProjectResolver; import com.microsoft.jdtls.ext.core.model.PackageNode; public final class ProjectCommand { @@ -508,137 +508,17 @@ private static String getSeverityString(int severity) { * @return List of DependencyInfo containing key-value pairs of project information */ public static List getProjectDependencies(List arguments, IProgressMonitor monitor) { - List result = new ArrayList<>(); - if (arguments == null || arguments.isEmpty()) { - return result; + return new ArrayList<>(); } - try { - String projectUri = (String) arguments.get(0); - IPath projectPath = ResourceUtils.canonicalFilePathFromURI(projectUri); - - // Find the project - IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); - IProject project = null; - - for (IProject p : ProjectUtils.getAllProjects()) { - if (p.getLocation() != null && p.getLocation().equals(projectPath)) { - project = p; - break; - } - } - - if (project == null || !project.isAccessible()) { - return result; - } - - IJavaProject javaProject = JavaCore.create(project); - if (javaProject == null || !javaProject.exists()) { - return result; - } - - // Add project name - result.add(new DependencyInfo("projectName", project.getName())); - - // Add project location - if (project.getLocation() != null) { - result.add(new DependencyInfo("projectLocation", project.getLocation().toOSString())); - } - - // Add JDK version - String javaVersion = javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true); - if (javaVersion != null) { - result.add(new DependencyInfo("javaVersion", javaVersion)); - } - - // Add source compatibility - String sourceCompliance = javaProject.getOption(JavaCore.COMPILER_SOURCE, true); - if (sourceCompliance != null) { - result.add(new DependencyInfo("sourceCompatibility", sourceCompliance)); - } - - // Add target compatibility - String targetCompliance = javaProject.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true); - if (targetCompliance != null) { - result.add(new DependencyInfo("targetCompatibility", targetCompliance)); - } - - // Add module name if it's a modular project - String moduleName = getModuleName(javaProject); - if (moduleName != null) { - result.add(new DependencyInfo("moduleName", moduleName)); - } - - // Get classpath entries (dependencies) - try { - IClasspathEntry[] classpathEntries = javaProject.getResolvedClasspath(true); - int libCount = 0; - int projectRefCount = 0; - - for (IClasspathEntry entry : classpathEntries) { - if (monitor.isCanceled()) { - break; - } - - switch (entry.getEntryKind()) { - case IClasspathEntry.CPE_LIBRARY: - libCount++; - IPath libPath = entry.getPath(); - if (libPath != null) { - String libName = libPath.lastSegment(); - result.add(new DependencyInfo("library_" + libCount, - libName + " (" + libPath.toOSString() + ")")); - } - break; - case IClasspathEntry.CPE_PROJECT: - projectRefCount++; - IPath projectRefPath = entry.getPath(); - if (projectRefPath != null) { - result.add(new DependencyInfo("projectReference_" + projectRefCount, - projectRefPath.lastSegment())); - } - break; - case IClasspathEntry.CPE_CONTAINER: - String containerPath = entry.getPath().toString(); - if (containerPath.contains("JRE_CONTAINER")) { - result.add(new DependencyInfo("jreContainerPath", containerPath)); - // Try to extract JRE name from container path - try { - IPath containerIPath = entry.getPath(); - String vmInstallName = JavaRuntime.getVMInstallName(containerIPath); - if (vmInstallName != null) { - result.add(new DependencyInfo("jreContainer", vmInstallName)); - } - } catch (Exception e) { - // Ignore if unable to get VM install name - } - } else if (containerPath.contains("MAVEN")) { - result.add(new DependencyInfo("buildTool", "Maven")); - } else if (containerPath.contains("GRADLE")) { - result.add(new DependencyInfo("buildTool", "Gradle")); - } - break; - } - } - - // Add summary counts - result.add(new DependencyInfo("totalLibraries", String.valueOf(libCount))); - result.add(new DependencyInfo("totalProjectReferences", String.valueOf(projectRefCount))); - - } catch (JavaModelException e) { - JdtlsExtActivator.logException("Error getting classpath entries", e); - } - - // Add build tool info by checking for build files - if (project.getFile("pom.xml").exists()) { - result.add(new DependencyInfo("buildTool", "Maven")); - } else if (project.getFile("build.gradle").exists() || project.getFile("build.gradle.kts").exists()) { - result.add(new DependencyInfo("buildTool", "Gradle")); - } - - } catch (Exception e) { - JdtlsExtActivator.logException("Error in getProjectDependencies", e); + String projectUri = (String) arguments.get(0); + List resolverResult = ProjectResolver.resolveProjectDependencies(projectUri, monitor); + + // Convert ProjectResolver.DependencyInfo to ProjectCommand.DependencyInfo + List result = new ArrayList<>(); + for (ProjectResolver.DependencyInfo info : resolverResult) { + result.add(new DependencyInfo(info.key, info.value)); } return result; diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ContextResolver.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ContextResolver.java index d09f95d0..539f335a 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ContextResolver.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ContextResolver.java @@ -43,109 +43,26 @@ public class ContextResolver { private static final int MAX_STATIC_METHODS_TO_DISPLAY = 10; private static final int MAX_STATIC_FIELDS_TO_DISPLAY = 10; - // Common JDK types to skip (Copilot already has good understanding of these) - // These are well-known classes that don't need to be extracted from JARs - private static final Set SKIP_COMMON_JDK_TYPES = new HashSet<>(); + // Common JDK packages to skip (Copilot already has good understanding of these) + // These are well-known packages whose classes don't need to be extracted from JARs + private static final Set SKIP_COMMON_JDK_PACKAGES = new HashSet<>(); static { - // java.lang - fundamental types - SKIP_COMMON_JDK_TYPES.add("java.lang.Object"); - SKIP_COMMON_JDK_TYPES.add("java.lang.String"); - SKIP_COMMON_JDK_TYPES.add("java.lang.Integer"); - SKIP_COMMON_JDK_TYPES.add("java.lang.Long"); - SKIP_COMMON_JDK_TYPES.add("java.lang.Double"); - SKIP_COMMON_JDK_TYPES.add("java.lang.Float"); - SKIP_COMMON_JDK_TYPES.add("java.lang.Boolean"); - SKIP_COMMON_JDK_TYPES.add("java.lang.Character"); - SKIP_COMMON_JDK_TYPES.add("java.lang.Byte"); - SKIP_COMMON_JDK_TYPES.add("java.lang.Short"); - SKIP_COMMON_JDK_TYPES.add("java.lang.Number"); - SKIP_COMMON_JDK_TYPES.add("java.lang.Void"); - SKIP_COMMON_JDK_TYPES.add("java.lang.Class"); - SKIP_COMMON_JDK_TYPES.add("java.lang.System"); - SKIP_COMMON_JDK_TYPES.add("java.lang.Math"); - SKIP_COMMON_JDK_TYPES.add("java.lang.Thread"); - SKIP_COMMON_JDK_TYPES.add("java.lang.Runnable"); - SKIP_COMMON_JDK_TYPES.add("java.lang.Exception"); - SKIP_COMMON_JDK_TYPES.add("java.lang.RuntimeException"); - SKIP_COMMON_JDK_TYPES.add("java.lang.Throwable"); - SKIP_COMMON_JDK_TYPES.add("java.lang.Error"); - - // java.util - collections - SKIP_COMMON_JDK_TYPES.add("java.util.List"); - SKIP_COMMON_JDK_TYPES.add("java.util.ArrayList"); - SKIP_COMMON_JDK_TYPES.add("java.util.LinkedList"); - SKIP_COMMON_JDK_TYPES.add("java.util.Map"); - SKIP_COMMON_JDK_TYPES.add("java.util.HashMap"); - SKIP_COMMON_JDK_TYPES.add("java.util.LinkedHashMap"); - SKIP_COMMON_JDK_TYPES.add("java.util.TreeMap"); - SKIP_COMMON_JDK_TYPES.add("java.util.Set"); - SKIP_COMMON_JDK_TYPES.add("java.util.HashSet"); - SKIP_COMMON_JDK_TYPES.add("java.util.LinkedHashSet"); - SKIP_COMMON_JDK_TYPES.add("java.util.TreeSet"); - SKIP_COMMON_JDK_TYPES.add("java.util.Collection"); - SKIP_COMMON_JDK_TYPES.add("java.util.Iterator"); - SKIP_COMMON_JDK_TYPES.add("java.util.Queue"); - SKIP_COMMON_JDK_TYPES.add("java.util.Deque"); - SKIP_COMMON_JDK_TYPES.add("java.util.Stack"); - SKIP_COMMON_JDK_TYPES.add("java.util.Vector"); - SKIP_COMMON_JDK_TYPES.add("java.util.Optional"); - SKIP_COMMON_JDK_TYPES.add("java.util.Arrays"); - SKIP_COMMON_JDK_TYPES.add("java.util.Collections"); - SKIP_COMMON_JDK_TYPES.add("java.util.Comparator"); - - // java.io - I/O - SKIP_COMMON_JDK_TYPES.add("java.io.File"); - SKIP_COMMON_JDK_TYPES.add("java.io.IOException"); - SKIP_COMMON_JDK_TYPES.add("java.io.InputStream"); - SKIP_COMMON_JDK_TYPES.add("java.io.OutputStream"); - SKIP_COMMON_JDK_TYPES.add("java.io.Reader"); - SKIP_COMMON_JDK_TYPES.add("java.io.Writer"); - SKIP_COMMON_JDK_TYPES.add("java.io.BufferedReader"); - SKIP_COMMON_JDK_TYPES.add("java.io.BufferedWriter"); - SKIP_COMMON_JDK_TYPES.add("java.io.FileReader"); - SKIP_COMMON_JDK_TYPES.add("java.io.FileWriter"); - SKIP_COMMON_JDK_TYPES.add("java.io.PrintWriter"); - SKIP_COMMON_JDK_TYPES.add("java.io.Serializable"); - - // java.nio - NIO - SKIP_COMMON_JDK_TYPES.add("java.nio.file.Path"); - SKIP_COMMON_JDK_TYPES.add("java.nio.file.Paths"); - SKIP_COMMON_JDK_TYPES.add("java.nio.file.Files"); - SKIP_COMMON_JDK_TYPES.add("java.nio.ByteBuffer"); - - // java.time - Date/Time API - SKIP_COMMON_JDK_TYPES.add("java.time.LocalDate"); - SKIP_COMMON_JDK_TYPES.add("java.time.LocalDateTime"); - SKIP_COMMON_JDK_TYPES.add("java.time.LocalTime"); - SKIP_COMMON_JDK_TYPES.add("java.time.Instant"); - SKIP_COMMON_JDK_TYPES.add("java.time.Duration"); - SKIP_COMMON_JDK_TYPES.add("java.time.Period"); - SKIP_COMMON_JDK_TYPES.add("java.time.ZonedDateTime"); - - // java.util.concurrent - Concurrency - SKIP_COMMON_JDK_TYPES.add("java.util.concurrent.ExecutorService"); - SKIP_COMMON_JDK_TYPES.add("java.util.concurrent.Future"); - SKIP_COMMON_JDK_TYPES.add("java.util.concurrent.CompletableFuture"); - SKIP_COMMON_JDK_TYPES.add("java.util.concurrent.ConcurrentHashMap"); - - // java.util.stream - Streams - SKIP_COMMON_JDK_TYPES.add("java.util.stream.Stream"); - SKIP_COMMON_JDK_TYPES.add("java.util.stream.Collectors"); - - // java.util.function - Functional interfaces - SKIP_COMMON_JDK_TYPES.add("java.util.function.Function"); - SKIP_COMMON_JDK_TYPES.add("java.util.function.Consumer"); - SKIP_COMMON_JDK_TYPES.add("java.util.function.Supplier"); - SKIP_COMMON_JDK_TYPES.add("java.util.function.Predicate"); - - // java.net - Networking - SKIP_COMMON_JDK_TYPES.add("java.net.URL"); - SKIP_COMMON_JDK_TYPES.add("java.net.URI"); - SKIP_COMMON_JDK_TYPES.add("java.net.HttpURLConnection"); - - // java.util.regex - Regular expressions - SKIP_COMMON_JDK_TYPES.add("java.util.regex.Pattern"); - SKIP_COMMON_JDK_TYPES.add("java.util.regex.Matcher"); + // Core Java packages - Copilot has excellent understanding of these + SKIP_COMMON_JDK_PACKAGES.add("java.lang"); // Object, String, Integer, etc. + SKIP_COMMON_JDK_PACKAGES.add("java.util"); // Collections, List, Map, Set, etc. + SKIP_COMMON_JDK_PACKAGES.add("java.io"); // File, InputStream, Reader, etc. + SKIP_COMMON_JDK_PACKAGES.add("java.nio"); // ByteBuffer, etc. + SKIP_COMMON_JDK_PACKAGES.add("java.nio.file"); // Path, Paths, Files + SKIP_COMMON_JDK_PACKAGES.add("java.time"); // LocalDate, LocalDateTime, Instant, etc. + SKIP_COMMON_JDK_PACKAGES.add("java.util.concurrent"); // ExecutorService, Future, CompletableFuture, etc. + SKIP_COMMON_JDK_PACKAGES.add("java.util.stream"); // Stream, Collectors + SKIP_COMMON_JDK_PACKAGES.add("java.util.function"); // Function, Consumer, Supplier, Predicate + SKIP_COMMON_JDK_PACKAGES.add("java.net"); // URL, URI, HttpURLConnection + SKIP_COMMON_JDK_PACKAGES.add("java.util.regex"); // Pattern, Matcher + SKIP_COMMON_JDK_PACKAGES.add("java.math"); // BigDecimal, BigInteger + SKIP_COMMON_JDK_PACKAGES.add("java.text"); // DateFormat, SimpleDateFormat, etc. + SKIP_COMMON_JDK_PACKAGES.add("java.sql"); // Connection, ResultSet, etc. + SKIP_COMMON_JDK_PACKAGES.add("javax.sql"); // DataSource, etc. } /** @@ -251,6 +168,32 @@ public static void resolveSingleType(IJavaProject javaProject, String typeName, } } + /** + * Check if a type belongs to a common JDK package that should be skipped. + * Uses package-level matching for efficient filtering. + * + * @param typeName Fully qualified type name (e.g., "java.lang.String") + * @return true if the type is from a common JDK package + */ + private static boolean isCommonJdkType(String typeName) { + if (typeName == null || typeName.isEmpty()) { + return false; + } + + // Extract package name from fully qualified type name + int lastDotIndex = typeName.lastIndexOf('.'); + if (lastDotIndex == -1) { + return false; // No package (default package) + } + + String packageName = typeName.substring(0, lastDotIndex); + + // Check if package matches any common JDK package + // This includes both exact matches and sub-packages + return SKIP_COMMON_JDK_PACKAGES.contains(packageName) || + SKIP_COMMON_JDK_PACKAGES.stream().anyMatch(pkg -> packageName.startsWith(pkg + ".")); + } + /** * Resolve a static import statement */ @@ -418,9 +361,9 @@ public static void resolveBinaryType(IJavaProject javaProject, String typeName, return; } - // Performance optimization: Skip common JDK types that Copilot already understands well + // Performance optimization: Skip common JDK packages that Copilot already understands well // This significantly reduces processing time for external dependencies - if (SKIP_COMMON_JDK_TYPES.contains(typeName)) { + if (isCommonJdkType(typeName)) { processedTypes.add(typeName); return; } diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ProjectResolver.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ProjectResolver.java new file mode 100644 index 00000000..a8b971e4 --- /dev/null +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ProjectResolver.java @@ -0,0 +1,234 @@ +package com.microsoft.jdtls.ext.core.parser; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.launching.JavaRuntime; +import org.eclipse.jdt.ls.core.internal.ResourceUtils; + +import com.microsoft.jdtls.ext.core.JdtlsExtActivator; + +public class ProjectResolver { + + public static class DependencyInfo { + public String key; + public String value; + + public DependencyInfo(String key, String value) { + this.key = key; + this.value = value; + } + } + + /** + * Resolve project dependencies information including JDK version. + * + * @param projectUri The project URI + * @param monitor Progress monitor for cancellation support + * @return List of DependencyInfo containing key-value pairs of project information + */ + public static List resolveProjectDependencies(String projectUri, IProgressMonitor monitor) { + List result = new ArrayList<>(); + + try { + IPath projectPath = ResourceUtils.canonicalFilePathFromURI(projectUri); + + // Find the project + IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); + IProject project = findProjectByPath(root, projectPath); + + if (project == null || !project.isAccessible()) { + return result; + } + + IJavaProject javaProject = JavaCore.create(project); + if (javaProject == null || !javaProject.exists()) { + return result; + } + + // Add basic project information + addBasicProjectInfo(result, project, javaProject); + + // Get classpath entries (dependencies) + processClasspathEntries(result, javaProject, monitor); + + // Add build tool info by checking for build files + detectBuildTool(result, project); + + } catch (Exception e) { + JdtlsExtActivator.logException("Error in resolveProjectDependencies", e); + } + + return result; + } + + /** + * Find project by path from all projects in workspace. + */ + private static IProject findProjectByPath(IWorkspaceRoot root, IPath projectPath) { + IProject[] allProjects = root.getProjects(); + for (IProject p : allProjects) { + if (p.getLocation() != null && p.getLocation().equals(projectPath)) { + return p; + } + } + return null; + } + + /** + * Add basic project information including name, location, and Java version settings. + */ + private static void addBasicProjectInfo(List result, IProject project, IJavaProject javaProject) { + // Add project name + result.add(new DependencyInfo("projectName", project.getName())); + + // Add project location + if (project.getLocation() != null) { + result.add(new DependencyInfo("projectLocation", project.getLocation().toOSString())); + } + + // Add JDK version + String javaVersion = javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true); + if (javaVersion != null) { + result.add(new DependencyInfo("javaVersion", javaVersion)); + } + + // Add source compatibility + String sourceCompliance = javaProject.getOption(JavaCore.COMPILER_SOURCE, true); + if (sourceCompliance != null) { + result.add(new DependencyInfo("sourceCompatibility", sourceCompliance)); + } + + // Add target compatibility + String targetCompliance = javaProject.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true); + if (targetCompliance != null) { + result.add(new DependencyInfo("targetCompatibility", targetCompliance)); + } + + // Add module name if it's a modular project + String moduleName = getModuleName(javaProject); + if (moduleName != null) { + result.add(new DependencyInfo("moduleName", moduleName)); + } + } + + /** + * Process classpath entries to extract library and project reference information. + */ + private static void processClasspathEntries(List result, IJavaProject javaProject, IProgressMonitor monitor) { + try { + IClasspathEntry[] classpathEntries = javaProject.getResolvedClasspath(true); + int libCount = 0; + int projectRefCount = 0; + + for (IClasspathEntry entry : classpathEntries) { + if (monitor.isCanceled()) { + break; + } + + switch (entry.getEntryKind()) { + case IClasspathEntry.CPE_LIBRARY: + libCount++; + processLibraryEntry(result, entry, libCount); + break; + case IClasspathEntry.CPE_PROJECT: + projectRefCount++; + processProjectEntry(result, entry, projectRefCount); + break; + case IClasspathEntry.CPE_CONTAINER: + processContainerEntry(result, entry); + break; + } + } + + // Add summary counts + result.add(new DependencyInfo("totalLibraries", String.valueOf(libCount))); + result.add(new DependencyInfo("totalProjectReferences", String.valueOf(projectRefCount))); + + } catch (JavaModelException e) { + JdtlsExtActivator.logException("Error getting classpath entries", e); + } + } + + /** + * Process a library classpath entry. + */ + private static void processLibraryEntry(List result, IClasspathEntry entry, int libCount) { + IPath libPath = entry.getPath(); + if (libPath != null) { + String libName = libPath.lastSegment(); + result.add(new DependencyInfo("library_" + libCount, + libName + " (" + libPath.toOSString() + ")")); + } + } + + /** + * Process a project reference classpath entry. + */ + private static void processProjectEntry(List result, IClasspathEntry entry, int projectRefCount) { + IPath projectRefPath = entry.getPath(); + if (projectRefPath != null) { + result.add(new DependencyInfo("projectReference_" + projectRefCount, + projectRefPath.lastSegment())); + } + } + + /** + * Process a container classpath entry (JRE, Maven, Gradle containers). + */ + private static void processContainerEntry(List result, IClasspathEntry entry) { + String containerPath = entry.getPath().toString(); + if (containerPath.contains("JRE_CONTAINER")) { + result.add(new DependencyInfo("jreContainerPath", containerPath)); + // Try to extract JRE name from container path + try { + IPath containerIPath = entry.getPath(); + String vmInstallName = JavaRuntime.getVMInstallName(containerIPath); + if (vmInstallName != null) { + result.add(new DependencyInfo("jreContainer", vmInstallName)); + } + } catch (Exception e) { + // Ignore if unable to get VM install name + } + } else if (containerPath.contains("MAVEN")) { + result.add(new DependencyInfo("buildTool", "Maven")); + } else if (containerPath.contains("GRADLE")) { + result.add(new DependencyInfo("buildTool", "Gradle")); + } + } + + /** + * Detect build tool by checking for build configuration files. + */ + private static void detectBuildTool(List result, IProject project) { + if (project.getFile("pom.xml").exists()) { + result.add(new DependencyInfo("buildTool", "Maven")); + } else if (project.getFile("build.gradle").exists() || project.getFile("build.gradle.kts").exists()) { + result.add(new DependencyInfo("buildTool", "Gradle")); + } + } + + /** + * Get module name for a Java project. + */ + private static String getModuleName(IJavaProject project) { + if (project == null || !JavaRuntime.isModularProject(project)) { + return null; + } + try { + org.eclipse.jdt.core.IModuleDescription module = project.getModuleDescription(); + return module == null ? null : module.getElementName(); + } catch (Exception e) { + return null; + } + } +} From 485a7c22f0d677ae4971b7cc91263fda2f44a191 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Thu, 23 Oct 2025 20:50:23 +0800 Subject: [PATCH 6/9] test: add invoke command for test --- .../ext/core/parser/ContextResolver.java | 463 +++++++----------- .../ext/core/parser/ProjectResolver.java | 104 ++-- 2 files changed, 252 insertions(+), 315 deletions(-) diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ContextResolver.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ContextResolver.java index 539f335a..2d1ee610 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ContextResolver.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ContextResolver.java @@ -597,34 +597,18 @@ private static String generateClassSignature(org.eclipse.jdt.core.IType type) th * Performance optimization: Skip JavaDoc extraction for binary types to avoid expensive I/O */ private static String extractBriefJavaDoc(org.eclipse.jdt.core.IType type) { + // Performance optimization: Skip JavaDoc extraction for binary types + // getAttachedJavadoc() is expensive - may involve JAR reading, network downloads, HTML parsing + if (type.isBinary()) { + return null; + } + try { - // Performance optimization: Skip JavaDoc extraction for binary types - // getAttachedJavadoc() is very expensive - it may involve: - // - Reading from JAR files - // - Network downloads from Maven Central - // - Parsing large HTML documents - // For binary types, the method signatures alone provide sufficient context - if (type.isBinary()) { - return null; - } - String javadoc = type.getAttachedJavadoc(null); if (javadoc == null || javadoc.isEmpty()) { return null; } - - // Extract first sentence (up to first period followed by space or newline) - int endIndex = javadoc.indexOf(". "); - if (endIndex == -1) { - endIndex = javadoc.indexOf(".\n"); - } - if (endIndex != -1) { - return javadoc.substring(0, endIndex + 1).trim(); - } - - // If no sentence boundary found, limit to 120 characters - return javadoc.length() > 120 ? javadoc.substring(0, 120) + "..." : javadoc; - + return getFirstSentenceOrLimit(javadoc, 120); } catch (Exception e) { return null; } @@ -634,61 +618,14 @@ private static String extractBriefJavaDoc(org.eclipse.jdt.core.IType type) { * Generate simplified field signature for binary types */ private static String generateBinaryFieldSignature(org.eclipse.jdt.core.IField field) { - try { - StringBuilder sb = new StringBuilder(); - - // Modifiers - int flags = field.getFlags(); - if (org.eclipse.jdt.core.Flags.isPublic(flags)) sb.append("public "); - if (org.eclipse.jdt.core.Flags.isStatic(flags)) sb.append("static "); - if (org.eclipse.jdt.core.Flags.isFinal(flags)) sb.append("final "); - - // Type and name - sb.append(simplifyTypeName(org.eclipse.jdt.core.Signature.toString(field.getTypeSignature()))); - sb.append(" ").append(field.getElementName()).append(";"); - - return sb.toString(); - } catch (JavaModelException e) { - return "// Error generating field signature"; - } + return generateFieldSignatureInternal(field, true); } /** * Generate simplified method signature for binary types (no implementation) */ private static String generateBinaryMethodSignature(org.eclipse.jdt.core.IMethod method) { - try { - StringBuilder sb = new StringBuilder(); - - // Modifiers - int flags = method.getFlags(); - if (org.eclipse.jdt.core.Flags.isPublic(flags)) sb.append("public "); - if (org.eclipse.jdt.core.Flags.isStatic(flags)) sb.append("static "); - if (org.eclipse.jdt.core.Flags.isFinal(flags)) sb.append("final "); - if (org.eclipse.jdt.core.Flags.isAbstract(flags)) sb.append("abstract "); - - // Return type (skip for constructors) - if (!method.isConstructor()) { - sb.append(simplifyTypeName(org.eclipse.jdt.core.Signature.toString(method.getReturnType()))); - sb.append(" "); - } - - // Method name - sb.append(method.getElementName()).append("("); - - // Parameters (simplified - just types, no names) - String[] paramTypes = method.getParameterTypes(); - for (int i = 0; i < paramTypes.length; i++) { - if (i > 0) sb.append(", "); - sb.append(simplifyTypeName(org.eclipse.jdt.core.Signature.toString(paramTypes[i]))); - } - - sb.append(");"); - - return sb.toString(); - } catch (JavaModelException e) { - return "// Error generating method signature"; - } + return generateMethodSignatureInternal(method, true, false); } /** @@ -1012,35 +949,7 @@ private static String convertHtmlEntities(String text) { * Returns the first sentence or paragraph of the JavaDoc as a brief description */ private static String extractMethodJavaDocSummary(IMethod method) { - try { - // Try to get JavaDoc from source - org.eclipse.jdt.core.ISourceRange javadocRange = method.getJavadocRange(); - if (javadocRange == null) { - return ""; - } - - String rawJavadoc = method.getCompilationUnit().getSource() - .substring(javadocRange.getOffset(), javadocRange.getOffset() + javadocRange.getLength()); - - if (!isNotEmpty(rawJavadoc)) { - return ""; - } - - // Clean the JavaDoc comment - String cleaned = cleanJavadocComment(rawJavadoc); - - // Extract the description (before any @param, @return, @throws tags) - String description = extractJavadocDescription(cleaned); - - // Get first sentence or limit length - String summary = getFirstSentenceOrLimit(description, 120); - - return summary; - - } catch (Exception e) { - // Silently fail and return empty string - return ""; - } + return extractJavaDocSummaryFromElement(method); } /** @@ -1084,33 +993,25 @@ private static String getFirstSentenceOrLimit(String text, int maxLength) { return ""; } - // Try to find the first sentence (ending with ., !, or ?) - int firstPeriod = text.indexOf(". "); - int firstExclamation = text.indexOf("! "); - int firstQuestion = text.indexOf("? "); - + // Find first sentence boundary (., !, ?) + int[] boundaries = {text.indexOf(". "), text.indexOf(".\n"), text.indexOf("! "), text.indexOf("? ")}; int firstSentenceEnd = -1; - if (firstPeriod != -1) firstSentenceEnd = firstPeriod; - if (firstExclamation != -1 && (firstSentenceEnd == -1 || firstExclamation < firstSentenceEnd)) { - firstSentenceEnd = firstExclamation; - } - if (firstQuestion != -1 && (firstSentenceEnd == -1 || firstQuestion < firstSentenceEnd)) { - firstSentenceEnd = firstQuestion; + for (int boundary : boundaries) { + if (boundary != -1 && (firstSentenceEnd == -1 || boundary < firstSentenceEnd)) { + firstSentenceEnd = boundary; + } } - // If we found a sentence ending and it's within reasonable length + // Return first sentence if within reasonable length if (firstSentenceEnd != -1 && firstSentenceEnd < maxLength) { return text.substring(0, firstSentenceEnd + 1).trim(); } - // Otherwise, limit to maxLength + // Otherwise truncate at maxLength with word boundary if (text.length() > maxLength) { - // Try to cut at a word boundary int lastSpace = text.lastIndexOf(' ', maxLength); - if (lastSpace > maxLength / 2) { - return text.substring(0, lastSpace).trim() + "..."; - } - return text.substring(0, maxLength).trim() + "..."; + int cutPoint = (lastSpace > maxLength / 2) ? lastSpace : maxLength; + return text.substring(0, cutPoint).trim() + "..."; } return text.trim(); @@ -1120,175 +1021,24 @@ private static String getFirstSentenceOrLimit(String text, int maxLength) { * Extract summary description from field JavaDoc */ private static String extractFieldJavaDocSummary(org.eclipse.jdt.core.IField field) { - try { - // Try to get JavaDoc from source - org.eclipse.jdt.core.ISourceRange javadocRange = field.getJavadocRange(); - if (javadocRange == null) { - return ""; - } - - String rawJavadoc = field.getCompilationUnit().getSource() - .substring(javadocRange.getOffset(), javadocRange.getOffset() + javadocRange.getLength()); - - if (!isNotEmpty(rawJavadoc)) { - return ""; - } - - // Clean the JavaDoc comment - String cleaned = cleanJavadocComment(rawJavadoc); - - // Extract the description (before any @tags) - String description = extractJavadocDescription(cleaned); - - // Get first sentence or limit length - String summary = getFirstSentenceOrLimit(description, 120); - - return summary; - - } catch (Exception e) { - // Silently fail and return empty string - return ""; - } + return extractJavaDocSummaryFromElement(field); } /** * Generate human-readable method signature with JavaDoc description */ public static String generateMethodSignature(IMethod method) { - StringBuilder sb = new StringBuilder(); - - try { - int flags = method.getFlags(); - appendAccessModifiers(sb, flags); - appendOtherModifiers(sb, flags, true); - - // Type parameters (if any) - @SuppressWarnings("deprecation") - String[] typeParameters = method.getTypeParameterSignatures(); - if (typeParameters != null && typeParameters.length > 0) { - sb.append("<"); - for (int i = 0; i < typeParameters.length; i++) { - if (i > 0) sb.append(", "); - sb.append(convertTypeSignature(typeParameters[i])); - } - sb.append("> "); - } - - // Return type (constructors don't have return type) - if (!method.isConstructor()) { - String returnType = convertTypeSignature(method.getReturnType()); - sb.append(returnType).append(" "); - } - - // Method name - sb.append(method.getElementName()).append("("); - - // Parameter list - String[] paramTypes = method.getParameterTypes(); - String[] paramNames = method.getParameterNames(); - for (int i = 0; i < paramTypes.length; i++) { - if (i > 0) { - sb.append(", "); - } - sb.append(convertTypeSignature(paramTypes[i])); - if (paramNames != null && i < paramNames.length) { - sb.append(" ").append(paramNames[i]); - } - } - - sb.append(")"); - - // Exception declarations - String[] exceptionTypes = method.getExceptionTypes(); - if (exceptionTypes != null && exceptionTypes.length > 0) { - sb.append(" throws "); - for (int i = 0; i < exceptionTypes.length; i++) { - if (i > 0) sb.append(", "); - sb.append(convertTypeSignature(exceptionTypes[i])); - } - } - - } catch (JavaModelException e) { - return method.getElementName() + "(...)"; - } - - // Extract JavaDoc description and prepend if exists - String javadocSummary = extractMethodJavaDocSummary(method); - if (isNotEmpty(javadocSummary)) { - return "// " + javadocSummary + "\n " + sb.toString(); - } - - return sb.toString(); + return generateMethodSignatureInternal(method, false, true); } /** * Generate human-readable field signature with JavaDoc description */ public static String generateFieldSignature(org.eclipse.jdt.core.IField field) { - StringBuilder sb = new StringBuilder(); - - try { - int flags = field.getFlags(); - appendAccessModifiers(sb, flags); - appendOtherModifiers(sb, flags, false); - - // Type and name - String fieldType = convertTypeSignature(field.getTypeSignature()); - sb.append(fieldType).append(" ").append(field.getElementName()); - - // If it's a constant, try to get the initial value - if (org.eclipse.jdt.core.Flags.isStatic(flags) && org.eclipse.jdt.core.Flags.isFinal(flags)) { - Object constant = field.getConstant(); - if (constant != null) { - sb.append(" = "); - if (constant instanceof String) { - sb.append("\"").append(constant).append("\""); - } else { - sb.append(constant); - } - } - } - - } catch (JavaModelException e) { - return field.getElementName(); - } - - // Extract JavaDoc description and prepend if exists - String javadocSummary = extractFieldJavaDocSummary(field); - if (isNotEmpty(javadocSummary)) { - return "// " + javadocSummary + "\n " + sb.toString(); - } - - return sb.toString(); + return generateFieldSignatureInternal(field, false); } - /** - * Append access modifiers (public/protected/private) to StringBuilder - */ - private static void appendAccessModifiers(StringBuilder sb, int flags) { - if (org.eclipse.jdt.core.Flags.isPublic(flags)) { - sb.append("public "); - } else if (org.eclipse.jdt.core.Flags.isProtected(flags)) { - sb.append("protected "); - } else if (org.eclipse.jdt.core.Flags.isPrivate(flags)) { - sb.append("private "); - } - } - /** - * Append other modifiers (static/final/abstract) to StringBuilder - */ - private static void appendOtherModifiers(StringBuilder sb, int flags, boolean isMethod) { - if (org.eclipse.jdt.core.Flags.isStatic(flags)) { - sb.append("static "); - } - if (org.eclipse.jdt.core.Flags.isFinal(flags)) { - sb.append("final "); - } - if (isMethod && org.eclipse.jdt.core.Flags.isAbstract(flags)) { - sb.append("abstract "); - } - } /** * Convert JDT type signature to human-readable format @@ -1422,6 +1172,173 @@ private static String simplifyTypeName(String qualifiedName) { return lastDot == -1 ? qualifiedName : qualifiedName.substring(lastDot + 1); } + /** + * Unified JavaDoc summary extractor for methods and fields + */ + private static String extractJavaDocSummaryFromElement(org.eclipse.jdt.core.IMember element) { + try { + org.eclipse.jdt.core.ISourceRange javadocRange = element.getJavadocRange(); + if (javadocRange == null) { + return ""; + } + + String rawJavadoc = element.getCompilationUnit().getSource() + .substring(javadocRange.getOffset(), javadocRange.getOffset() + javadocRange.getLength()); + + if (rawJavadoc == null || rawJavadoc.isEmpty()) { + return ""; + } + + String cleaned = cleanJavadocComment(rawJavadoc); + String description = extractJavadocDescription(cleaned); + return getFirstSentenceOrLimit(description, 120); + } catch (Exception e) { + return ""; + } + } + + /** + * Unified method signature generator (handles both source and binary types) + * @param simplified true for binary types (no parameter names, no JavaDoc) + * @param includeJavadoc true to include JavaDoc comments + */ + private static String generateMethodSignatureInternal(IMethod method, boolean simplified, boolean includeJavadoc) { + try { + StringBuilder sb = new StringBuilder(); + int flags = method.getFlags(); + + // Modifiers + if (org.eclipse.jdt.core.Flags.isPublic(flags)) sb.append("public "); + if (!simplified) { + if (org.eclipse.jdt.core.Flags.isProtected(flags)) sb.append("protected "); + if (org.eclipse.jdt.core.Flags.isPrivate(flags)) sb.append("private "); + } + if (org.eclipse.jdt.core.Flags.isStatic(flags)) sb.append("static "); + if (org.eclipse.jdt.core.Flags.isFinal(flags)) sb.append("final "); + if (org.eclipse.jdt.core.Flags.isAbstract(flags)) sb.append("abstract "); + + // Type parameters (only for non-simplified) + if (!simplified) { + @SuppressWarnings("deprecation") + String[] typeParameters = method.getTypeParameterSignatures(); + if (typeParameters != null && typeParameters.length > 0) { + sb.append("<"); + for (int i = 0; i < typeParameters.length; i++) { + if (i > 0) sb.append(", "); + sb.append(convertTypeSignature(typeParameters[i])); + } + sb.append("> "); + } + } + + // Return type (skip for constructors) + if (!method.isConstructor()) { + String returnType = simplified ? + simplifyTypeName(org.eclipse.jdt.core.Signature.toString(method.getReturnType())) : + convertTypeSignature(method.getReturnType()); + sb.append(returnType).append(" "); + } + + // Method name and parameters + sb.append(method.getElementName()).append("("); + String[] paramTypes = method.getParameterTypes(); + String[] paramNames = simplified ? null : method.getParameterNames(); + + for (int i = 0; i < paramTypes.length; i++) { + if (i > 0) sb.append(", "); + String paramType = simplified ? + simplifyTypeName(org.eclipse.jdt.core.Signature.toString(paramTypes[i])) : + convertTypeSignature(paramTypes[i]); + sb.append(paramType); + if (paramNames != null && i < paramNames.length) { + sb.append(" ").append(paramNames[i]); + } + } + sb.append(")"); + + // Exception declarations (only for non-simplified) + if (!simplified) { + String[] exceptionTypes = method.getExceptionTypes(); + if (exceptionTypes != null && exceptionTypes.length > 0) { + sb.append(" throws "); + for (int i = 0; i < exceptionTypes.length; i++) { + if (i > 0) sb.append(", "); + sb.append(convertTypeSignature(exceptionTypes[i])); + } + } + } else { + sb.append(";"); + } + + // Add JavaDoc if requested + if (includeJavadoc) { + String javadocSummary = extractMethodJavaDocSummary(method); + if (javadocSummary != null && !javadocSummary.isEmpty()) { + return "// " + javadocSummary + "\n " + sb.toString(); + } + } + + return sb.toString(); + } catch (JavaModelException e) { + return simplified ? "// Error generating method signature" : method.getElementName() + "(...)"; + } + } + + /** + * Unified field signature generator (handles both source and binary types) + * @param simplified true for binary types (no constant values, no JavaDoc) + */ + private static String generateFieldSignatureInternal(org.eclipse.jdt.core.IField field, boolean simplified) { + try { + StringBuilder sb = new StringBuilder(); + int flags = field.getFlags(); + + // Modifiers + if (org.eclipse.jdt.core.Flags.isPublic(flags)) sb.append("public "); + if (!simplified) { + if (org.eclipse.jdt.core.Flags.isProtected(flags)) sb.append("protected "); + if (org.eclipse.jdt.core.Flags.isPrivate(flags)) sb.append("private "); + } + if (org.eclipse.jdt.core.Flags.isStatic(flags)) sb.append("static "); + if (org.eclipse.jdt.core.Flags.isFinal(flags)) sb.append("final "); + + // Type and name + String fieldType = simplified ? + simplifyTypeName(org.eclipse.jdt.core.Signature.toString(field.getTypeSignature())) : + convertTypeSignature(field.getTypeSignature()); + sb.append(fieldType).append(" ").append(field.getElementName()); + + // Constant value (only for non-simplified) + if (!simplified && org.eclipse.jdt.core.Flags.isStatic(flags) && org.eclipse.jdt.core.Flags.isFinal(flags)) { + Object constant = field.getConstant(); + if (constant != null) { + sb.append(" = "); + if (constant instanceof String) { + sb.append("\"").append(constant).append("\""); + } else { + sb.append(constant); + } + } + } + + if (simplified) { + sb.append(";"); + } + + // Add JavaDoc if not simplified + if (!simplified) { + String javadocSummary = extractFieldJavaDocSummary(field); + if (javadocSummary != null && !javadocSummary.isEmpty()) { + return "// " + javadocSummary + "\n " + sb.toString(); + } + } + + return sb.toString(); + } catch (JavaModelException e) { + return simplified ? "// Error generating field signature" : field.getElementName(); + } + } + /** * Utility method to check if a string is not empty or null */ diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ProjectResolver.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ProjectResolver.java index a8b971e4..2f317a41 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ProjectResolver.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ProjectResolver.java @@ -19,6 +19,19 @@ public class ProjectResolver { + // Constants for dependency info keys + private static final String KEY_BUILD_TOOL = "buildTool"; + private static final String KEY_PROJECT_NAME = "projectName"; + private static final String KEY_PROJECT_LOCATION = "projectLocation"; + private static final String KEY_JAVA_VERSION = "javaVersion"; + private static final String KEY_SOURCE_COMPATIBILITY = "sourceCompatibility"; + private static final String KEY_TARGET_COMPATIBILITY = "targetCompatibility"; + private static final String KEY_MODULE_NAME = "moduleName"; + private static final String KEY_TOTAL_LIBRARIES = "totalLibraries"; + private static final String KEY_TOTAL_PROJECT_REFS = "totalProjectReferences"; + private static final String KEY_JRE_CONTAINER_PATH = "jreContainerPath"; + private static final String KEY_JRE_CONTAINER = "jreContainer"; + public static class DependencyInfo { public String key; public String value; @@ -88,37 +101,21 @@ private static IProject findProjectByPath(IWorkspaceRoot root, IPath projectPath * Add basic project information including name, location, and Java version settings. */ private static void addBasicProjectInfo(List result, IProject project, IJavaProject javaProject) { - // Add project name - result.add(new DependencyInfo("projectName", project.getName())); + result.add(new DependencyInfo(KEY_PROJECT_NAME, project.getName())); - // Add project location - if (project.getLocation() != null) { - result.add(new DependencyInfo("projectLocation", project.getLocation().toOSString())); - } + addIfNotNull(result, KEY_PROJECT_LOCATION, + project.getLocation() != null ? project.getLocation().toOSString() : null); - // Add JDK version - String javaVersion = javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true); - if (javaVersion != null) { - result.add(new DependencyInfo("javaVersion", javaVersion)); - } + addIfNotNull(result, KEY_JAVA_VERSION, + javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true)); - // Add source compatibility - String sourceCompliance = javaProject.getOption(JavaCore.COMPILER_SOURCE, true); - if (sourceCompliance != null) { - result.add(new DependencyInfo("sourceCompatibility", sourceCompliance)); - } + addIfNotNull(result, KEY_SOURCE_COMPATIBILITY, + javaProject.getOption(JavaCore.COMPILER_SOURCE, true)); - // Add target compatibility - String targetCompliance = javaProject.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true); - if (targetCompliance != null) { - result.add(new DependencyInfo("targetCompatibility", targetCompliance)); - } + addIfNotNull(result, KEY_TARGET_COMPATIBILITY, + javaProject.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true)); - // Add module name if it's a modular project - String moduleName = getModuleName(javaProject); - if (moduleName != null) { - result.add(new DependencyInfo("moduleName", moduleName)); - } + addIfNotNull(result, KEY_MODULE_NAME, getModuleName(javaProject)); } /** @@ -151,8 +148,8 @@ private static void processClasspathEntries(List result, IJavaPr } // Add summary counts - result.add(new DependencyInfo("totalLibraries", String.valueOf(libCount))); - result.add(new DependencyInfo("totalProjectReferences", String.valueOf(projectRefCount))); + result.add(new DependencyInfo(KEY_TOTAL_LIBRARIES, String.valueOf(libCount))); + result.add(new DependencyInfo(KEY_TOTAL_PROJECT_REFS, String.valueOf(projectRefCount))); } catch (JavaModelException e) { JdtlsExtActivator.logException("Error getting classpath entries", e); @@ -165,9 +162,8 @@ private static void processClasspathEntries(List result, IJavaPr private static void processLibraryEntry(List result, IClasspathEntry entry, int libCount) { IPath libPath = entry.getPath(); if (libPath != null) { - String libName = libPath.lastSegment(); result.add(new DependencyInfo("library_" + libCount, - libName + " (" + libPath.toOSString() + ")")); + libPath.lastSegment() + " (" + libPath.toOSString() + ")")); } } @@ -187,33 +183,36 @@ private static void processProjectEntry(List result, IClasspathE */ private static void processContainerEntry(List result, IClasspathEntry entry) { String containerPath = entry.getPath().toString(); + if (containerPath.contains("JRE_CONTAINER")) { - result.add(new DependencyInfo("jreContainerPath", containerPath)); - // Try to extract JRE name from container path + result.add(new DependencyInfo(KEY_JRE_CONTAINER_PATH, containerPath)); try { - IPath containerIPath = entry.getPath(); - String vmInstallName = JavaRuntime.getVMInstallName(containerIPath); - if (vmInstallName != null) { - result.add(new DependencyInfo("jreContainer", vmInstallName)); - } + String vmInstallName = JavaRuntime.getVMInstallName(entry.getPath()); + addIfNotNull(result, KEY_JRE_CONTAINER, vmInstallName); } catch (Exception e) { // Ignore if unable to get VM install name } } else if (containerPath.contains("MAVEN")) { - result.add(new DependencyInfo("buildTool", "Maven")); + result.add(new DependencyInfo(KEY_BUILD_TOOL, "Maven")); } else if (containerPath.contains("GRADLE")) { - result.add(new DependencyInfo("buildTool", "Gradle")); + result.add(new DependencyInfo(KEY_BUILD_TOOL, "Gradle")); } } /** * Detect build tool by checking for build configuration files. + * Only adds if not already detected from classpath containers. */ private static void detectBuildTool(List result, IProject project) { + // Check if buildTool already set from container + if (hasBuildToolInfo(result)) { + return; + } + if (project.getFile("pom.xml").exists()) { - result.add(new DependencyInfo("buildTool", "Maven")); + result.add(new DependencyInfo(KEY_BUILD_TOOL, "Maven")); } else if (project.getFile("build.gradle").exists() || project.getFile("build.gradle.kts").exists()) { - result.add(new DependencyInfo("buildTool", "Gradle")); + result.add(new DependencyInfo(KEY_BUILD_TOOL, "Gradle")); } } @@ -226,9 +225,30 @@ private static String getModuleName(IJavaProject project) { } try { org.eclipse.jdt.core.IModuleDescription module = project.getModuleDescription(); - return module == null ? null : module.getElementName(); + return module != null ? module.getElementName() : null; } catch (Exception e) { return null; } } + + /** + * Helper method to add dependency info only if value is not null. + */ + private static void addIfNotNull(List result, String key, String value) { + if (value != null) { + result.add(new DependencyInfo(key, value)); + } + } + + /** + * Check if buildTool info is already present in result list. + */ + private static boolean hasBuildToolInfo(List result) { + for (DependencyInfo info : result) { + if (KEY_BUILD_TOOL.equals(info.key)) { + return true; + } + } + return false; + } } From 9ccad82df99f98bc7aa07890dd8e236f3b1c8e96 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Thu, 23 Oct 2025 21:22:35 +0800 Subject: [PATCH 7/9] feat: update --- package-lock.json | 172 ---------------------- package.json | 1 - src/copilot/contextProvider.ts | 201 -------------------------- src/copilot/copilotHelper.ts | 255 --------------------------------- src/copilot/utils.ts | 154 -------------------- src/extension.ts | 2 - 6 files changed, 785 deletions(-) delete mode 100644 src/copilot/contextProvider.ts delete mode 100644 src/copilot/copilotHelper.ts delete mode 100644 src/copilot/utils.ts diff --git a/package-lock.json b/package-lock.json index 03b7b9bb..5863a3aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.26.1", "license": "MIT", "dependencies": { - "@github/copilot-language-server": "^1.316.0", "await-lock": "^2.2.2", "fmtr": "^1.1.4", "fs-extra": "^10.1.0", @@ -159,90 +158,6 @@ "node": ">=10.0.0" } }, - "node_modules/@github/copilot-language-server": { - "version": "1.385.0", - "resolved": "https://registry.npmjs.org/@github/copilot-language-server/-/copilot-language-server-1.385.0.tgz", - "integrity": "sha512-CB61VtJSmuDhSb7IKzYv5+hIsuIye6fg27wZaUYizw704m6qOdgevnCMiWzAMUD+2J7RfmBY0/VRGf/AVL22Pw==", - "license": "MIT", - "dependencies": { - "vscode-languageserver-protocol": "^3.17.5" - }, - "bin": { - "copilot-language-server": "dist/language-server.js" - }, - "optionalDependencies": { - "@github/copilot-language-server-darwin-arm64": "1.385.0", - "@github/copilot-language-server-darwin-x64": "1.385.0", - "@github/copilot-language-server-linux-arm64": "1.385.0", - "@github/copilot-language-server-linux-x64": "1.385.0", - "@github/copilot-language-server-win32-x64": "1.385.0" - } - }, - "node_modules/@github/copilot-language-server-darwin-arm64": { - "version": "1.385.0", - "resolved": "https://registry.npmjs.org/@github/copilot-language-server-darwin-arm64/-/copilot-language-server-darwin-arm64-1.385.0.tgz", - "integrity": "sha512-HCSffo9wzNTNK7nvyUoxcCZmotZYRnt7hq046wcVjcwWLvr3I+SjdskGeIbuxob1l7eeBkodHAqbVyJ2j5qAUA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@github/copilot-language-server-darwin-x64": { - "version": "1.385.0", - "resolved": "https://registry.npmjs.org/@github/copilot-language-server-darwin-x64/-/copilot-language-server-darwin-x64-1.385.0.tgz", - "integrity": "sha512-fIlpPZEQqRClq2PHL7yQh0BNQmRFnF1mg4m78rPoRV+CW5YU98U10bh9wo2lh9SomKuc+Tjtm5rmGYtIkDQapA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@github/copilot-language-server-linux-arm64": { - "version": "1.385.0", - "resolved": "https://registry.npmjs.org/@github/copilot-language-server-linux-arm64/-/copilot-language-server-linux-arm64-1.385.0.tgz", - "integrity": "sha512-CvDTrdig/5woAHEs1eyAkv0pQaCtTgFQIjMT5K1cI3keo2Cf1sC5ya4nx0zSvfQhVkBLgSXf/DPhlfneXdxxxw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@github/copilot-language-server-linux-x64": { - "version": "1.385.0", - "resolved": "https://registry.npmjs.org/@github/copilot-language-server-linux-x64/-/copilot-language-server-linux-x64-1.385.0.tgz", - "integrity": "sha512-fFkReJRjdjpCkSK89+2mumM0F2wr7nrGyfxVhq8ex47l2H66GbDVRp4tvVYrhD1nXL1+9WSut+LdVJjIHjHBcg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@github/copilot-language-server-win32-x64": { - "version": "1.385.0", - "resolved": "https://registry.npmjs.org/@github/copilot-language-server-win32-x64/-/copilot-language-server-win32-x64-1.385.0.tgz", - "integrity": "sha512-bm0l6eK9auuPlUqVnbFb2EwIN4G3nAVWSfiCiY5xVJeExiQsEqfdAsEV56zawV/9RIVaD4x/kGjgI9T8imaalg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -5820,31 +5735,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/vscode-jsonrpc": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", - "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/vscode-languageserver-protocol": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", - "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", - "license": "MIT", - "dependencies": { - "vscode-jsonrpc": "8.2.0", - "vscode-languageserver-types": "3.17.5" - } - }, - "node_modules/vscode-languageserver-types": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", - "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", - "license": "MIT" - }, "node_modules/vscode-tas-client": { "version": "0.1.75", "resolved": "https://registry.npmjs.org/vscode-tas-client/-/vscode-tas-client-0.1.75.tgz", @@ -6342,49 +6232,6 @@ "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", "dev": true }, - "@github/copilot-language-server": { - "version": "1.385.0", - "resolved": "https://registry.npmjs.org/@github/copilot-language-server/-/copilot-language-server-1.385.0.tgz", - "integrity": "sha512-CB61VtJSmuDhSb7IKzYv5+hIsuIye6fg27wZaUYizw704m6qOdgevnCMiWzAMUD+2J7RfmBY0/VRGf/AVL22Pw==", - "requires": { - "@github/copilot-language-server-darwin-arm64": "1.385.0", - "@github/copilot-language-server-darwin-x64": "1.385.0", - "@github/copilot-language-server-linux-arm64": "1.385.0", - "@github/copilot-language-server-linux-x64": "1.385.0", - "@github/copilot-language-server-win32-x64": "1.385.0", - "vscode-languageserver-protocol": "^3.17.5" - } - }, - "@github/copilot-language-server-darwin-arm64": { - "version": "1.385.0", - "resolved": "https://registry.npmjs.org/@github/copilot-language-server-darwin-arm64/-/copilot-language-server-darwin-arm64-1.385.0.tgz", - "integrity": "sha512-HCSffo9wzNTNK7nvyUoxcCZmotZYRnt7hq046wcVjcwWLvr3I+SjdskGeIbuxob1l7eeBkodHAqbVyJ2j5qAUA==", - "optional": true - }, - "@github/copilot-language-server-darwin-x64": { - "version": "1.385.0", - "resolved": "https://registry.npmjs.org/@github/copilot-language-server-darwin-x64/-/copilot-language-server-darwin-x64-1.385.0.tgz", - "integrity": "sha512-fIlpPZEQqRClq2PHL7yQh0BNQmRFnF1mg4m78rPoRV+CW5YU98U10bh9wo2lh9SomKuc+Tjtm5rmGYtIkDQapA==", - "optional": true - }, - "@github/copilot-language-server-linux-arm64": { - "version": "1.385.0", - "resolved": "https://registry.npmjs.org/@github/copilot-language-server-linux-arm64/-/copilot-language-server-linux-arm64-1.385.0.tgz", - "integrity": "sha512-CvDTrdig/5woAHEs1eyAkv0pQaCtTgFQIjMT5K1cI3keo2Cf1sC5ya4nx0zSvfQhVkBLgSXf/DPhlfneXdxxxw==", - "optional": true - }, - "@github/copilot-language-server-linux-x64": { - "version": "1.385.0", - "resolved": "https://registry.npmjs.org/@github/copilot-language-server-linux-x64/-/copilot-language-server-linux-x64-1.385.0.tgz", - "integrity": "sha512-fFkReJRjdjpCkSK89+2mumM0F2wr7nrGyfxVhq8ex47l2H66GbDVRp4tvVYrhD1nXL1+9WSut+LdVJjIHjHBcg==", - "optional": true - }, - "@github/copilot-language-server-win32-x64": { - "version": "1.385.0", - "resolved": "https://registry.npmjs.org/@github/copilot-language-server-win32-x64/-/copilot-language-server-win32-x64-1.385.0.tgz", - "integrity": "sha512-bm0l6eK9auuPlUqVnbFb2EwIN4G3nAVWSfiCiY5xVJeExiQsEqfdAsEV56zawV/9RIVaD4x/kGjgI9T8imaalg==", - "optional": true - }, "@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -10508,25 +10355,6 @@ "dev": true, "requires": {} }, - "vscode-jsonrpc": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", - "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==" - }, - "vscode-languageserver-protocol": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", - "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", - "requires": { - "vscode-jsonrpc": "8.2.0", - "vscode-languageserver-types": "3.17.5" - } - }, - "vscode-languageserver-types": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", - "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" - }, "vscode-tas-client": { "version": "0.1.75", "resolved": "https://registry.npmjs.org/vscode-tas-client/-/vscode-tas-client-0.1.75.tgz", diff --git a/package.json b/package.json index cb859508..217d60d7 100644 --- a/package.json +++ b/package.json @@ -1110,7 +1110,6 @@ "webpack-cli": "^4.10.0" }, "dependencies": { - "@github/copilot-language-server": "^1.316.0", "await-lock": "^2.2.2", "fmtr": "^1.1.4", "fs-extra": "^10.1.0", diff --git a/src/copilot/contextProvider.ts b/src/copilot/contextProvider.ts deleted file mode 100644 index 1709a44b..00000000 --- a/src/copilot/contextProvider.ts +++ /dev/null @@ -1,201 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { - ResolveRequest, - SupportedContextItem, - type ContextProvider, -} from '@github/copilot-language-server'; -import * as vscode from 'vscode'; -import { CopilotHelper } from './copilotHelper'; -import { sendInfo } from "vscode-extension-telemetry-wrapper"; -import { - JavaContextProviderUtils, - CancellationError, - InternalCancellationError, - CopilotCancellationError, - ContextResolverFunction, - CopilotApi -} from './utils'; - -export async function registerCopilotContextProviders( - context: vscode.ExtensionContext -) { - try { - const apis = await JavaContextProviderUtils.getCopilotApis(); - if (!apis.clientApi || !apis.chatApi) { - return; - } - - // Register the Java completion context provider - const provider: ContextProvider = { - id: "vscjava.vscode-java-dependency", // use extension id as provider id for now - selector: [{ language: "java" }], - resolver: { resolve: createJavaContextResolver() } - }; - - const installCount = await JavaContextProviderUtils.installContextProviderOnApis(apis, provider, context, installContextProvider); - - if (installCount === 0) { - return; - } - console.log('======= register context provider =======') - - sendInfo("", { - "action": "registerCopilotContextProvider", - "extension": "vscjava.vscode-java-dependency", - "status": "succeeded", - "installCount": installCount - }); - } - catch (error) { - console.error('Error occurred while registering Java context provider for GitHub Copilot extension:', error); - } -} - -/** - * Create the Java context resolver function - */ -function createJavaContextResolver(): ContextResolverFunction { - return async (request: ResolveRequest, copilotCancel: vscode.CancellationToken): Promise => { - const resolveStartTime = performance.now(); - let logMessage = `Java Context Provider: resolve(${request.documentContext.uri}:${request.documentContext.offset}):`; - - try { - // Check for immediate cancellation - JavaContextProviderUtils.checkCancellation(copilotCancel); - - return await resolveJavaContext(request, copilotCancel); - } catch (error: any) { - // This should never be reached due to handleError throwing, but TypeScript requires it - return []; - } finally { - const duration = Math.round(performance.now() - resolveStartTime); - if (!logMessage.includes('cancellation')) { - logMessage += `(completed in ${duration}ms)`; - console.info(logMessage); - } - } - }; -} - -/** - * Send telemetry data for Java context resolution - */ -function sendContextTelemetry(request: ResolveRequest, start: number, itemCount: number, status: string, error?: string) { - const duration = Math.round(performance.now() - start); - const telemetryData: any = { - "action": "resolveJavaContext", - "completionId": request.completionId, - "duration": duration, - "itemCount": itemCount, - "status": status - }; - - if (error) { - telemetryData.error = error; - } - - sendInfo("", telemetryData); -} - -async function resolveJavaContext(request: ResolveRequest, copilotCancel: vscode.CancellationToken): Promise { - const items: SupportedContextItem[] = []; - const start = performance.now(); - const documentUri = request.documentContext.uri; - const caretOffset = request.documentContext.offset; - - try { - // Check for cancellation before starting - JavaContextProviderUtils.checkCancellation(copilotCancel); - - // Get current document and position information - const activeEditor = vscode.window.activeTextEditor; - if (!activeEditor || activeEditor.document.languageId !== 'java') { - return items; - } - - const document = activeEditor.document; - - // Resolve imports directly without caching - const importClass = await CopilotHelper.resolveLocalImports(document.uri, copilotCancel); - console.trace('Resolved imports count:', importClass?.length || 0); - - // Check for cancellation after resolution - JavaContextProviderUtils.checkCancellation(copilotCancel); - - // Check for cancellation before processing results - JavaContextProviderUtils.checkCancellation(copilotCancel); - - if (importClass) { - // Process imports in batches to reduce cancellation check overhead - const contextItems = JavaContextProviderUtils.createContextItemsFromImports(importClass); - - // Check cancellation once after creating all items - JavaContextProviderUtils.checkCancellation(copilotCancel); - - items.push(...contextItems); - } - console.log('------ d1'); - - // Get workspace folder from document URI to get project URI - const workspaceFolder = vscode.workspace.getWorkspaceFolder(document.uri); - if (workspaceFolder) { - // Check for cancellation before calling external libraries metadata - JavaContextProviderUtils.checkCancellation(copilotCancel); - console.log('----------- d2') - // Get external libraries metadata as an array of Trait objects - const metadata = await CopilotHelper.getExternalLibrariesMetadata(workspaceFolder.uri, copilotCancel); - console.trace('Resolved external libraries metadata count:', metadata.length); - console.log('--------- d3') - // Check for cancellation after resolution - JavaContextProviderUtils.checkCancellation(copilotCancel); - - // The metadata is already in Trait format (array of {name, value} objects) - if (metadata && metadata.length > 0) { - // Check cancellation once after receiving all traits - JavaContextProviderUtils.checkCancellation(copilotCancel); - - items.push(...metadata); - } - } - } catch (error: any) { - if (error instanceof CopilotCancellationError) { - sendContextTelemetry(request, start, items.length, "cancelled_by_copilot"); - throw error; - } - if (error instanceof vscode.CancellationError || error.message === CancellationError.Canceled) { - sendContextTelemetry(request, start, items.length, "cancelled_internally"); - throw new InternalCancellationError(); - } - - // Send telemetry for general errors (but continue with partial results) - sendContextTelemetry(request, start, items.length, "error_partial_results", error.message || "unknown_error"); - - console.error(`Error resolving Java context for ${documentUri}:${caretOffset}:`, error); - - // Return partial results and log completion for error case - return items; - } - - // Send telemetry data once at the end for success case - sendContextTelemetry(request, start, items.length, "succeeded"); - console.log('========= Finish context provider, time:', performance.now() - start, " item.length", items.length); - console.dir(items) - return items; -} - -export async function installContextProvider( - copilotAPI: CopilotApi, - contextProvider: ContextProvider -): Promise { - const hasGetContextProviderAPI = typeof copilotAPI.getContextProviderAPI === 'function'; - if (hasGetContextProviderAPI) { - const contextAPI = await copilotAPI.getContextProviderAPI('v1'); - if (contextAPI) { - return contextAPI.registerContextProvider(contextProvider); - } - } - return undefined; -} diff --git a/src/copilot/copilotHelper.ts b/src/copilot/copilotHelper.ts deleted file mode 100644 index 90f0806a..00000000 --- a/src/copilot/copilotHelper.ts +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -import { commands, Uri, CancellationToken } from "vscode"; - -export interface INodeImportClass { - uri: string; - className: string; // Changed from 'class' to 'className' to match Java code -} - -interface INodeData { - displayName?: string; - name: string; - moduleName?: string; - path?: string; - handlerIdentifier?: string; - uri?: string; - kind: number; // NodeKind enum value - children?: any[]; - metaData?: { [id: string]: any }; -} - -enum NodeKind { - Workspace = 1, - Project = 2, - PackageRoot = 3, - Package = 4, - PrimaryType = 5, - CompilationUnit = 6, - ClassFile = 7, - Container = 8, - Folder = 9, - File = 10, -} -/** - * Helper class for Copilot integration to analyze Java project dependencies - */ -export namespace CopilotHelper { - /** - * Resolves all local project types imported by the given file - * @param fileUri The URI of the Java file to analyze - * @param cancellationToken Optional cancellation token to abort the operation - * @returns Array of strings in format "type:fully.qualified.name" where type is class|interface|enum|annotation - */ - export async function resolveLocalImports(fileUri: Uri, cancellationToken?: CancellationToken): Promise { - if (cancellationToken?.isCancellationRequested) { - return []; - } - - if (cancellationToken?.isCancellationRequested) { - return []; - } - - try { - // Create a promise that can be cancelled - const commandPromise = commands.executeCommand("java.execute.workspaceCommand", "java.project.getImportClassContent", fileUri.toString()) as Promise; - - if (cancellationToken) { - const result = await Promise.race([ - commandPromise, - new Promise((_, reject) => { - cancellationToken.onCancellationRequested(() => { - reject(new Error('Operation cancelled')); - }); - }) - ]); - return result || []; - } else { - const result = await commandPromise; - return result || []; - } - } catch (error: any) { - if (error.message === 'Operation cancelled') { - return []; - } - return []; - } - } - - /** - * Get external libraries metadata for the project (top-level/direct dependencies only) - * @param workspaceFolderUri The URI of the workspace folder - * @param cancellationToken Optional cancellation token to abort the operation - * @returns Array of DependencyMetadata objects containing name-value pairs - */ - export async function getExternalLibrariesMetadata(workspaceFolderUri: Uri, cancellationToken?: CancellationToken): Promise> { - if (cancellationToken?.isCancellationRequested) { - return []; - } - - try { - const metadata: Array<{name: string, value: string}> = []; - - // Step 0: Get all projects in the workspace folder - const projects = await commands.executeCommand( - "java.execute.workspaceCommand", - "java.project.list", - workspaceFolderUri.toString() - ) as INodeData[]; - - - if (!projects || projects.length === 0) { - return []; - } - - // Process the first project (or you can process all projects) - const project = projects[0]; - const projectUri = project.uri; - - if (!projectUri) { - return []; - } - - - if (cancellationToken?.isCancellationRequested) { - return []; - } - - // Step 1: Get project's children to find containers - const projectChildren = await commands.executeCommand( - "java.execute.workspaceCommand", - "java.getPackageData", - { - kind: NodeKind.Project, - projectUri: projectUri - } - ) as INodeData[]; - - - if (cancellationToken?.isCancellationRequested) { - return []; - } - - // Step 2: Find container nodes (Maven Dependencies, JRE System Library, Referenced Libraries) - const containers = projectChildren?.filter(node => node.kind === NodeKind.Container) || []; - - // Also check for PackageRoot nodes directly in project children (these are top-level dependencies) - const directPackageRoots = projectChildren?.filter(node => node.kind === NodeKind.PackageRoot) || []; - - // Process direct package roots first - for (const pkgRoot of directPackageRoots) { - if (cancellationToken?.isCancellationRequested) { - return metadata; - } - - const jarName = pkgRoot.name || pkgRoot.displayName || 'unknown'; - const jarPath = pkgRoot.path || ''; - - // Add dependency trait - metadata.push({ - name: `dependency:${jarName}`, - value: jarPath - }); - - // Add metadata if available - if (pkgRoot.metaData) { - const groupId = pkgRoot.metaData['maven.groupId']; - const artifactId = pkgRoot.metaData['maven.artifactId']; - const version = pkgRoot.metaData['maven.version']; - - if (groupId && artifactId && version) { - metadata.push({ - name: `dependency.coordinates:${jarName}`, - value: `${groupId}:${artifactId}:${version}` - }); - } - } - } - - // Step 3: For containers, only get the top-level (direct) dependencies - for (const container of containers) { - if (cancellationToken?.isCancellationRequested) { - return metadata; - } - - - // Only process Maven Dependencies and Gradle Dependencies containers for direct dependencies - const containerName = container.name?.toLowerCase() || ''; - const isRelevantContainer = containerName.includes('maven') || - containerName.includes('gradle') || - containerName.includes('referenced'); - - if (!isRelevantContainer) { - continue; - } - - const packageRoots = await commands.executeCommand( - "java.execute.workspaceCommand", - "java.getPackageData", - { - kind: NodeKind.Container, - projectUri: projectUri, - path: container.path - } - ) as INodeData[]; - - - if (cancellationToken?.isCancellationRequested) { - return metadata; - } - - // Process each top-level package root (these are direct dependencies) - for (const pkgRoot of packageRoots || []) { - if (pkgRoot.kind === NodeKind.PackageRoot) { - const jarName = pkgRoot.name || pkgRoot.displayName || 'unknown'; - const jarPath = pkgRoot.path || ''; - - - // Add dependency trait - metadata.push({ - name: `dependency:${jarName}`, - value: jarPath - }); - - // Add metadata if available - if (pkgRoot.metaData) { - const groupId = pkgRoot.metaData['maven.groupId']; - const artifactId = pkgRoot.metaData['maven.artifactId']; - const version = pkgRoot.metaData['maven.version']; - - if (groupId && artifactId && version) { - metadata.push({ - name: `dependency.coordinates:${jarName}`, - value: `${groupId}:${artifactId}:${version}` - }); - } - - // Add type - if (containerName.includes('maven')) { - metadata.push({ - name: `dependency.type:${jarName}`, - value: 'maven' - }); - } else if (containerName.includes('gradle')) { - metadata.push({ - name: `dependency.type:${jarName}`, - value: 'gradle' - }); - } - } - } - } - } - - return metadata; - - } catch (error: any) { - if (error.message === 'Operation cancelled') { - return []; - } - console.error('[getExternalLibrariesMetadata] Error getting external libraries metadata:', error); - return []; - } - } -} diff --git a/src/copilot/utils.ts b/src/copilot/utils.ts deleted file mode 100644 index ff8dfd89..00000000 --- a/src/copilot/utils.ts +++ /dev/null @@ -1,154 +0,0 @@ -import * as vscode from 'vscode'; -import { - ContextProviderApiV1, - ResolveRequest, - SupportedContextItem, - type ContextProvider, -} from '@github/copilot-language-server'; - -/** - * Error classes for Copilot context provider cancellation handling - */ -export class CancellationError extends Error { - static readonly Canceled = "Canceled"; - constructor() { - super(CancellationError.Canceled); - this.name = this.message; - } -} - -export class InternalCancellationError extends CancellationError { -} - -export class CopilotCancellationError extends CancellationError { -} - -/** - * Type definitions for common patterns - */ -export type ContextResolverFunction = (request: ResolveRequest, token: vscode.CancellationToken) => Promise; - -export interface CopilotApiWrapper { - clientApi?: CopilotApi; - chatApi?: CopilotApi; -} - -export interface CopilotApi { - getContextProviderAPI(version: string): Promise; -} - -/** - * Utility class for handling common operations in Java Context Provider - */ -export class JavaContextProviderUtils { - /** - * Check if operation should be cancelled and throw appropriate error - */ - static checkCancellation(token: vscode.CancellationToken): void { - if (token.isCancellationRequested) { - throw new CopilotCancellationError(); - } - } - - /** - * Create context items from import classes - */ - static createContextItemsFromImports(importClasses: any[]): SupportedContextItem[] { - return importClasses.map((cls: any) => ({ - uri: cls.uri, - value: cls.className, - importance: 70, - origin: 'request' as const - })); - } - - /** - * Create a basic Java version context item - */ - static createJavaVersionItem(javaVersion: string): SupportedContextItem { - return { - name: 'java.version', - value: javaVersion, - importance: 90, - id: 'java-version', - origin: 'request' - }; - } - - - /** - * Get and validate Copilot APIs - */ - static async getCopilotApis(): Promise { - const copilotClientApi = await getCopilotClientApi(); - const copilotChatApi = await getCopilotChatApi(); - return { clientApi: copilotClientApi, chatApi: copilotChatApi }; - } - - /** - * Install context provider on available APIs - */ - static async installContextProviderOnApis( - apis: CopilotApiWrapper, - provider: ContextProvider, - context: vscode.ExtensionContext, - installFn: (api: CopilotApi, provider: ContextProvider) => Promise - ): Promise { - let installCount = 0; - - if (apis.clientApi) { - const disposable = await installFn(apis.clientApi, provider); - if (disposable) { - context.subscriptions.push(disposable); - installCount++; - } - } - - if (apis.chatApi) { - const disposable = await installFn(apis.chatApi, provider); - if (disposable) { - context.subscriptions.push(disposable); - installCount++; - } - } - - return installCount; - } -} - -/** - * Get Copilot client API - */ -export async function getCopilotClientApi(): Promise { - const extension = vscode.extensions.getExtension('github.copilot'); - if (!extension) { - return undefined; - } - try { - return await extension.activate(); - } catch { - return undefined; - } -} - -/** - * Get Copilot chat API - */ -export async function getCopilotChatApi(): Promise { - type CopilotChatApi = { getAPI?(version: number): CopilotApi | undefined }; - const extension = vscode.extensions.getExtension('github.copilot-chat'); - if (!extension) { - return undefined; - } - - let exports: CopilotChatApi | undefined; - try { - exports = await extension.activate(); - } catch { - return undefined; - } - if (!exports || typeof exports.getAPI !== 'function') { - return undefined; - } - return exports.getAPI(1); -} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index af169c32..d2fe424b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -21,7 +21,6 @@ import { setContextForDeprecatedTasks, updateExportTaskType } from "./tasks/buil import { CodeActionProvider } from "./tasks/buildArtifact/migration/CodeActionProvider"; import { newJavaFile } from "./explorerCommands/new"; import upgradeManager from "./upgrade/upgradeManager"; -import { registerCopilotContextProviders } from "./copilot/contextProvider"; export async function activate(context: ExtensionContext): Promise { contextManager.initialize(context); @@ -38,7 +37,6 @@ export async function activate(context: ExtensionContext): Promise { } }); contextManager.setContextValue(Context.EXTENSION_ACTIVATED, true); - await registerCopilotContextProviders(context); } async function activateExtension(_operationId: string, context: ExtensionContext): Promise { From 13189bc653ca81c8942454d073d3bbcb91fef9d6 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Fri, 24 Oct 2025 14:46:21 +0800 Subject: [PATCH 8/9] fix: update the logic to the resolve --- .../jdtls/ext/core/ProjectCommand.java | 78 ++++++++++--------- .../ext/core/parser/ContextResolver.java | 4 +- 2 files changed, 44 insertions(+), 38 deletions(-) diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java index 0989225a..b1f86aff 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java @@ -350,20 +350,13 @@ public static boolean checkImportStatus() { return hasError; } - // Threshold for triggering external dependency resolution - // If project source classes < this value, supplement with external dependencies - private static final int MIN_SOURCE_CLASSES_THRESHOLD = 5; - - // Maximum number of external dependency classes to include - private static final int MAX_DEPENDENCY_CLASSES = 10; - // Maximum methods to display for binary (external) classes - private static final int MAX_METHODS_FOR_BINARY = 5; + private static final int MAX_METHODS_FOR_BINARY = 50; /** * Get import class content for Copilot integration. * This method extracts information about imported classes from a Java file. - * Uses an adaptive strategy: only includes external dependencies when project sources are sparse. + * Uses a time-controlled strategy: prioritizes internal classes, adds external classes only if time permits. * * @param arguments List containing the file URI as the first element * @param monitor Progress monitor for cancellation support @@ -374,6 +367,11 @@ public static List getImportClassContent(List arguments return Collections.emptyList(); } + // Time control: total budget 80ms, early return at 75ms + long startTime = System.currentTimeMillis(); + final long TIME_BUDGET_MS = 80; + final long EARLY_RETURN_MS = 75; + try { String fileUri = (String) arguments.get(0); @@ -408,17 +406,18 @@ public static List getImportClassContent(List arguments org.eclipse.jdt.core.ICompilationUnit compilationUnit = (org.eclipse.jdt.core.ICompilationUnit) javaElement; // Parse imports and resolve local project files - // Delegate to JavaContentParser for processing List classInfoList = new ArrayList<>(); // Get all imports from the compilation unit org.eclipse.jdt.core.IImportDeclaration[] imports = compilationUnit.getImports(); Set processedTypes = new HashSet<>(); - // Phase 1: Resolve project source classes only + // Phase 1: Priority - Resolve project source classes (internal) for (org.eclipse.jdt.core.IImportDeclaration importDecl : imports) { - if (monitor.isCanceled()) { - break; + // Check time budget before each operation + long elapsed = System.currentTimeMillis() - startTime; + if (monitor.isCanceled() || elapsed >= EARLY_RETURN_MS) { + return classInfoList; // Early return if approaching time limit } String importName = importDecl.getElementName(); @@ -437,34 +436,41 @@ public static List getImportClassContent(List arguments } } - // Phase 2: Adaptive external dependency resolution - // Only trigger if project source classes are sparse (< threshold) - if (classInfoList.size() < MIN_SOURCE_CLASSES_THRESHOLD && !monitor.isCanceled()) { - // Track external classes separately to apply limits - List externalClasses = new ArrayList<>(); + // Phase 2: If time permits, resolve external dependencies + long elapsedAfterInternal = System.currentTimeMillis() - startTime; + if (elapsedAfterInternal < EARLY_RETURN_MS && !monitor.isCanceled()) { + // Calculate remaining time budget for external classes + long remainingTime = TIME_BUDGET_MS - elapsedAfterInternal; - for (org.eclipse.jdt.core.IImportDeclaration importDecl : imports) { - if (monitor.isCanceled() || externalClasses.size() >= MAX_DEPENDENCY_CLASSES) { - break; - } - - String importName = importDecl.getElementName(); - boolean isStatic = (importDecl.getFlags() & org.eclipse.jdt.core.Flags.AccStatic) != 0; + // Only proceed with external if we have reasonable time left (at least 15ms) + if (remainingTime >= 15) { + List externalClasses = new ArrayList<>(); - // Skip package imports (*.* ) - too broad for external dependencies - if (importName.endsWith(".*")) { - continue; + for (org.eclipse.jdt.core.IImportDeclaration importDecl : imports) { + // Check time before each external resolution + long currentElapsed = System.currentTimeMillis() - startTime; + if (monitor.isCanceled() || currentElapsed >= EARLY_RETURN_MS) { + break; + } + + String importName = importDecl.getElementName(); + boolean isStatic = (importDecl.getFlags() & org.eclipse.jdt.core.Flags.AccStatic) != 0; + + // Skip package imports (*.* ) - too broad for external dependencies + if (importName.endsWith(".*")) { + continue; + } + + // Resolve external (binary) types with simplified content + if (!isStatic) { + ContextResolver.resolveBinaryType(javaProject, importName, externalClasses, + processedTypes, MAX_METHODS_FOR_BINARY, monitor); + } } - // Resolve external (binary) types with simplified content - if (!isStatic) { - ContextResolver.resolveBinaryType(javaProject, importName, externalClasses, - processedTypes, MAX_METHODS_FOR_BINARY, monitor); - } + // Append external classes after project sources + classInfoList.addAll(externalClasses); } - - // Append external classes after project sources - classInfoList.addAll(externalClasses); } return classInfoList; diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ContextResolver.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ContextResolver.java index 2d1ee610..bf815c50 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ContextResolver.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/parser/ContextResolver.java @@ -71,11 +71,11 @@ public class ContextResolver { */ public static class ImportClassInfo { public String uri; // File URI (required) - public String value; // Human-readable class description with JavaDoc appended (required) + public String className; // Human-readable class description with JavaDoc appended (required) public ImportClassInfo(String uri, String value) { this.uri = uri; - this.value = value; + this.className = value; } } From aa1c86f075d7d29f51c562ad22c374690b90cc33 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Fri, 24 Oct 2025 14:53:40 +0800 Subject: [PATCH 9/9] fix: update --- .../src/com/microsoft/jdtls/ext/core/ProjectCommand.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java index b1f86aff..4aa6632b 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java @@ -350,9 +350,6 @@ public static boolean checkImportStatus() { return hasError; } - // Maximum methods to display for binary (external) classes - private static final int MAX_METHODS_FOR_BINARY = 50; - /** * Get import class content for Copilot integration. * This method extracts information about imported classes from a Java file. @@ -464,7 +461,7 @@ public static List getImportClassContent(List arguments // Resolve external (binary) types with simplified content if (!isStatic) { ContextResolver.resolveBinaryType(javaProject, importName, externalClasses, - processedTypes, MAX_METHODS_FOR_BINARY, monitor); + processedTypes, Integer.MAX_VALUE, monitor); } }