Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
domeej committed Jun 10, 2021
1 parent ef836e7 commit da745b2
Show file tree
Hide file tree
Showing 40 changed files with 2,350 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.DS_Store
.cache
.idea
.temp
123 changes: 123 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Flamalyzer - Static code analysis for Flamingo projects

Flamalyzer is a vet-like tool which detects common errors in Flamingo projects.

## Installation

```shell
go install flamingo.me/flamalyzer@latest
```

## Usage

```shell
Flamalyzer [FLAGS] [PATH]
```

### Flags
There are different Flags which can be passed, if no Flags given the Flamalyzer will run in default mode.

```shell
--configFolder=[PATH]
```
To define the path to your config-files, the default is `./.flamalyzer`

```shell
--configSuffix=[SUFFIX]
```

To define a string which must occur in the config-files name which should be loaded
(useful if you have different files for different use-cases)

```shell
--debugFlamalyzer
```

To enable more information about what's going on in the program. Like info if the given configFolder-Path isn't configured properly.

### Run Flamalyzer within vet

```shell
go vet -vettool=$(which flamalyzer) [PATH]
```

### Filewatcher
Flamalyzer can be used as external filewatcher to enable error-highlighting in the IDE.
Flamalyzer's output matches exactly vet's output, so the filewatcher configuration from vet can be used.


## Available Analyses

### Dingo: Pointer receiver check

This analysis checks that an inject-function has a pointer-receiver

### Dingo: correct interface binding check

This analysis checks that an instance implements the interface it is bound to.

### Dingo: proper inject tags check

This analysis checks if the inject tags are used properly.

"inject tags" should be used for annotated injections only (e.g. config), otherwise inject method should be used.

This means:

- No empty inject tags
- Inject tags can be defined in the Inject-Function or must be referenced if defined outside
- They must be declared in the same package as the Inject-Function

#### Architecture: dependency conventions check

This analysis checks all import statements below the entry path that the provided Group-Conventions are respected.

Configuration example:

```yaml
groups:
entrypaths: [ "src/architecture" ]
infrastructure:
allowedDependencies: [ "infrastructure", "interfaces", "application", "domain" ]
interfaces:
allowedDependencies: [ "interfaces", "application", "domain" ]
application:
allowedDependencies: [ "application", "domain" ]
domain:
allowedDependencies: [ "domain" ]
```
## Configuration
The Configuration is done via **yaml**-files.
The files are expected in `./.flamalyzer`

The directory can be specified by `--configFolder=[PATH]`

**config.yaml** example:
```yaml
# Config of the Dingo-ReceiverAnalyzer
dingoAnalyzer:
checkPointerReceiver: false
strictTagsAndFunctions: false
correctInterfaceToInstanceBinding: false
# Config of the dependencyConventions-ReceiverAnalyzer
dependencyConventionsAnalyzer:
entryPaths: []
dependencyConventions: true
groups:
infrastructure:
allowedDependencies: ["infrastructure", "interfaces", "application", "domain"]
interfaces:
allowedDependencies: ["interfaces", "application", "domain"]
application:
allowedDependencies: ["application", "domain"]
domain:
allowedDependencies: ["domain"]
```

There is the possibility to **filter** the files which should be read in.

Use `--configSuffix=[SUFFIX]` to pass a string which must be part of config-file name.
18 changes: 18 additions & 0 deletions config/flamalyzer.variantA.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# this is an example config
debug: false

# Config of the Dingo-Analyzer
dingoAnalyzer:
checkPointerReceiver: false
checkStrictTagsAndFunctions: false
checkCorrectInterfaceToInstanceBinding: false

# Config of the DependencyConventions-Analyzer
architectureAnalyzer:
entryPaths: []
checkDependencyConventions: true
groups:
infrastructure: ["infrastructure", "interfaces", "application", "domain"]
interfaces: ["interfaces", "application", "domain"]
application: ["application", "domain"]
domain: ["domain"]
12 changes: 12 additions & 0 deletions config/flamalyzer.variantB.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# this is an example config

debug: false

architectureAnalyzer:
entryPaths: []
checkDependencyConventions: false

dingoAnalyzer:
checkPointerReceiver: true
checkStrictTagsAndFunctions: true
checkCorrectInterfaceToInstanceBinding: true
13 changes: 13 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module flamingo.me/flamalyzer

go 1.16

require (
flamingo.me/dingo v0.2.9
github.com/mitchellh/mapstructure v1.4.1
github.com/spf13/pflag v1.0.5
golang.org/x/mod v0.4.2 // indirect
golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988 // indirect
golang.org/x/tools v0.1.0
gopkg.in/yaml.v2 v2.4.0
)
49 changes: 49 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
flamingo.me/dingo v0.2.9 h1:HL7YV4iv3F6xLcUPvIBEzdkbhBSb6PukkZdoOZ8H+Eo=
flamingo.me/dingo v0.2.9/go.mod h1:NXspAYkbktnP0EKs/27QW6Evija8WZfWGtrMcauOejQ=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988 h1:EjgCl+fVlIaPJSori0ikSz3uV0DOHKWOJFpv1sAAhBM=
golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
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-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
17 changes: 17 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package main

