Skip to content

Commit f7ed20b

Browse files
feat(amazonq): enhance workspaceContext classpath generation (aws#1955)
* feat(amazonq): enhance workspaceContext classpath generation * test(amazonq): add unit test for language detection
1 parent ad8e2db commit f7ed20b

File tree

3 files changed

+93
-15
lines changed

3 files changed

+93
-15
lines changed

server/aws-lsp-codewhisperer/src/language-server/workspaceContext/artifactManager.ts

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ import JSZip = require('jszip')
66
import { EclipseConfigGenerator, JavaProjectAnalyzer } from './javaManager'
77
import { resolveSymlink, isDirectory, isEmptyDirectory } from './util'
88
import glob = require('fast-glob')
9-
import { CodewhispererLanguage, getCodeWhispererLanguageIdFromPath } from '../../shared/languageDetection'
9+
import {
10+
CodewhispererLanguage,
11+
getCodeWhispererLanguageIdFromPath,
12+
isJavaProjectFileFromPath,
13+
} from '../../shared/languageDetection'
1014

1115
export interface FileMetadata {
1216
filePath: string
@@ -627,31 +631,55 @@ export class ArtifactManager {
627631
files: FileMetadata[]
628632
): Promise<FileMetadata[]> {
629633
const workspacePath = URI.parse(workspaceFolder.uri).path
630-
const hasJavaFiles = files.some(file => file.language === 'java')
634+
const hasJavaFiles = files.some(file => file.language === 'java' && file.relativePath.endsWith('java'))
631635

632636
if (!hasJavaFiles) {
633637
return files
634638
}
635639

640+
// Extract project roots from file paths for scenarios that workspace were not opened up from project roots
641+
const projectRoots = this.extractJavaProjectRoots(files)
636642
const additionalFiles: FileMetadata[] = []
637643

638-
// Generate Eclipse configuration files
639-
const javaManager = new JavaProjectAnalyzer(workspacePath)
640-
const structure = await javaManager.analyze()
641-
const generator = new EclipseConfigGenerator(workspaceFolder, this.logging)
644+
// Process each project root separately
645+
for (const projectRoot of projectRoots) {
646+
const isRootProject = projectRoot === '.'
647+
const projectPath = path.join(workspacePath, projectRoot)
642648

643-
// Generate and add .classpath file
644-
const classpathFiles = await generator.generateDotClasspath(structure)
645-
for (const classpathFile of classpathFiles) {
646-
additionalFiles.push(classpathFile)
647-
}
649+
// Create project-specific "workspace folder" for analyzing
650+
const projectWorkspaceFolder: WorkspaceFolder = {
651+
...workspaceFolder,
652+
uri: URI.file(projectPath).toString(),
653+
}
654+
655+
const javaManager = new JavaProjectAnalyzer(projectPath)
656+
const structure = await javaManager.analyze()
657+
const generator = new EclipseConfigGenerator(projectWorkspaceFolder, this.logging)
658+
659+
const classpathFiles = await generator.generateDotClasspath(structure)
660+
const projectConfigFiles = await generator.generateDotProject(
661+
isRootProject ? workspaceFolder.name : projectRoot,
662+
structure
663+
)
664+
665+
// Update relativePath to include project directory for zip upload
666+
const updatedFiles = [...classpathFiles, ...projectConfigFiles].map(file => ({
667+
...file,
668+
relativePath: isRootProject ? file.relativePath : path.join(projectRoot, file.relativePath),
669+
}))
648670

649-
// Generate and add .project file
650-
const projectFiles = await generator.generateDotProject(path.basename(workspacePath), structure)
651-
for (const projectFile of projectFiles) {
652-
additionalFiles.push(projectFile)
671+
additionalFiles.push(...updatedFiles)
653672
}
654673

655674
return [...files, ...additionalFiles]
656675
}
676+
677+
private extractJavaProjectRoots(files: FileMetadata[]): string[] {
678+
const projectRoots = new Set<string>()
679+
files
680+
.filter(file => isJavaProjectFileFromPath(file.relativePath))
681+
.map(file => path.dirname(file.relativePath))
682+
.forEach(projectRoot => projectRoots.add(projectRoot))
683+
return Array.from(projectRoots)
684+
}
657685
}

server/aws-lsp-codewhisperer/src/shared/languageDetection.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
getSupportedLanguageId,
66
languageByExtension,
77
qLanguageIdByDocumentLanguageId,
8+
getCodeWhispererLanguageIdFromPath,
9+
isJavaProjectFileFromPath,
810
} from './languageDetection'
911

1012
describe('LanguageDetection', () => {
@@ -40,4 +42,28 @@ describe('LanguageDetection', () => {
4042
assert.ok(!getSupportedLanguageId(typescriptDocument, ['javascript']))
4143
})
4244
})
45+
46+
describe('getCodeWhispererLanguageIdFromPath', () => {
47+
it('should return language type with override', () => {
48+
assert.strictEqual(getCodeWhispererLanguageIdFromPath('test/pom.xml'), 'java')
49+
assert.strictEqual(getCodeWhispererLanguageIdFromPath('test/build.gradle.kts'), 'java')
50+
assert.strictEqual(getCodeWhispererLanguageIdFromPath('test/build.xml'), 'java')
51+
assert.strictEqual(getCodeWhispererLanguageIdFromPath('test/build.gradle'), 'java')
52+
assert.strictEqual(getCodeWhispererLanguageIdFromPath('test/test.java'), 'java')
53+
assert.strictEqual(getCodeWhispererLanguageIdFromPath('test/package.json'), 'javascript')
54+
assert.strictEqual(getCodeWhispererLanguageIdFromPath('test/test.js'), 'javascript')
55+
assert.strictEqual(getCodeWhispererLanguageIdFromPath('test/test.ts'), 'typescript')
56+
assert.strictEqual(getCodeWhispererLanguageIdFromPath('test/test.py'), 'python')
57+
})
58+
})
59+
60+
describe('isJavaProjectFileFromPath', () => {
61+
it('should return project file as java language', () => {
62+
assert.ok(isJavaProjectFileFromPath('test/build.gradle'))
63+
assert.ok(isJavaProjectFileFromPath('test/pom.xml'))
64+
assert.ok(isJavaProjectFileFromPath('test/build.gradle.kts'))
65+
assert.ok(isJavaProjectFileFromPath('test/build.xml'))
66+
assert.ok(!isJavaProjectFileFromPath('test/package.json'))
67+
})
68+
})
4369
})

