Home

Awesome

<h1 align="center"> <img src="https://user-images.githubusercontent.com/19743841/97435499-d7541280-1963-11eb-80e1-70d3d80f30cc.png" /> </h1> <p align="center"> <a href="https://codecov.io/gh/resotto/gochk"><img src="https://codecov.io/gh/resotto/gochk/branch/master/graph/badge.svg?token=0YQPLUGIXA"/></a> <a href="https://github.com/resotto/gochk/actions"><img src="https://github.com/resotto/gochk/workflows/build/badge.svg" /></a> <a href="https://goreportcard.com/report/github.com/resotto/gochk"><img src="https://goreportcard.com/badge/github.com/resotto/gochk"></a> <a href="https://bestpractices.coreinfrastructure.org/projects/4380"><img src="https://bestpractices.coreinfrastructure.org/projects/4380/badge"></a> <a href="https://pkg.go.dev/github.com/resotto/gochk?tab=overview"><img src="https://pkg.go.dev/badge/github.com/resotto/gochk" alt="PkgGoDev"></a> <a href="https://github.com/resotto/gochk/issues/1"><img src="https://img.shields.io/badge/chat-on%20issue-yellow"></a> <a href="https://github.com/resotto/gochk/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" /></a> </p> <p align="center"> Static Dependency Analysis Tool for Go Files </p> <p align="center"> <img src="https://user-images.githubusercontent.com/19743841/97001249-0983ff80-1573-11eb-818f-9bdbffe8f762.gif"> <a href="https://asciinema.org/a/371171" target="_blank"><img src="https://asciinema.org/a/371171.svg" /></a> </p>

What is Gochk?

What problem does Gochk solve?

When to apply Gochk to codebase?

Who is the main user of Gochk?

Why Gochk?


Table of Contents

Getting Started

Docker

See Build.

Local

go get -u github.com/resotto/gochk
cd ${GOPATH}/src/github.com/resotto/gochk

Please edit paths of dependencyOrders in gochk/configs/config.json according to your dependency rule, whose smaller index value means outer circle.

"dependencyOrders": ["external", "adapter", "application", "domain"],

And then, let's gochk your target path with -t:

go run cmd/gochk/main.go -t=${YourTargetPath}

If you have Goilerplate, you can also gochk it:

go run cmd/gochk/main.go -t=../goilerplate

If your current working directory is not in Gochk root ${GOPATH}/src/github.com/resotto/gochk, you must specify the location of the config.json with -c:

cd internal
go run ../cmd/gochk/main.go -t=../../goilerplate -c=../configs/config.json

Moreover, if you want to exit with 1 when violations occur, please specify -e=true (default false):

go run ../cmd/gochk/main.go -t=../../goilerplate -c=../configs/config.json -e=true

Installation

First of all, let's check GOPATH has already been set:

go env GOPATH

And then, please confirm that ${GOPATH}/bin is included in your $PATH:

echo $PATH

Finally, please install Gochk:

cd cmd/gochk
go install

How Gochk works

Prerequisites

What Gochk does

Gochk checks whether .go files violate Clean Architecture The Dependency Rule or not, and prints its results.

This rule says that source code dependencies can only point inwards. Nothing in an inner circle can know anything at all about something in an outer circle.

For example, if an usecase in "Use Cases" imports (depends on) what is in "Controllers/Gateways/Presenters", it violates dependency rule.

<p align="center"> <img src="https://user-images.githubusercontent.com/19743841/93830264-afa9c480-fcaa-11ea-9589-7c5308c291f4.jpg"> </p> <p align="center"> <a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html">The Clean Architecture</a> </p>

Check Logic

Firstly, Gochk fetches the file path and gets the index of dependencyOrders in gochk/configs/config.json if one of them is included in the file path.

Secondly, Gochk reads the file, parses import paths, and also gets the indices of dependencyOrders if matched.

And then, Gochk compares those indices and detects violation if the index of the import path is smaller than that of the file path.

For example, if you have a file app/application/usecase/xxx.go with import path "app/adapter/service" and dependencyOrders = ["adapter", "application"], the index of the file is 1 and the index of its import is 0.
Therefore, the file violates dependency rule since the following inequality is established:

How to see results

Quick Check

You can check whether there are violations or not quickly by looking at the end of results.

If you see Dependencies which violate dependency orders found!, there are violations!🚨

2020/10/19 23:37:03 Dependencies which violate dependency orders found!

If you see the following AA, congrats! there are no violationsπŸŽ‰

2020/10/19 23:57:25 No violations
    ________     _______       ______    __     __    __   _ _
   /  ______\   /  ___  \     /  ____\  |  |   |  |  |  | /   /
  /  /  ____   /  /   \  \   /  /       |  |___|  |  |  |/   /
 /  /  |_   | |  |     |  | |  |        |   ___   |  |      /
 \  \    \  | |  |     |  | |  |        |  |   |  |  |  |\  \
  \  \___/  /  \  \___/  /   \  \_____  |  |   |  |  |  | \  \
   \_______/    \_______/     \_______\ |__|   |__|  |__|  \__\

