Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type" : "bugfix",
"description" : "fix(amazonq): fix for /test project payload collection filter"
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import software.aws.toolkits.core.utils.createTemporaryZipFile
import software.aws.toolkits.core.utils.debug
import software.aws.toolkits.core.utils.getLogger
import software.aws.toolkits.core.utils.putNextEntry
import software.aws.toolkits.jetbrains.services.amazonq.FeatureDevSessionContext
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.Payload
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.PayloadContext
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.PayloadMetadata
Expand All @@ -33,6 +32,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.language.programmi
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.CODE_SCAN_CREATE_PAYLOAD_TIMEOUT_IN_SECONDS
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.DEFAULT_CODE_SCAN_TIMEOUT_IN_SECONDS
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.DEFAULT_PAYLOAD_LIMIT_IN_BYTES
import software.aws.toolkits.jetbrains.services.codewhisperer.util.GitIgnoreFilteringUtil
import software.aws.toolkits.jetbrains.services.codewhisperer.util.isWithin
import software.aws.toolkits.resources.message
import java.io.File
Expand All @@ -53,8 +53,6 @@ class CodeTestSessionConfig(
project.guessProjectDir() ?: error("Cannot guess base directory for project ${project.name}")
}

private val featureDevSessionContext = FeatureDevSessionContext(project)

val fileIndex = ProjectRootManager.getInstance(project).fileIndex

/**
Expand Down Expand Up @@ -186,15 +184,16 @@ class CodeTestSessionConfig(

moduleLoop@ for (module in project.modules) {
val changeListManager = ChangeListManager.getInstance(module.project)
if (module.guessModuleDir() != null) {
stack.push(module.guessModuleDir())
module.guessModuleDir()?.let { moduleDir ->
val gitIgnoreFilteringUtil = GitIgnoreFilteringUtil(moduleDir)
stack.push(moduleDir)
while (stack.isNotEmpty()) {
val current = stack.pop()

if (!current.isDirectory) {
if (current.isFile && current.path != selectedFile?.path &&
!changeListManager.isIgnoredFile(current) &&
runBlocking { !featureDevSessionContext.ignoreFile(current) } &&
runBlocking { !gitIgnoreFilteringUtil.ignoreFile(current) } &&
runReadAction { !fileIndex.isInLibrarySource(current) }
) {
if (willExceedPayloadLimit(currentTotalFileSize, current.length)) {
Expand All @@ -217,7 +216,7 @@ class CodeTestSessionConfig(
} else {
// Directory case: only traverse if not ignored
if (!changeListManager.isIgnoredFile(current) &&
runBlocking { !featureDevSessionContext.ignoreFile(current) } &&
runBlocking { !gitIgnoreFilteringUtil.ignoreFile(current) } &&
!traversedDirectories.contains(current) && current.isValid &&
runReadAction { !fileIndex.isInLibrarySource(current) }
) {
Expand All @@ -242,21 +241,14 @@ class CodeTestSessionConfig(
return PayloadMetadata(files, currentTotalFileSize, currentTotalLines, maxCountLanguage.toTelemetryType())
}

fun getPath(root: String, relativePath: String = ""): Path? = try {
Path.of(root, relativePath).normalize()
} catch (e: Exception) {
LOG.debug { "Cannot find file at path $relativePath relative to the root $root" }
null
}

fun getRelativePath(): Path? = try {
selectedFile?.path?.let { Path.of(projectRoot.path).relativize(Path.of(it)).normalize() }
} catch (e: Exception) {
LOG.debug { "Cannot calculate relative path of $selectedFile with respect to $projectRoot" }
null
}

fun File.toVirtualFile() = LocalFileSystem.getInstance().findFileByIoFile(this)
private fun File.toVirtualFile() = LocalFileSystem.getInstance().findFileByIoFile(this)

companion object {
private val LOG = getLogger<CodeTestSessionConfig>()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.codewhisperer.util

import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import kotlinx.coroutines.async
import kotlinx.coroutines.withContext
import java.nio.file.Paths
import kotlin.coroutines.coroutineContext
import kotlin.io.path.relativeTo

class GitIgnoreFilteringUtil(private val moduleDir: VirtualFile) {
private var ignorePatternsWithGitIgnore = emptyList<Regex>()
private val additionalGitIgnoreRules = setOf(
".aws-sam",
".gem",
".git",
".gitignore",
".gradle",
".hg",
".idea",
".project",
".rvm",
".svn",
"*.zip",
"*.bin",
"*.png",
"*.jpg",
"*.svg",
"*.pyc",
"license.txt",
"License.txt",
"LICENSE.txt",
"license.md",
"License.md",
"LICENSE.md",
"node_modules",
"build",
"dist",
"annotation-generated-src",
"annotation-generated-tst"
)

init {
ignorePatternsWithGitIgnore = try {
buildList {
addAll(additionalGitIgnoreRules.map { convertGitIgnorePatternToRegex(it) })
addAll(parseGitIgnore())
}.mapNotNull { pattern ->
runCatching { Regex(pattern) }.getOrNull()
}
} catch (e: Exception) {
emptyList()
}
}

private fun parseGitIgnore(): Set<String> {
val gitignoreFile = moduleDir.findChild(".gitignore")
return gitignoreFile?.let {
if (it.isValid && it.exists()) {
it.inputStream.bufferedReader().readLines()
.filter { line ->
line.isNotBlank() && !line.startsWith("#")
}
.map { pattern ->
convertGitIgnorePatternToRegex(pattern.trim())
}
.toSet()
} else {
emptySet()
}
} ?: emptySet()
}

// gitignore patterns are not regex, method update needed.
private fun convertGitIgnorePatternToRegex(pattern: String): String = pattern
.replace(".", "\\.")
.replace("*", ".*")
.let { if (it.endsWith("/")) "$it.*" else "$it/.*" } // Add a trailing /* to all patterns. (we add a trailing / to all files when matching)

suspend fun ignoreFile(file: VirtualFile): Boolean = ignoreFile(file.presentableUrl)

suspend fun ignoreFile(path: String): Boolean {

Check notice on line 85 in plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/GitIgnoreFilteringUtil.kt

View workflow job for this annotation

GitHub Actions / qodana

Class member can have 'private' visibility

Function 'ignoreFile' could be private
// this method reads like something a JS dev would write and doesn't do what the author thinks
val deferredResults = ignorePatternsWithGitIgnore.map { pattern ->
withContext(coroutineContext) {
// avoid partial match (pattern.containsMatchIn) since it causes us matching files
// against folder patterns. (e.g. settings.gradle ignored by .gradle rule!)
// we convert the glob rules to regex, add a trailing /* to all rules and then match
// entries against them by adding a trailing /.
// TODO: Add unit tests for gitignore matching
val relative = if (path.startsWith(moduleDir.presentableUrl)) Paths.get(path).relativeTo(moduleDir.toNioPath()) else path
async { pattern.matches("$relative/") }
}
}

// this will serially iterate over and block
// ideally we race the results https://github.com/Kotlin/kotlinx.coroutines/issues/2867
// i.e. Promise.any(...)
return deferredResults.any { it.await() }
}

private fun getRelativePath(file: VirtualFile): String = VfsUtil.getRelativePath(file, moduleDir) ?: ""

Check warning on line 105 in plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/GitIgnoreFilteringUtil.kt

View workflow job for this annotation

GitHub Actions / qodana

Unused symbol

Function "getRelativePath" is never used
}
Loading
Loading