Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into feature/builtin_pkg
Browse files Browse the repository at this point in the history
  • Loading branch information
ashanbrown committed Jan 25, 2025
2 parents ee97ef5 + c0fe523 commit 0fb3880
Show file tree
Hide file tree
Showing 11 changed files with 138 additions and 126 deletions.
13 changes: 0 additions & 13 deletions .circleci/config.yml

This file was deleted.

26 changes: 26 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Build

on: [push]

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [ '1.18', '1.23.x' ]

steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- uses: pre-commit/[email protected]
- name: Setup Go ${{ matrix.go-version }}
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Run Go tests
run: go test ./...
- name: Run make tests
run: make test
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
repos:
- repo: https://github.com/golangci/golangci-lint
rev: v1.50.1
rev: v1.63.4
hooks:
- id: golangci-lint
94 changes: 50 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# forbidigo

[![CircleCI](https://dl.circleci.com/status-badge/img/gh/ashanbrown/forbidigo/tree/master.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/ashanbrown/forbidigo/tree/master)

`forbidigo` is a Go static analysis tool to forbidigo use of particular identifiers.
[![Github Actions](https://github.com/ashanbrown/forbidigo/actions/workflows/build.yml/badge.svg)](https://github.com/ashanbrown/forbidigo/actions/workflows/build.yml?query=branch%3Amaster)

`forbidigo` is recommended to be run as part of [golangci-lint](https://github.com/golangci/golangci-lint) where it can be controlled using file-based configuration and `//nolint` directives, but it can also be run as a standalone tool.

## Installation

go get -u github.com/ashanbrown/forbidigo
```
go get -u github.com/ashanbrown/forbidigo
```

## Usage

forbidigo [flags...] patterns... -- packages...
```
forbidigo [flags...] patterns... -- packages...
```

If no patterns are specified, the default pattern of `^(fmt\.Print.*|print|println)$` is used to eliminate debug statements. By default,
functions (and whole files), that are identifies as Godoc examples (https://blog.golang.org/examples) are excluded from
Expand All @@ -36,57 +36,62 @@ Replacing the literal source code works for items in a package as in the
`<package name>.<type name>.<field or method name>` replaces the source code
text. `<package name>` is what the package declares in its `package` statement,
which may be different from last part of the import path:

import "example.com/some/pkg" // pkg uses `package somepkg`
s := somepkg.SomeStruct{}
s.SomeMethod() // -> somepkg.SomeStruct.SomeMethod
```go
import "example.com/some/pkg" // pkg uses `package somepkg`
s := somepkg.SomeStruct{}
s.SomeMethod() // -> somepkg.SomeStruct.SomeMethod
```

Pointers are treated like the type they point to:

var cf *spew.ConfigState = ...
cf.Dump() // -> spew.ConfigState.Dump
```go
var cf *spew.ConfigState = ...
cf.Dump() // -> spew.ConfigState.Dump
```

When a type is an alias for a type in some other package, the name of that
other package will be used.

An imported identifier gets replaced as if it had been imported without `import .`
*and* also gets matched literally, so in this example both `^ginkgo.FIt$`
and `^FIt$` would catch the usage of `FIt`:

import . "github.com/onsi/ginkgo/v2"
FIt(...) // -> ginkgo.FIt, FIt
```go
import . "github.com/onsi/ginkgo/v2"
FIt(...) // -> ginkgo.FIt, FIt
```

Beware that looking up the package name has limitations. When a struct embeds
some other type, references to the inherited fields or methods get resolved
with the outer struct as type:
```go
package foo

package foo

type InnerStruct {
SomeField int
}
type InnerStruct {
SomeField int
}

func (i innerStruct) SomeMethod() {}
func (i innerStruct) SomeMethod() {}

type OuterStruct {
InnerStruct
}
type OuterStruct {
InnerStruct
}

s := OuterStruct{}
s.SomeMethod() // -> foo.OuterStruct.SomeMethod
i := s.SomeField // -> foo.OuterStruct.SomeField
s := OuterStruct{}
s.SomeMethod() // -> foo.OuterStruct.SomeMethod
i := s.SomeField // -> foo.OuterStruct.SomeField
```

When a method gets called via some interface, that invocation also only
gets resolved to the interface, not the underlying implementation:
```go
// innerStruct as above

// innerStruct as above
type myInterface interface {
SomeMethod()
}

type myInterface interface {
SomeMethod()
}

var i myInterface = InnerStruct{}
i.SomeMethod() // -> foo.myInterface.SomeMethod
var i myInterface = InnerStruct{}
i.SomeMethod() // -> foo.myInterface.SomeMethod
```

Using the package name is simple, but the name is not necessarily unique. For
more advanced cases, it is possible to specify more complex patterns. Such
Expand All @@ -106,17 +111,18 @@ To distinguish such patterns from traditional regular expression patterns, the
encoding must start with a `{` or contain line breaks. When using just JSON
encoding, backslashes must get quoted inside strings. When using YAML, this
isn't necessary. The following pattern strings are equivalent:
```
{p: "^fmt\\.Println$", msg: "do not write to stdout"}
{p: "^fmt\\.Println$", msg: "do not write to stdout"}

{p: ^fmt\.Println$,
msg: do not write to stdout,
}
{p: ^fmt\.Println$,
msg: do not write to stdout,
}
{p: ^fmt\.Println$, msg: do not write to stdout}
{p: ^fmt\.Println$, msg: do not write to stdout}
p: ^fmt\.Println$
msg: do not write to stdout
p: ^fmt\.Println$
msg: do not write to stdout
```

A larger set of interesting patterns might include:

Expand Down
28 changes: 23 additions & 5 deletions forbidigo/patterns.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package forbidigo

import (
"bytes"
"fmt"
"io"
"regexp"
"regexp/syntax"
"strings"

"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)

// pattern matches code that is not supposed to be used.
Expand All @@ -33,15 +35,15 @@ type pattern struct {
// patterns).
type yamlPattern pattern

func (p *yamlPattern) UnmarshalYAML(unmarshal func(interface{}) error) error {
func (p *yamlPattern) UnmarshalYAML(value *yaml.Node) error {
// Try struct first. It's unlikely that a regular expression string
// is valid YAML for a struct.
var ptrn pattern
if err := unmarshal(&ptrn); err != nil {
if err := unmarshalStrict(&ptrn, value); err != nil && err != io.EOF {
errStr := err.Error()
// Didn't work, try plain string.
var ptrn string
if err := unmarshal(&ptrn); err != nil {
if err := unmarshalStrict(&ptrn, value); err != nil && err != io.EOF {
return fmt.Errorf("pattern is neither a regular expression string (%s) nor a Pattern struct (%s)", err.Error(), errStr)
}
p.Pattern = ptrn
Expand All @@ -51,6 +53,20 @@ func (p *yamlPattern) UnmarshalYAML(unmarshal func(interface{}) error) error {
return ((*pattern)(p)).validate()
}

// unmarshalStrict implements missing yaml.UnmarshalStrict in gopkg.in/yaml.v3.
// See https://github.com/go-yaml/yaml/issues/639.
// Inspired by https://github.com/ffenix113/zigbee_home/pull/68
func unmarshalStrict(to any, node *yaml.Node) error {
buf := &bytes.Buffer{}
if err := yaml.NewEncoder(buf).Encode(node); err != nil {
return err
}

decoder := yaml.NewDecoder(buf)
decoder.KnownFields(true)
return decoder.Decode(to)
}

var _ yaml.Unmarshaler = &yamlPattern{}

// parse accepts a regular expression or, if the string starts with { or contains a line break, a
Expand All @@ -61,7 +77,9 @@ func parse(ptrn string) (*pattern, error) {
if strings.HasPrefix(strings.TrimSpace(ptrn), "{") ||
strings.Contains(ptrn, "\n") {
// Embedded JSON or YAML. We can decode both with the YAML decoder.
if err := yaml.UnmarshalStrict([]byte(ptrn), pattern); err != nil {
decoder := yaml.NewDecoder(strings.NewReader(ptrn))
decoder.KnownFields(true)
if err := decoder.Decode(pattern); err != nil && err != io.EOF {
return nil, fmt.Errorf("parsing as JSON or YAML failed: %v", err)
}
} else {
Expand Down
17 changes: 9 additions & 8 deletions forbidigo/patterns_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package forbidigo

import (
"bytes"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)

func TestParseValidPatterns(t *testing.T) {
Expand Down Expand Up @@ -113,9 +114,8 @@ func TestUnmarshalYAML(t *testing.T) {
expectedPattern: `fmt\.Errorf`,
},
{
name: "match import with YAML",
yaml: `p: ^fmt\.Println$
`,
name: "match import with YAML",
yaml: `p: ^fmt\.Println$ `,
expectedPattern: `^fmt\.Println$`,
},
{
Expand All @@ -124,9 +124,8 @@ func TestUnmarshalYAML(t *testing.T) {
expectedErr: "unable to compile source code pattern `fmt\\`: error parsing regexp: trailing backslash at end of expression: ``",
},
{
name: "struct: invalid regexp",
yaml: `p: fmt\
`,
name: "struct: invalid regexp",
yaml: `p: fmt\ `,
expectedErr: "unable to compile source code pattern `fmt\\`: error parsing regexp: trailing backslash at end of expression: ``",
},
{
Expand All @@ -139,7 +138,9 @@ func TestUnmarshalYAML(t *testing.T) {
} {
t.Run(tc.name, func(t *testing.T) {
var p yamlPattern
err := yaml.UnmarshalStrict([]byte(tc.yaml), &p)
decoder := yaml.NewDecoder(bytes.NewReader([]byte(tc.yaml)))
decoder.KnownFields(true)
err := decoder.Decode(&p)
if tc.expectedErr == "" {
require.NoError(t, err)
} else {
Expand Down
20 changes: 12 additions & 8 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
module github.com/ashanbrown/forbidigo
module github.com/ashanbrown/forbidigo/v2

go 1.13
go 1.18

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/go-cmp v0.5.6
github.com/stretchr/testify v1.5.1
golang.org/x/sys v0.5.0 // indirect
golang.org/x/tools v0.3.0
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/yaml.v2 v2.4.0
github.com/stretchr/testify v1.6.0
golang.org/x/tools v0.13.0
gopkg.in/yaml.v3 v3.0.1
)

require (
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/sys v0.12.0 // indirect
)
Loading

0 comments on commit 0fb3880

Please sign in to comment.