Repository: mgechev/revive
Pull request #1399 (2025-06-05 12:31:23)

unexported_return: exclude main package and test files
The content of these files cannot be imported by other packages,
so they are excluded from the `unexported_return` rule.

Related to https://github.com/mgechev/revive/issues/1397#issuecomment-2943897721



Patch:
diff --git a/lint/file.go b/lint/file.go
index a83bcad..bf6aed4 100644
--- a/lint/file.go
+++ b/lint/file.go
@@ -24,6 +24,23 @@ type File struct {
 // IsTest returns if the file contains tests.
 func (f *File) IsTest() bool { return strings.HasSuffix(f.Name, "_test.go") }
 
+// IsImportable returns if the symbols defined in this file can be imported in other packages.
+//
+// Symbols from the package `main` or test files are not exported, so they cannot be imported.
+func (f *File) IsImportable() bool {
+	if f.IsTest() {
+		// Test files cannot be imported.
+		return false
+	}
+
+	if f.Pkg.IsMain() {
+		// The package `main` cannot be imported.
+		return false
+	}
+
+	return true
+}
+
 // Content returns the file's content.
 func (f *File) Content() []byte {
 	return f.content
diff --git a/rule/unexported_return.go b/rule/unexported_return.go
index ab619a7..7a51d71 100644
--- a/rule/unexported_return.go
+++ b/rule/unexported_return.go
@@ -14,6 +14,12 @@ type UnexportedReturnRule struct{}
 
 // Apply applies the rule to given file.
 func (*UnexportedReturnRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
+	if !file.IsImportable() {
+		// Symbols defined in such files cannot be used in other packages.
+		// Therefore, we don't need to check for unexported return types.
+		return nil
+	}
+
 	var failures []lint.Failure
 
 	file.Pkg.TypeCheck()


Test patch:
diff --git a/test/unexported_return_test.go b/test/unexported_return_test.go
new file mode 100644
index 0000000..1516885
--- /dev/null
+++ b/test/unexported_return_test.go
@@ -0,0 +1,16 @@
+package test
+
+import (
+	"testing"
+
+	"github.com/mgechev/revive/rule"
+)
+
+func TestUnexportedReturn(t *testing.T) {
+	testRule(t, "unexported_return_package_foo", &rule.UnexportedReturnRule{})
+	testRule(t, "unexported_return_package_foo_test", &rule.UnexportedReturnRule{})
+	testRule(t, "unexported_return_package_footest_test", &rule.UnexportedReturnRule{})
+	testRule(t, "unexported_return_package_main", &rule.UnexportedReturnRule{})
+	testRule(t, "unexported_return_package_main_test", &rule.UnexportedReturnRule{})
+	testRule(t, "unexported_return_package_maintest_test", &rule.UnexportedReturnRule{})
+}
diff --git a/testdata/unexported_return_package_foo.go b/testdata/unexported_return_package_foo.go
new file mode 100644
index 0000000..5c97013
--- /dev/null
+++ b/testdata/unexported_return_package_foo.go
@@ -0,0 +1,12 @@
+package foo
+
+// this is a file in a package
+//
+// such files SHOULD be linted by unexported_return rule
+// because symbols defined in test files cannot be used in other packages
+
+type foo struct{}
+
+func NewFoo() foo { // MATCH /exported func NewFoo returns unexported type foo.foo, which can be annoying to use/
+	return foo{}
+}
diff --git a/testdata/unexported_return_package_foo_test.go b/testdata/unexported_return_package_foo_test.go
new file mode 100644
index 0000000..7eddf74
--- /dev/null
+++ b/testdata/unexported_return_package_foo_test.go
@@ -0,0 +1,12 @@
+package foo
+
+// this is a test file in a package
+//
+// such files SHOULD NOT be linted by unexported_return rule
+// because symbols defined in test files cannot be used in other packages
+
+type foo struct{}
+
+func NewFoo() foo {
+	return foo{}
+}
diff --git a/testdata/unexported_return_package_footest_test.go b/testdata/unexported_return_package_footest_test.go
new file mode 100644
index 0000000..5a8d292
--- /dev/null
+++ b/testdata/unexported_return_package_footest_test.go
@@ -0,0 +1,12 @@
+package foo_test
+
+// this is a test file in a _test package
+//
+// such files SHOULD NOT be linted by unexported_return rule
+// because symbols defined in test files cannot be used in other packages
+
+type foo struct{}
+
+func NewFoo() foo {
+	return foo{}
+}
diff --git a/testdata/unexported_return_package_main.go b/testdata/unexported_return_package_main.go
new file mode 100644
index 0000000..9f4568c
--- /dev/null
+++ b/testdata/unexported_return_package_main.go
@@ -0,0 +1,12 @@
+package main
+
+// this is a file in the main package
+//
+// such files SHOULD NOT be linted by unexported_return rule
+// because symbols defined in main package cannot be imported in other packages
+
+type foo struct{}
+
+func NewFoo() foo {
+	return foo{}
+}
diff --git a/testdata/unexported_return_package_main_test.go b/testdata/unexported_return_package_main_test.go
new file mode 100644
index 0000000..e2df4d9
--- /dev/null
+++ b/testdata/unexported_return_package_main_test.go
@@ -0,0 +1,12 @@
+package main
+
+// this is a test file in the main package
+//
+// such files SHOULD NOT be linted by unexported_return rule
+// because symbols defined in main package cannot be imported in other packages
+
+type foo struct{}
+
+func NewFoo() foo {
+	return foo{}
+}
diff --git a/testdata/unexported_return_package_maintest_test.go b/testdata/unexported_return_package_maintest_test.go
new file mode 100644
index 0000000..25ce09d
--- /dev/null
+++ b/testdata/unexported_return_package_maintest_test.go
@@ -0,0 +1,12 @@
+package main_test
+
+// this is a test file in the main_test package
+//
+// such files SHOULD NOT be linted by unexported_return rule
+// because symbols defined in main package cannot be imported in other packages
+
+type foo struct{}
+
+func NewFoo() foo {
+	return foo{}
+}