Coder Social home page Coder Social logo

faillint's Introduction

faillint

Faillint is a simple Go linter that fails when a specific set of import paths or exported path's functions, constant, vars or types are used. It's meant to be used in CI/CD environments to catch rules you want to enforce in your projects.

As an example, you could enforce the usage of github.com/pkg/errors instead of the errors package. To prevent the usage of the errors package, you can configure faillint to fail whenever someone imports the errors package in this case. To make sure fmt.Errorf is not used for creating errors as well, you can configure to fail on such single function of fmt as well.

faillint

Install

go install github.com/fatih/faillint@latest

Example

Assume we have the following file:

package a

import (
        "errors"
)

func foo() error {
        return errors.New("bar!")
}

Let's run faillint to check if errors import is used and report it:

$ faillint -paths "errors=github.com/pkg/errors" a.go
a.go:4:2: package "errors" shouldn't be imported, suggested: "github.com/pkg/errors"

Usage

faillint works on a file, directory or a Go package:

$ faillint -paths "errors,fmt.{Errorf}" foo.go # pass a file
$ faillint -paths "errors,fmt.{Errorf}" ./...  # recursively analyze all files
$ faillint -paths "errors,fmt.{Errorf}" github.com/fatih/gomodifytags # or pass a package

By default, faillint will not check any import paths. You need to explicitly define it with the -paths flag, which is comma-separated list. Some examples are:

# Fail if the errors package is used.
-paths "errors"

# Fail if the old context package is imported.
-paths "golang.org/x/net/context"

# Fail both on stdlib log and errors package to enforce other internal libraries.
-paths "log,errors"

# Fail if any of Print, Printf of Println function were used from fmt library.
-paths "fmt.{Print,Printf,Println}"

