Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions java/gazelle/testdata/kt_fqn_deps/BUILD.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# gazelle:jvm_kotlin_enabled true
1 change: 1 addition & 0 deletions java/gazelle/testdata/kt_fqn_deps/BUILD.out
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# gazelle:jvm_kotlin_enabled true
13 changes: 13 additions & 0 deletions java/gazelle/testdata/kt_fqn_deps/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Kotlin fully qualified name dependencies

Test that Gazelle detects dependencies from fully qualified class references
used directly in Kotlin expressions, without corresponding import statements.

When Kotlin code uses FQN constructor calls like
`com.example.errors.CustomError(e)` instead of importing `CustomError`, the
parser must still recognize the cross-package dependency. This is common when
there are name conflicts (e.g., a custom `InterruptedException` alongside
`java.util.concurrent.InterruptedException`).

The `app/src` package uses a FQN constructor call to `com.example.errors.CustomError`
and should get a `deps` entry for `//errors/src`.
19 changes: 19 additions & 0 deletions java/gazelle/testdata/kt_fqn_deps/WORKSPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

rules_kotlin_version = "1.9.6"

rules_kotlin_sha = "3b772976fec7bdcda1d84b9d39b176589424c047eb2175bed09aac630e50af43"

http_archive(
name = "rules_kotlin",
sha256 = rules_kotlin_sha,
urls = ["https://github.com/bazelbuild/rules_kotlin/releases/download/v%s/rules_kotlin-v%s.tar.gz" % (rules_kotlin_version, rules_kotlin_version)],
)

load("@rules_kotlin//kotlin:repositories.bzl", "kotlin_repositories")

kotlin_repositories()

load("@rules_kotlin//kotlin:core.bzl", "kt_register_toolchains")

kt_register_toolchains()
9 changes: 9 additions & 0 deletions java/gazelle/testdata/kt_fqn_deps/app/src/App.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.app

fun doWork() {
try {
// work
} catch (e: Exception) {
throw com.example.errors.CustomError(e)
}
}
Empty file.
8 changes: 8 additions & 0 deletions java/gazelle/testdata/kt_fqn_deps/app/src/BUILD.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")

kt_jvm_library(
name = "src",
srcs = ["App.kt"],
visibility = ["//:__subpackages__"],
deps = ["//errors/src"],
)
Empty file.
7 changes: 7 additions & 0 deletions java/gazelle/testdata/kt_fqn_deps/errors/src/BUILD.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")

kt_jvm_library(
name = "src",
srcs = ["CustomError.kt"],
visibility = ["//:__subpackages__"],
)
3 changes: 3 additions & 0 deletions java/gazelle/testdata/kt_fqn_deps/errors/src/CustomError.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.example.errors

class CustomError(cause: Exception) : RuntimeException(cause)
1 change: 1 addition & 0 deletions java/gazelle/testdata/kt_fqn_deps/maven_install.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"version": "2"}
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,29 @@ public void visitDotQualifiedExpression(KtDotQualifiedExpression expression) {
String functionName = callExpr.getCalleeExpression().getText();

checkExtensionFunctionCall(receiverType, functionName);

// Detect FQN constructor call: com.example.ClassName(args)
// Mirrors ClasspathParser.visitNewClass → checkFullyQualifiedType
if (isLikelyClassName(functionName) && receiverType.contains(".")) {
String fqClassName = receiverType + "." + functionName;
packageData.usedTypes.add(fqClassName);
}
}
}

// Detect FQN class reference: com.example.ClassName (as selector of a DQE)
// Mirrors ClasspathParser.visitMethodInvocation + looksLikeClassName
if (selectorExpression instanceof KtSimpleNameExpression) {
String selectorName = ((KtSimpleNameExpression) selectorExpression).getReferencedName();
if (isLikelyClassName(selectorName)) {
KtExpression receiverExpr = expression.getReceiverExpression();
if (receiverExpr != null) {
String receiverText = receiverExpr.getText();
if (receiverText.contains(".")) {
String fqClassName = receiverText + "." + selectorName;
packageData.usedTypes.add(fqClassName);
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,22 +148,25 @@ public void constantTest() throws IOException {
data.perClassData.keySet());
}

// @Test
// public void fullyQualifiedClassAndFunctionUse() throws IOException {
// ParsedPackageData data = parser.parseClasses(getPathsWithNames("FullyQualifieds.kt"));
// assertEquals(
// Set.of("com.example"),
// data.usedPackagesWithoutSpecificTypes);
// assertEquals(
// Set.of(
// "workspace.com.gazelle.java.javaparser.generators.DeleteBookRequest",
// "workspace.com.gazelle.java.javaparser.generators.DeleteBookResponse",
// "workspace.com.gazelle.java.javaparser.utils.Printer",
// "workspace.com.gazelle.java.javaparser.factories.Factory",
// "java.util.ArrayList",
// "com.example.PrivateArg"),
// data.usedTypes);
// }
@Test
public void detectsFqnClassReferences() throws IOException {
ParsedPackageData data = parser.parseClasses(getPathsWithNames("FullyQualifieds.kt"));

// FQN constructor calls and class references detected via visitDotQualifiedExpression
assertTrue(
data.usedTypes.contains(
"workspace.com.gazelle.java.javaparser.generators.DeleteBookRequest"),
"Should detect FQN constructor call: " + data.usedTypes);
assertTrue(
data.usedTypes.contains("workspace.com.gazelle.java.javaparser.utils.Printer"),
"Should detect FQN class reference (static method call): " + data.usedTypes);
assertTrue(
data.usedTypes.contains("workspace.com.gazelle.java.javaparser.factories.Factory"),
"Should detect FQN class reference (factory call): " + data.usedTypes);
assertTrue(
data.usedTypes.contains("java.util.ArrayList"),
"Should detect FQN generic constructor call: " + data.usedTypes);
}

@Test
public void staticImportsTest() throws IOException {
Expand Down
Loading