Repository: mgechev/revive
Pull request #441 (2020-07-14 20:06:22)

Update golang.org/x/tools commit hash to bd1e9de
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [golang.org/x/tools](https://togithub.com/golang/tools) | require | digest | `6acd2ab` -> `bd1e9de` |

---

### Renovate configuration

:date: **Schedule**: At any time (no schedule defined).

:vertical_traffic_light: **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

:recycle: **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

:no_bell: **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#mgechev/revive).


Patch:
diff --git a/README.md b/README.md
index e3ce248..db02f15 100644
--- a/README.md
+++ b/README.md
@@ -349,6 +349,7 @@ List of all available rules. The rules ported from `golint` are left unchanged a
 | [`unconditional-recursion`](./RULES_DESCRIPTIONS.md#unconditional-recursion)          |  n/a   | Warns on function calls that will lead to (direct) infinite recursion |    no    |  no   |
 | [`identical-branches`](./RULES_DESCRIPTIONS.md#identical-branches)          |  n/a   | Spots if-then-else statements with identical `then` and `else` branches       |    no    |  no   |
 | [`defer`](./RULES_DESCRIPTIONS.md#defer)          |  map   |  Warns on some [defer gotchas](https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-iii-36a1ab3d6ef1)       |    no    |  no   |
+| [`unexported-naming`](./RULES_DESCRIPTIONS.md#unexported-naming)          |  n/a   |  Warns on wrongly named un-exported symbols       |    no    |  no   |
 
 ## Configurable rules
 
diff --git a/RULES_DESCRIPTIONS.md b/RULES_DESCRIPTIONS.md
index 62fd2a5..b7dfd76 100644
--- a/RULES_DESCRIPTIONS.md
+++ b/RULES_DESCRIPTIONS.md
@@ -57,6 +57,7 @@ List of all available rules.
   - [var-naming](#var-naming)
   - [var-declaration](#var-declaration)
   - [unconditional-recursion](#unconditional-recursion)
+  - [unexported-naming](#unexported-naming)
   - [unexported-return](#unexported-return)
   - [unhandled-error](#unhandled-error)
   - [unnecessary-stmt](#unnecessary-stmt)
@@ -517,6 +518,12 @@ _Description_: Unconditional recursive calls will produce infinite recursion, th
 
 _Configuration_: N/A
 
+## unexported-naming
+
+_Description_: this rule warns on wrongly named un-exported symbols, i.e. un-exported symbols whose name start with a capital letter.
+
+_Configuration_: N/A
+
 ## unexported-return
 
 _Description_: This rule warns when an exported function or method returns a value of an un-exported type.
diff --git a/config.go b/config.go
index 65f5bf9..5a45377 100644
--- a/config.go
+++ b/config.go
@@ -88,6 +88,7 @@ var allRules = append([]lint.Rule{
 	&rule.UnconditionalRecursionRule{},
 	&rule.IdenticalBranchesRule{},
 	&rule.DeferRule{},
+	&rule.UnexportedNamingRule{},
 }, defaultRules...)
 
 var allFormatters = []lint.Formatter{
diff --git a/go.mod b/go.mod
index be9d1e2..d22d35f 100644
--- a/go.mod
+++ b/go.mod
@@ -10,5 +10,5 @@ require (
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/olekukonko/tablewriter v0.0.4
 	github.com/pkg/errors v0.9.1
-	golang.org/x/tools v0.0.0-20200713235242-6acd2ab80ede
+	golang.org/x/tools v0.0.0-20200722181740-bd1e9de8d890
 )
diff --git a/go.sum b/go.sum
index 5cebab1..d538046 100644
--- a/go.sum
+++ b/go.sum
@@ -89,6 +89,8 @@ golang.org/x/tools v0.0.0-20200702044944-0cc1aa72b347 h1:/e4fNMHdLn7SQSxTrRZTma2
 golang.org/x/tools v0.0.0-20200702044944-0cc1aa72b347/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200713235242-6acd2ab80ede h1:ItmFoZpZfJTYGsnON6247QuXEOKiKLBpieGcAgweGsk=
 golang.org/x/tools v0.0.0-20200713235242-6acd2ab80ede/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200722181740-bd1e9de8d890 h1:Fwx9UWtbBIMKQ+hdL1ltEyRaLkbpvN+ii5XUAz9o2n8=
+golang.org/x/tools v0.0.0-20200722181740-bd1e9de8d890/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
diff --git a/rule/unexported-naming.go b/rule/unexported-naming.go
new file mode 100644
index 0000000..96cec3e
--- /dev/null
+++ b/rule/unexported-naming.go
@@ -0,0 +1,115 @@
+package rule
+
+import (
+	"fmt"
+	"go/ast"
+	"go/token"
+
+	"github.com/mgechev/revive/lint"
+)
+
+// UnexportedNamingRule lints wrongly named unexported symbols.
+type UnexportedNamingRule struct{}
+
+// Apply applies the rule to given file.
+func (r *UnexportedNamingRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
+	var failures []lint.Failure
+	onFailure := func(failure lint.Failure) {
+		failures = append(failures, failure)
+	}
+
+	ba := &unexportablenamingLinter{onFailure}
+	ast.Walk(ba, file.AST)
+
+	return failures
+}
+
+// Name returns the rule name.
+func (r *UnexportedNamingRule) Name() string {
+	return "unexported-naming"
+}
+
+type unexportablenamingLinter struct {
+	onFailure func(lint.Failure)
+}
+
+func (unl unexportablenamingLinter) Visit(node ast.Node) ast.Visitor {
+	switch n := node.(type) {
+	case *ast.FuncDecl:
+		unl.lintFunction(n.Type, n.Body)
+		return nil
+	case *ast.FuncLit:
+		unl.lintFunction(n.Type, n.Body)
+
+		return nil
+	case *ast.AssignStmt:
+		if n.Tok != token.DEFINE {
+			return nil
+		}
+
+		ids := []*ast.Ident{}
+		for _, e := range n.Lhs {
+			id, ok := e.(*ast.Ident)
+			if !ok {
+				continue
+			}
+			ids = append(ids, id)
+		}
+
+		unl.lintIDs(ids)
+
+	case *ast.DeclStmt:
+		gd, ok := n.Decl.(*ast.GenDecl)
+		if !ok {
+			return nil
+		}
+
+		if len(gd.Specs) < 1 {
+			return nil
+		}
+
+		vs, ok := gd.Specs[0].(*ast.ValueSpec)
+		if !ok {
+			return nil
+		}
+
+		unl.lintIDs(vs.Names)
+	}
+
+	return unl
+}
+
+func (unl unexportablenamingLinter) lintFunction(ft *ast.FuncType, body *ast.BlockStmt) {
+	unl.lintFields(ft.Params)
+	unl.lintFields(ft.Results)
+
+	if body != nil {
+		ast.Walk(unl, body)
+	}
+}
+
+func (unl unexportablenamingLinter) lintFields(fields *ast.FieldList) {
+	if fields == nil {
+		return
+	}
+
+	ids := []*ast.Ident{}
+	for _, field := range fields.List {
+		ids = append(ids, field.Names...)
+	}
+
+	unl.lintIDs(ids)
+}
+
+func (unl unexportablenamingLinter) lintIDs(ids []*ast.Ident) {
+	for _, id := range ids {
+		if id.IsExported() {
+			unl.onFailure(lint.Failure{
+				Node:       id,
+				Confidence: 1,
+				Category:   "naming",
+				Failure:    fmt.Sprintf("the symbol %s is local, its name should start with a lowercase letter", id.String()),
+			})
+		}
+	}
+}


Test patch:
diff --git a/test/unxeported-naming_test.go b/test/unxeported-naming_test.go
new file mode 100644
index 0000000..d689bef
--- /dev/null
+++ b/test/unxeported-naming_test.go
@@ -0,0 +1,11 @@
+package test
+
+import (
+	"testing"
+
+	"github.com/mgechev/revive/rule"
+)
+
+func TestUnexportednaming(t *testing.T) {
+	testRule(t, "unexported-naming", &rule.UnexportedNamingRule{})
+}
diff --git a/testdata/unexported-naming.go b/testdata/unexported-naming.go
new file mode 100644
index 0000000..880a3d7
--- /dev/null
+++ b/testdata/unexported-naming.go
@@ -0,0 +1,21 @@
+package fixtures
+
+var unexported string
+var Exported string
+
+func unexportednaming(
+	S int, // MATCH /the symbol S is local, its name should start with a lowercase letter/
+	s int,
+) (
+	Result bool, // MATCH /the symbol Result is local, its name should start with a lowercase letter/
+	result bool,
+) {
+	var NotExportable int // MATCH /the symbol NotExportable is local, its name should start with a lowercase letter/
+	var local float32
+	{
+		OtherNotExportable := 0 // MATCH /the symbol OtherNotExportable is local, its name should start with a lowercase letter/
+	}
+	const NotExportableConstant = "local" // MATCH /the symbol NotExportableConstant is local, its name should start with a lowercase letter/
+
+	return
+}