# Fail if the package is imported including sub paths starting with
  "golang.org/x/net/". In example: `golang.org/x/net/context`, 
  `golang.org/x/net/nettest`, .nettest`, ...
-paths "golang.org/x/net/..."

If you have a preferred import path to suggest, append the suggestion after a = character:

# Fail if the errors package is used and suggest to use github.com/pkg/errors.
-paths "errors=github.com/pkg/errors"

# Fail for the old context import path and suggest to use the stdlib context.
-paths "golang.org/x/net/context=context"

# Fail both on stdlib log and errors package to enforce other libraries.
-paths "log=go.uber.org/zap,errors=github.com/pkg/errors"

# Fail on fmt.Errorf and suggest the Errorf function from github.compkg/errors instead.
-paths "fmt.{Errorf}=github.com/pkg/errors.{Errorf}"

Ignoring problems

If you want to ignore a problem reported by faillint you can add a lint directive based on staticcheck's design.

Line-based lint directives

Line-based lint directives can be applied to imports or functions you want to tolerate. The format is,

//lint:ignore faillint reason

For example,

package a

import (
        //lint:ignore faillint Whatever your reason is.
        "errors"
        "fmt" //lint:ignore faillint Whatever your reason is.
)

func foo() error {
        //lint:ignore faillint Whatever your reason is.
        return errors.New("bar!")
}

File-based lint directives

File-based lint directives can be applied to ignore faillint problems in a whole file. The format is,

//lint:file-ignore faillint reason

This may be placed anywhere in the file but conventionally it should be placed at, or near, the top of the file.

For example,

//lint:file-ignore faillint This file should be ignored by faillint.

package a

import (
        "errors"
)

func foo() error {
        return errors.New("bar!")
}

The need for this tool?

Most of these checks should be probably detected during the review cycle. But it's totally normal to accidentally import them (we're all humans in the end).

Second, tools like goimports favors certain packages. As an example going forward if you decided to use github.com/pkg/errors in you project, and write errors.New() in a new file, goimports will automatically import the errors package (and not github.com/pkg/errors). The code will perfectly compile. faillint would be able to detect and report it to you.

Credits

This tool is built on top of the excellent go/analysis package that makes it easy to write custom analyzers in Go. If you're interested in writing a tool, check out my Using go/analysis to write a custom linter blog post.

Part of the code is modified and based on astutil.UsesImport

faillint's People

Contributors

bwplotka avatar dimitarvdimitrov avatar fatih avatar joshmgross avatar mweb avatar nnutter avatar saswatamcode avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

faillint's Issues

package imported without types errors when using with Go 1.18

When using this tool with Go 1.18, I'm seeing errors like

faillint: internal error: package "time" without types was imported from "github.com/github/my-project-repo/constants"

This might be fixed by bumping golang.org/x/tools to a newer version

Ignore vendor dir

Hello,

what would be the recommended approach for ignoring the vendor directory while scanning the rest of the project?

Would this be a possible upcoming feature?

Thanks.

Add ability to lint only test/benchmark/fuzz functions

Thank you for this tool! I have a use case where I want to ban time.Sleep from test functions to avoid flaky tests. Instead of time.Sleep with a hard-coded timeout, I want to push others to use functions that would retry some operations continuously until some deadline. For that, faillint should be able to operate only on Test*, Benchmark*, and Fuzz* functions. What do you think? Would you be open to adding such an option?

[Feature Request] Support excluding packages

It would be nice to have a flag to exclude a package from a scan.

Example

faillint -paths google.golang.org/grpc/status ./pkg/... -exclude ./pkg/server/...

instead of having to do

faillint -paths google.golang.org/grpc/status ./pkg/log/... ./pkg/metrics/... ./pkg/internal/... # and so on and so forth

Dot imports

I'd like to forbid dot imports on some projects, could faillint catch package aliases or explicitly . with -paths?

e.g.

import (
   . "github.com/pkg/errors"
)

Comment imports to tolerate exceptions

Imagine wanting to avoid a package but, in practice, needing to be able to tolerate exceptions to it (while using faillint in a CI pipeline). Much like many linting tools I wonder if the import could be annotated like,

import (
    "github.com/gogo/protobuf/proto" // faillint: gogo is tolerated here because XYZ.
)

So if we had -path "github.com/gogo/protobuf/proto=github.com/golang/protobuf/proto" but faillint saw a // faillint: annotation it would suppress its objection (and if no objections exit zero).

Ignore Tests

Would it be possible to ignore test files. I had a quick look at the code. I saw a way to disable the test for test packages but could not see how I would achieve this for a single test file.

I'll try to have a better look at the code maybe I can find something there.

Panic while parsing

Seen at https://github.com/grafana/mimir/actions/runs/8520707102/job/23337387670?pr=7356

Of interest, this is a PR to bump Go version from 1.21 to 1.22.

Since this repo seems moribund, we will try https://github.com/OpenPeeDeeP/depguard.

GOFLAGS="-tags=requires_docker,stringlabels" faillint -paths "github.com/bmizerany/assert=github.com/stretchr/testify/assert,\
	golang.org/x/net/context=context,\
	sync/atomic=go.uber.org/atomic,\
	regexp=github.com/grafana/regexp,\
	github.com/go-kit/kit/log/...=github.com/go-kit/log,\
	github.com/prometheus/client_golang/prometheus.{MultiError}=github.com/prometheus/prometheus/tsdb/errors.{NewMulti},\
	github.com/weaveworks/common/user.{ExtractOrgID}=github.com/grafana/mimir/pkg/tenant.{TenantID,TenantIDs},\
	github.com/weaveworks/common/user.{ExtractOrgIDFromHTTPRequest}=github.com/grafana/mimir/pkg/tenant.{ExtractTenantIDFromHTTPRequest}" ./pkg/... ./cmd/... ./tools/... ./integration/...
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
	panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x5b25ef]

goroutine 95 [running]:
go/types.(*Checker).handleBailout(0xc0001b0400, 0xc004179c60)
	/usr/local/go/src/go/types/check.go:367 +0x88
panic({0x7192e0?, 0x9a4b20?})
	/usr/local/go/src/runtime/panic.go:770 +0x132
go/types.(*StdSizes).Sizeof(0x0, {0x7e7e90, 0x9a87e0})
	/usr/local/go/src/go/types/sizes.go:228 +0x30f
go/types.(*Config).sizeof(...)
	/usr/local/go/src/go/types/sizes.go:333
go/types.representableConst.func1({0x7e7e90?, 0x9a87e0?})
	/usr/local/go/src/go/types/const.go:76 +0x9e
go/types.representableConst({0x7e91f0, 0x99d4a0}, 0xc0001b0400, 0x9a87e0, 0xc004178078)
	/usr/local/go/src/go/types/const.go:92 +0x192
[...]
golang.org/x/tools/go/packages.(*loader).loadRecursive(0x6d696d2f72696d69?, 0x6f646e65762f7269?)
	/go/pkg/mod/golang.org/x/[email protected]/go/packages/packages.go:826 +0x4a
golang.org/x/tools/go/packages.(*loader).refine.func2(0x2f33762f676b702f?)
	/go/pkg/mod/golang.org/x/[email protected]/go/packages/packages.go:761 +0x26
created by golang.org/x/tools/go/packages.(*loader).refine in goroutine 1
	/go/pkg/mod/golang.org/x/[email protected]/go/packages/packages.go:760 +0xc9a
make: *** [Makefile:308: lint] Error 2

Add support to path prefixes

We have some use cases where we would like to blacklist an import path prefix so that all import packages belonging to that prefix are blacklisted as well. Would be of any interest to add such feature to the project?

Add failing for certain methods in a given package (i.e: fmt.Errorf)

Currently faillint only fails on package level (based on the import path). We should also make it possible to fail for a sub-set of exported functions, types or variables (i.e: ast.TypeSpec and ast.ValueSpec should be enough).

Why is this useful? Let's look at this example:

If a user opted to use github.com/pkg/errors not only want they prevent the usage of errors package, they probably also want to avoid using fmt.Errorf and start using errors.Errorf method from the github.com/pkg/errors package. There are many reasons for that. For example the github.com/pkg/errors package includes stack information in each error value. Hence they want to make sure all errors are created with the github.com/pkg/errors package.

We could still use the paths flags and could define the sub-set with a dot identifier. For example the following command could fail for all usages of errors and the usage of fmt.Errorf:

faillint --paths "errors,fmt.Errorf"

If anyone wants to work on this, please comment on the PR before starting on it. I'll work on that at some point otherwise.

Analyse go files with buildtags

We have some tests with a buildtag // +build requires_docker at the beginning of the file and I've just realised these files are skipped by faillint and I can't find a way to specify build tags (I also checked a bit the source code). Is there any way to get it working on source files with build tags?

Skip generated files.

Hey ๐Ÿ‘‹

Any ideas on how to deal with generated files? How can we easily ignore those without many faillint invocations?

/home/bwplotka/Repos/thanos/pkg/store/storepb/types.pb.go:18:13: declaration "Errorf" from package "fmt" shouldn't be used, suggested: "github.com/pkg/errors.{Errorf}"

I tried find . -type f -name "*.go" | grep -v vendor/ | grep -vE '*.pb.go' | xargs $(FAILLINT) -paths $(FAILLINT_PATHS) but got:

-: named files must all be in one directory; have ./test/e2e/ and ./test/e2e/e2ethanos/
faillint: error during loading

[Feature Request] Autofix support

Rather than printing a warning and a suggestion, it should be possible to allow user to specify an option (e.g. -fix) so that directly replace the offending import path with the suggested one.

Possibility to detect function from a struct

I'd like a way to block github.com/jmoiron/sqlx, struct DB, function Select, Exec, Must*

  • Must* functions are dangerous in a service context (assuming you don't have recover middleware)
  • Select, Exec, and a few others don't take a context, we need to use *Context( for cancellation aware versions.

The issue is that these aren't top level functions or vars, but are nested inside an *sqlx.DB.

Wanted expression:

github.com/jmoiron/sqlx.DB.{Select,Exec,Must*}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.