import (
"flamingo.me/dingo"
"flamingo.me/flamalyzer/src/analyzers/architecture"
dingoAnalyzer "flamingo.me/flamalyzer/src/analyzers/dingo"
"flamingo.me/flamalyzer/src/flamalyzer"
)

func main() {
flamalyzer.Run(
[]dingo.Module{
new(dingoAnalyzer.Module),
new(architecture.Module),
},
)
}
31 changes: 31 additions & 0 deletions src/analyzers/analyzer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package analyzers

import (
"reflect"

"flamingo.me/flamalyzer/src/flamalyzer/configuration"
"github.com/mitchellh/mapstructure"
"golang.org/x/tools/go/analysis"
)

// AnalyzerProvider returns all bound instances
type AnalyzerProvider func() []Analyzer

// Analyzer is a collection of checks performed by Flamalyzer.
type Analyzer interface {
ChecksToExecute() []*analysis.Analyzer
}

// DecodeAnalyzerConfigurationsToAnalyzerProps decodes the props loaded from the config-files to the specific props of an analyzer
// The props musst be passed a Pointer e.g &props
func DecodeAnalyzerConfigurationsToAnalyzerProps(entryName string, config configuration.AnalyzerConfig, propsPtr interface{}) {
if reflect.ValueOf(propsPtr).Type().Kind() != reflect.Ptr {
panic("The passed propsPtr must be a pointer, otherwise the result of this function won't be available in the analyzer!")
}
inputProps := config.GetProps(entryName)
err := mapstructure.Decode(inputProps, &propsPtr)
if err != nil {
panic(err)
}

}
63 changes: 63 additions & 0 deletions src/analyzers/architecture/analyzer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Package architecture provides checks targeting architecture conventions
package architecture

import (
"flamingo.me/dingo"
"flamingo.me/flamalyzer/src/analyzers"
"flamingo.me/flamalyzer/src/analyzers/architecture/checks/dependency"
"flamingo.me/flamalyzer/src/flamalyzer/configuration"
"golang.org/x/tools/go/analysis"
)

// Module to register the architecture checks
type Module struct{}

// The default properties which are used if there is no config-file
var defaultProps = Props{
EntryPaths: []string{},
CheckDependencyConventions: true,
Groups: map[string][]string{
"infrastructure": {"infrastructure", "interfaces", "application", "domain"},
"interfaces": {"interfaces", "application", "domain"},
"application": {"application", "domain"},
"domain": {"domain"},
},
}

// Props of an analyzer which will be used by the config-module to match the entries
// of a file to this variables. Is used to activate and deactivate checks, for example.
type Props struct {
Name string
EntryPaths []string
CheckDependencyConventions bool
Groups map[string][]string
}

// The Analyzer holds a set of checks, uses the config and has props that can be defined to get read by the config
type Analyzer struct {
checks []*analysis.Analyzer
config configuration.AnalyzerConfig
props Props
}

// Configure DI
func (m *Module) Configure(injector *dingo.Injector) {
injector.BindMulti(new(analyzers.Analyzer)).To(new(Analyzer))
}

// Inject dependencies
func (d *Analyzer) Inject(config configuration.AnalyzerConfig) {
d.props = defaultProps
d.props.Name = "architectureAnalyzer"
d.config = config
}

// ChecksToExecute decides which checks to run
func (d *Analyzer) ChecksToExecute() []*analysis.Analyzer {
analyzers.DecodeAnalyzerConfigurationsToAnalyzerProps(d.props.Name, d.config, &d.props)

if d.props.CheckDependencyConventions {
d.checks = append(d.checks, dependency.NewAnalyzer(d.props.Groups, d.props.EntryPaths).Analyzer)
}
return d.checks
}
20 changes: 20 additions & 0 deletions src/analyzers/architecture/analyzer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// This module configures the Testing with the "analysistest" package
package architecture

import (
"path/filepath"
"testing"

"flamingo.me/flamalyzer/src/analyzers/architecture/checks/dependency"
"golang.org/x/tools/go/analysis/analysistest"
)

func TestArchitectureConventions(t *testing.T) {
analysis := dependency.NewAnalyzer(map[string][]string{
"infrastructure": {"infrastructure", "interfaces", "application", "domain"},
"interfaces": {"interfaces", "application", "domain"},
"application": {"application", "domain"},
"domain": {"domain"},
}, []string{})
analysistest.Run(t, filepath.Join(analysistest.TestData(), "/dependencyConventions"), analysis.Analyzer, "./...")
}
Loading

0 comments on commit da745b2

Please sign in to comment.