Result types

Gochk displays each result type in a different color by default:

For None, Verified, and Ignored, only the file path will be displayed.

[None]     ../goilerplate/internal/app/adapter/postgresql/conn.go
[Verified] ../goilerplate/cmd/app/main.go
[Ignored]  ../goilerplate/.git

For Warning, it displays what happened to the file.

[Warning]  open /Users/resotto/go/src/github.com/resotto/goilerplate/internal/app/application/usecase/lock.go: permission denied

For Violated, it displays the file path, its dependency, and how it violates dependency rule.

[Violated] ../goilerplate/internal/app/domain/temp.go imports "github.com/resotto/goilerplate/internal/app/adapter/postgresql/model"
 => domain depends on adapter

Configuration

Changing Default Target Path, Config Path, and Exit Mode

You can modify default target path, config path, and exit mode in main.go:

exitMode := flag.Bool("e", false /* default value */, "flag whether Gochk exits with 1 or not when violations occur. (false is default)")
targetPath := flag.String("t", "." /* default value */, "target path (\".\" is default)")
configPath := flag.String("c", "configs/config.json" /* default value */, "configuration file path (\"configs/config.json\" is default)")

config.json

gochk/configs/config.json has configuration values.

{
  "dependencyOrders": ["external", "adapter", "application", "domain"],
  "ignore": ["test", ".git"],
  "printViolationsAtTheBottom": false
}
// read.go
func matchIgnore(ignorePaths []string, path string, info os.FileInfo) (bool, error) {
	if included, _ := include(ignorePaths, path); included {
		if info.IsDir() {
			return true, filepath.SkipDir
		}
		return true, nil
	}
	return false, nil
}

Customization

Changing Result Color

First, please add the ANSI escape code to print.go:

const (
	teal     color = "\033[1;36m"
	green          = "\033[1;32m"
	yellow         = "\033[1;33m"
	purple         = "\033[1;35m"
	red            = "\033[1;31m"
	newColor       = "\033[1;34m" // New color
	reset          = "\033[0m"
)

And then, let's change color of result type in read.go:

func newWarning(message string) CheckResult {
	cr := CheckResult{}
	cr.resultType = warning
	cr.message = message
	cr.color = newColor // New color
	return cr
}

Tuning the Number of Goroutine

If printViolationsAtTheBottom is false, Gochk prints results with goroutine.

You can change the number of goroutine in print.go:

func printConcurrently(results []CheckResult) {
	c := make(chan struct{}, 10) // 10 goroutines by default
	var wg sync.WaitGroup
	for _, r := range results {
		r := r
		c <- struct{}{}
		wg.Add(1)
		go func() {
			defer func() { <-c; wg.Done() }()
			printColorMessage(r)
		}()
	}
	wg.Wait()
}

Unit Testing

Unit test files are located in gochk/internal/gochk.

gochk
β”œβ”€β”€ internal
β”‚Β Β  └── gochk
β”‚Β Β      β”œβ”€β”€ calc_internal_test.go # Unit test (internal)
β”‚Β Β      └── read_internal_test.go # Unit test (internal)
└── test
    └── testdata                  # Test data

So you can do unit test like:

~/go/src/github.com/resotto/gochk (master) > go test ./internal/gochk/... # Please specify -v if you need detailed outputs
ok      github.com/resotto/gochk/internal/gochk (cached)

You can also clean test cache with go clean -testcache.

~/go/src/github.com/resotto/gochk (master) > go clean -testcache
~/go/src/github.com/resotto/gochk (master) > go test ./internal/gochk/...
ok      github.com/resotto/gochk/internal/gochk 0.092s # Not cache

Performance Test

Performance test file is located in gochk/test/performance.

gochk
└── test
    └── performance
        └── check_test.go # Performance test

Thus, you can do performance test as follows. It will take few minutes.

~/go/src/github.com/resotto/gochk (master) > go test ./test/performance/...
ok      github.com/resotto/gochk/test/performance       61.705s

Test Contents

Performance test checks 40,000 test files in gochk/test/performance and measures only how long it takes to do it.

Note