server/aws-lsp-codewhisperer/src/shared/languageDetection.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,8 @@ export function getCodeWhispererLanguageIdFromPath(filePath: string): Codewhispe
294294
return 'javascript'
295295
}
296296

297+
if (isJavaProjectFileFromPath(filePath)) return 'java'
298+
297299
for (const [extension, languageId] of Object.entries(languageByExtension)) {
298300
if (filePath.endsWith(extension)) {
299301
return getRuntimeLanguage(languageId)
@@ -302,3 +304,25 @@ export function getCodeWhispererLanguageIdFromPath(filePath: string): Codewhispe
302304

303305
return undefined
304306
}
307+
308+
/**
309+
* For project context we're treating these file name as java project file to be uploaded to container & using it's location to identify java roots
310+
* Kotlin may also have these file but for project context we're only considering it for java until we have language support for kotlin
311+
* @param filePath
312+
* @returns boolean indicate the java file override
313+
*
314+
* @example
315+
* // Returns 'true' for a build.gradle file
316+
* isJavaProjectFileFromPath('src/build.gradle')
317+
*
318+
* @remarks
319+
* - This function is extension-based only
320+
*/
321+
export function isJavaProjectFileFromPath(filePath: string): boolean {
322+
return (
323+
filePath.endsWith(`build.gradle`) ||
324+
filePath.endsWith(`build.gradle.kts`) ||
325+
filePath.endsWith(`pom.xml`) ||
326+
filePath.endsWith(`build.xml`)
327+
)
328+
}

0 commit comments

Comments
 (0)