gochk
└── test
    β”œβ”€β”€ performance
    β”‚   β”œβ”€β”€ adapter         # Test directory
    β”‚   β”‚   β”œβ”€β”€ postgresql
    β”‚   β”‚   β”‚   └── model
    β”‚   β”‚   β”œβ”€β”€ repository
    β”‚   β”‚   β”œβ”€β”€ service
    β”‚   β”‚   β”œβ”€β”€ view
    β”‚   β”‚   ...             # Test files (g0.go ~ g9999.go)
    β”‚   β”œβ”€β”€ application     # Test directory
    β”‚   β”‚   β”œβ”€β”€ service
    β”‚   β”‚   β”œβ”€β”€ usecase
    β”‚   β”‚   ...             # Test files (g0.go ~ g9999.go)
    β”‚   β”œβ”€β”€ domain          # Test directory
    β”‚   β”‚   β”œβ”€β”€ factory
    β”‚   β”‚   β”œβ”€β”€ repository
    β”‚   β”‚   β”œβ”€β”€ valueobject
    β”‚   β”‚   ...             # Test files (g0.go ~ g9999.go)
    β”‚   └── external        # Test directory
    β”‚       ...             # Test files (g0.go ~ g9999.go)
    └── testdata
     Β Β  β”œβ”€β”€ adapter.txt     # Original file of performance/adapter/gX.go
     Β Β  β”œβ”€β”€ application.txt # Original file of performance/application/gX.go
     Β Β  β”œβ”€β”€ domain.txt      # Original file of performance/domain/gX.go
     Β Β  └── external.txt    # Original file of performance/external/gX.go

For each file, it imports standard libraries and dependencies like:

package xxx

import (
    // standard library imports omitted here

    "github.com/resotto/gochk/test/performance/adapter"                  // import this up to adapter
    "github.com/resotto/gochk/test/performance/adapter/postgresql"       // import this up to adapter
    "github.com/resotto/gochk/test/performance/adapter/postgresql/model" // import this up to adapter
    "github.com/resotto/gochk/test/performance/adapter/repository"       // import this up to adapter
    "github.com/resotto/gochk/test/performance/adapter/service"          // import this up to adapter
    "github.com/resotto/gochk/test/performance/adapter/view"             // import this up to adapter
    "github.com/resotto/gochk/test/performance/application/service"      // import this up to application
    "github.com/resotto/gochk/test/performance/application/usecase"      // import this up to application
    "github.com/resotto/gochk/test/performance/domain/factory"           // import this only in domain
    "github.com/resotto/gochk/test/performance/domain/repository"        // import this only in domain
    "github.com/resotto/gochk/test/performance/domain/valueobject"       // import this only in domain
    "github.com/resotto/gochk/test/performance/external"                 // import this up to adapter
)

In performance test, dependencyOrders are:

var dependencyOrders = []string{"external", "adapter", "application", "domain"}

So, the number of violations equals to:

Score

Following scores are not cached ones and measured by two Macbook Pros whose spec is different.

CPURAM1st score2nd score3rd scoreAverage
2.7 GHz Dual-Core Intel Core i58 GB 1867 MHz DDR399.53s97.08s93.88s96.83s
2 GHz Quad-Core Intel Core i532 GB 3733 MHz LPDDR4X59.64s55.57s52.09s55.77s

Build

From Gochk root directory ${GOPATH}/src/github.com/resotto/gochk, please run:

docker build -t gochk:latest -f build/Dockerfile .

Or you can also pull the image from GitHub Container Registry:

docker pull ghcr.io/resotto/gochk:latest

After getting Gochk docker image, please prepare Dockerfile with the package you want to gochk:

# FROM gochk:latest
FROM ghcr.io/resotto/gochk:latest

RUN go get -u ${TargetPackage}

WORKDIR /go/src/github.com/resotto/gochk

ENTRYPOINT ["/go/bin/gochk", "-t=${TargetPackageRoot}"]

And then, please build the docker image:

docker build -t gochk-${YourPackage}:latest .

Finally, let's gochk your target package on docker container:

docker run --rm -it gochk-${YourPackage}:latest

GitHub Actions

You can gochk your package on GitHub Actions with following yml file:

name: gochk sample

on: [push]

jobs:
  gochk-goilerplate:
    runs-on: ubuntu-latest
    container:
      image: docker://ghcr.io/resotto/gochk:latest
    steps:
      - name: Clone Goilerplate
        uses: actions/checkout@v2
        with:
          repository: resotto/goilerplate
      - name: Run gochk
        run: |
          /go/bin/gochk -c=/go/src/github.com/resotto/gochk/configs/config.json

This is the result:

<p align="center"> <img src="https://user-images.githubusercontent.com/19743841/97106961-1ab24500-1708-11eb-939d-275b08744a9f.png"> </p>

Feedback

Contributing

<a href="https://www.contributor-covenant.org/version/2/0/code_of_conduct/"><img src="https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg" /></a>

See Contribution Guide.

Release Notes

Release Notes

License

MIT License.

Author

Resotto

<a href="https://github.com/resotto"><img src="https://user-images.githubusercontent.com/19743841/97778118-4629a980-1bb8-11eb-97ed-76dcdbe50406.png" /></a> <a href="https://twitter.com/resotto3"><img src="https://user-images.githubusercontent.com/19743841/97777698-52f8ce00-1bb5-11eb-93c9-b06e0c48b693.png" /></a> <a href="https://github.com/sponsors/resotto"><img src="https://img.shields.io/badge/Sponsor-ffffff?logo=github&logoColor=pink&style=flat-square" /></a>