Repository: mgechev/revive
Pull request #929 (2023-11-01 23:18:00)

extend parsing instructions to use json object instead of plain txt
related with #424 

now we can use instead of txt format:

`x = atomic.AddUint64(&x, 1)        // MATCH /direct assignment to atomic value/`

json format:

`x = atomic.AddUint64(&x, 1) // json:{"MATCH": "direct assignment to atomic value","Confidence": 1}`

of course this is only draft because I dont know how it should looks like logic to use all Failure properties, so I asume that for example we want to check if Confidence property have value equal to that setted up in json object, and if value is not equal then throw/log error otherwise everything is ok

this draft dont remove old MATH txt format, but add json feature, so we can use old format for some time :)

@chavacava what do you think about? 


Patch:
diff --git a/RULES_DESCRIPTIONS.md b/RULES_DESCRIPTIONS.md
index dad372b..c6096e4 100644
--- a/RULES_DESCRIPTIONS.md
+++ b/RULES_DESCRIPTIONS.md
@@ -282,7 +282,17 @@ _Description_: Importing with `.` makes the programs much harder to understand b
 
 More information [here](https://github.com/golang/go/wiki/CodeReviewComments#import-dot)
 
-_Configuration_: N/A
+_Configuration_:
+
+* `allowedPackages`: (list of strings) comma-separated list of allowed dot import packages
+
+Example:
+
+```toml
+[rule.dot-imports]
+  arguments = [{ allowedPackages = ["github.com/onsi/ginkgo/v2","github.com/onsi/gomega"] }]
+```
+
 
 ## duplicated-imports
 
diff --git a/go.mod b/go.mod
index a627240..ec8a499 100644
--- a/go.mod
+++ b/go.mod
@@ -5,22 +5,22 @@ go 1.20
 require (
 	github.com/BurntSushi/toml v1.3.2
 	github.com/chavacava/garif v0.1.0
-	github.com/fatih/color v1.15.0
+	github.com/fatih/color v1.16.0
 	github.com/fatih/structtag v1.2.0
 	github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/olekukonko/tablewriter v0.0.5
 	github.com/pkg/errors v0.9.1
 	github.com/spf13/afero v1.10.0
-	golang.org/x/tools v0.14.0
+	golang.org/x/tools v0.15.0
 )
 
 replace github.com/spf13/afero => github.com/spf13/afero v1.10.0
 
 require (
 	github.com/mattn/go-colorable v0.1.13 // indirect
-	github.com/mattn/go-isatty v0.0.17 // indirect
+	github.com/mattn/go-isatty v0.0.20 // indirect
 	github.com/mattn/go-runewidth v0.0.9 // indirect
-	golang.org/x/sys v0.13.0 // indirect
+	golang.org/x/sys v0.14.0 // indirect
 	golang.org/x/text v0.13.0 // indirect
 )
diff --git a/go.sum b/go.sum
index 45fa958..2ced0b9 100644
--- a/go.sum
+++ b/go.sum
@@ -61,6 +61,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
 github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
+github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
+github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
 github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
 github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
@@ -136,6 +138,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
 github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 h1:zpIH83+oKzcpryru8ceC6BxnoG8TBrhgAvRg8obzup0=
@@ -213,7 +217,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
+golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -301,10 +305,11 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
-golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
 golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
+golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -366,10 +371,10 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f
 golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
-golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
-golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
 golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
 golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
+golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
+golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
 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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/rule/datarace.go b/rule/datarace.go
index c8754ea..39e9669 100644
--- a/rule/datarace.go
+++ b/rule/datarace.go
@@ -111,7 +111,7 @@ func (w lintFunctionForDataRaces) Visit(node ast.Node) ast.Visitor {
 			return ok
 		}
 
-		ids := pick(funcLit.Body, selectIDs, nil)
+		ids := pick(funcLit.Body, selectIDs)
 		for _, id := range ids {
 			id := id.(*ast.Ident)
 			_, isRangeID := w.rangeIDs[id.Obj]
diff --git a/rule/dot-imports.go b/rule/dot-imports.go
index db78f19..6b87767 100644
--- a/rule/dot-imports.go
+++ b/rule/dot-imports.go
@@ -1,16 +1,23 @@
 package rule
 
 import (
+	"fmt"
 	"go/ast"
+	"sync"
 
 	"github.com/mgechev/revive/lint"
 )
 
 // DotImportsRule lints given else constructs.
-type DotImportsRule struct{}
+type DotImportsRule struct {
+	sync.Mutex
+	allowedPackages allowPackages
+}
 
 // Apply applies the rule to given file.
-func (*DotImportsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
+func (r *DotImportsRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
+	r.configure(arguments)
+
 	var failures []lint.Failure
 
 	fileAst := file.AST
@@ -20,6 +27,7 @@ func (*DotImportsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
 		onFailure: func(failure lint.Failure) {
 			failures = append(failures, failure)
 		},
+		allowPackages: r.allowedPackages,
 	}
 
 	ast.Walk(walker, fileAst)
@@ -32,15 +40,49 @@ func (*DotImportsRule) Name() string {
 	return "dot-imports"
 }
 
+func (r *DotImportsRule) configure(arguments lint.Arguments) {
+	r.Lock()
+	defer r.Unlock()
+
+	if r.allowedPackages != nil {
+		return
+	}
+
+	r.allowedPackages = make(allowPackages)
+	if len(arguments) == 0 {
+		return
+	}
+
+	args, ok := arguments[0].(map[string]any)
+	if !ok {
+		panic(fmt.Sprintf("Invalid argument to the dot-imports rule. Expecting a k,v map, got %T", arguments[0]))
+	}
+
+	if allowedPkgArg, ok := args["allowedPackages"]; ok {
+		if pkgs, ok := allowedPkgArg.([]any); ok {
+			for _, p := range pkgs {
+				if pkg, ok := p.(string); ok {
+					r.allowedPackages.add(pkg)
+				} else {
+					panic(fmt.Sprintf("Invalid argument to the dot-imports rule, string expected. Got '%v' (%T)", p, p))
+				}
+			}
+		} else {
+			panic(fmt.Sprintf("Invalid argument to the dot-imports rule, []string expected. Got '%v' (%T)", allowedPkgArg, allowedPkgArg))
+		}
+	}
+}
+
 type lintImports struct {
-	file      *lint.File
-	fileAst   *ast.File
-	onFailure func(lint.Failure)
+	file          *lint.File
+	fileAst       *ast.File
+	onFailure     func(lint.Failure)
+	allowPackages allowPackages
 }
 
 func (w lintImports) Visit(_ ast.Node) ast.Visitor {
 	for _, is := range w.fileAst.Imports {
-		if is.Name != nil && is.Name.Name == "." {
+		if is.Name != nil && is.Name.Name == "." && !w.allowPackages.isAllowedPackage(is.Path.Value) {
 			w.onFailure(lint.Failure{
 				Confidence: 1,
 				Failure:    "should not use dot imports",
@@ -51,3 +93,14 @@ func (w lintImports) Visit(_ ast.Node) ast.Visitor {
 	}
 	return nil
 }
+
+type allowPackages map[string]struct{}
+
+func (ap allowPackages) add(pkg string) {
+	ap[fmt.Sprintf(`"%s"`, pkg)] = struct{}{} // import path strings are with double quotes
+}
+
+func (ap allowPackages) isAllowedPackage(pkg string) bool {
+	_, allowed := ap[pkg]
+	return allowed
+}
diff --git a/rule/flag-param.go b/rule/flag-param.go
index 19a05f9..f9bfb71 100644
--- a/rule/flag-param.go
+++ b/rule/flag-param.go
@@ -88,7 +88,7 @@ func (w conditionVisitor) Visit(node ast.Node) ast.Visitor {
 		return false
 	}
 
-	uses := pick(ifStmt.Cond, fselect, nil)
+	uses := pick(ifStmt.Cond, fselect)
 
 	if len(uses) < 1 {
 		return w
diff --git a/rule/import-alias-naming.go b/rule/import-alias-naming.go
index 0c6513d..a6d096c 100644
--- a/rule/import-alias-naming.go
+++ b/rule/import-alias-naming.go
@@ -68,7 +68,7 @@ func (r *ImportAliasNamingRule) Apply(file *lint.File, arguments lint.Arguments)
 		}
 
 		alias := is.Name
-		if alias == nil || alias.Name == "_" {
+		if alias == nil || alias.Name == "_" || alias.Name == "." { // "_" and "." are special types of import aiases and should be processed by another linter rule
 			continue
 		}
 
diff --git a/rule/modifies-value-receiver.go b/rule/modifies-value-receiver.go
index 34e6515..ae260a9 100644
--- a/rule/modifies-value-receiver.go
+++ b/rule/modifies-value-receiver.go
@@ -97,7 +97,7 @@ func (w lintModifiesValRecRule) Visit(node ast.Node) ast.Visitor {
 			return false
 		}
 
-		assignmentsToReceiver := pick(n.Body, fselect, nil)
+		assignmentsToReceiver := pick(n.Body, fselect)
 
 		for _, assignment := range assignmentsToReceiver {
 			w.onFailure(lint.Failure{
diff --git a/rule/optimize-operands-order.go b/rule/optimize-operands-order.go
index 88928bb..841bde5 100644
--- a/rule/optimize-operands-order.go
+++ b/rule/optimize-operands-order.go
@@ -54,13 +54,13 @@ func (w lintOptimizeOperandsOrderlExpr) Visit(node ast.Node) ast.Visitor {
 	}
 
 	// check if the left sub-expression contains a function call
-	nodes := pick(binExpr.X, isCaller, nil)
+	nodes := pick(binExpr.X, isCaller)
 	if len(nodes) < 1 {
 		return w
 	}
 
 	// check if the right sub-expression does not contain a function call
-	nodes = pick(binExpr.Y, isCaller, nil)
+	nodes = pick(binExpr.Y, isCaller)
 	if len(nodes) > 0 {
 		return w
 	}
diff --git a/rule/unconditional-recursion.go b/rule/unconditional-recursion.go
index 7e73b72..9ac2648 100644
--- a/rule/unconditional-recursion.go
+++ b/rule/unconditional-recursion.go
@@ -195,5 +195,5 @@ func (lintUnconditionalRecursionRule) hasControlExit(node ast.Node) bool {
 		return false
 	}
 
-	return len(pick(node, isExit, nil)) != 0
+	return len(pick(node, isExit)) != 0
 }
diff --git a/rule/unused-param.go b/rule/unused-param.go
index 1f73618..63eab37 100644
--- a/rule/unused-param.go
+++ b/rule/unused-param.go
@@ -113,7 +113,7 @@ func (w lintUnusedParamRule) Visit(node ast.Node) ast.Visitor {
 
 			return false
 		}
-		_ = pick(n.Body, fselect, nil)
+		_ = pick(n.Body, fselect)
 
 		for _, p := range n.Type.Params.List {
 			for _, n := range p.Names {
diff --git a/rule/unused-receiver.go b/rule/unused-receiver.go
index 691c1fb..715dba3 100644
--- a/rule/unused-receiver.go
+++ b/rule/unused-receiver.go
@@ -113,7 +113,7 @@ func (w lintUnusedReceiverRule) Visit(node ast.Node) ast.Visitor {
 
 			return isAnID && ident.Obj == recID.Obj
 		}
-		refs2recID := pick(n.Body, fselect, nil)
+		refs2recID := pick(n.Body, fselect)
 
 		if len(refs2recID) > 0 {
 			return nil // the receiver is referenced in the func body
diff --git a/rule/utils.go b/rule/utils.go
index 9e7f13e..5778e76 100644
--- a/rule/utils.go
+++ b/rule/utils.go
@@ -93,21 +93,15 @@ func srcLine(src []byte, p token.Position) string {
 
 // pick yields a list of nodes by picking them from a sub-ast with root node n.
 // Nodes are selected by applying the fselect function
-// f function is applied to each selected node before inserting it in the final result.
-// If f==nil then it defaults to the identity function (ie it returns the node itself)
-func pick(n ast.Node, fselect func(n ast.Node) bool, f func(n ast.Node) []ast.Node) []ast.Node {
+func pick(n ast.Node, fselect func(n ast.Node) bool) []ast.Node {
 	var result []ast.Node
 
 	if n == nil {
 		return result
 	}
 
-	if f == nil {
-		f = func(n ast.Node) []ast.Node { return []ast.Node{n} }
-	}
-
 	onSelect := func(n ast.Node) {
-		result = append(result, f(n)...)
+		result = append(result, n)
 	}
 	p := picker{fselect: fselect, onSelect: onSelect}
 	ast.Walk(p, n)


Test patch:
diff --git a/test/dot_imports_test.go b/test/dot_imports_test.go
new file mode 100644
index 0000000..31cd4bd
--- /dev/null
+++ b/test/dot_imports_test.go
@@ -0,0 +1,18 @@
+package test
+
+import (
+	"testing"
+
+	"github.com/mgechev/revive/lint"
+	"github.com/mgechev/revive/rule"
+)
+
+func TestDotImports(t *testing.T) {
+	args := []any{map[string]any{
+		"allowedPackages": []any{"errors", "context", "github.com/BurntSushi/toml"},
+	}}
+
+	testRule(t, "import-dot", &rule.DotImportsRule{}, &lint.RuleConfig{
+		Arguments: args,
+	})
+}
diff --git a/test/json-data-format_test.go b/test/json-data-format_test.go
new file mode 100644
index 0000000..3914b31
--- /dev/null
+++ b/test/json-data-format_test.go
@@ -0,0 +1,17 @@
+package test
+
+import (
+	"testing"
+
+	"github.com/mgechev/revive/lint"
+	"github.com/mgechev/revive/rule"
+)
+
+func TestJsonDataFormat(t *testing.T) {
+	testRule(t, "json-data-format-atomic", &rule.AtomicRule{})
+
+}
+func TestJsonDataFormatVarNaming(t *testing.T) {
+	testRule(t, "json-data-format-var-naming", &rule.VarNamingRule{}, &lint.RuleConfig{})
+
+}
diff --git a/test/utils.go b/test/utils.go
index 0b3b0bd..3954423 100644
--- a/test/utils.go
+++ b/test/utils.go
@@ -3,6 +3,7 @@ package test
 import (
 	"bufio"
 	"bytes"
+	"encoding/json"
 	"fmt"
 	"go/ast"
 	"go/parser"
@@ -89,6 +90,7 @@ func assertFailures(t *testing.T, baseDir string, fi os.FileInfo, src []byte, ru
 			if p.Position.Start.Line != in.Line {
 				continue
 			}
+
 			if in.Match == p.Failure {
 				// check replacement if we are expecting one
 				if in.Replacement != "" {
@@ -102,6 +104,14 @@ func assertFailures(t *testing.T, baseDir string, fi os.FileInfo, src []byte, ru
 					}
 				}
 
+				if in.Confidence > 0 {
+
+					if in.Confidence != p.Confidence {
+						t.Errorf("Lint failed at %s:%d; got confidence %f, want %f", fi.Name(), in.Line, p.Confidence, in.Confidence)
+					}
+
+				}
+
 				// remove this problem from ps
 				copy(failures[i:], failures[i+1:])
 				failures = failures[:len(failures)-1]
@@ -122,9 +132,19 @@ func assertFailures(t *testing.T, baseDir string, fi os.FileInfo, src []byte, ru
 }
 
 type instruction struct {
-	Line        int    // the line number this applies to
-	Match       string // which pattern to match
-	Replacement string // what the suggested replacement line should be
+	Line        int     // the line number this applies to
+	Match       string  // which pattern to match
+	Replacement string  // what the suggested replacement line should be
+	RuleName    string  // what rule we use
+	Category    string  // which category
+	Confidence  float64 // confidence level
+}
+
+// JSONInstruction structure used when we parse json object insted of classic MATCH string
+type JSONInstruction struct {
+	Match      string  `json:"MATCH"`
+	Category   string  `json:"Category"`
+	Confidence float64 `json:"Confidence"`
 }
 
 // parseInstructions parses instructions from the comments in a Go source file.
@@ -140,7 +160,7 @@ func parseInstructions(t *testing.T, filename string, src []byte) []instruction
 		ln := fset.Position(cg.Pos()).Line
 		raw := cg.Text()
 		for _, line := range strings.Split(raw, "\n") {
-			if line == "" || strings.HasPrefix(line, "#") {
+			if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "ignore") {
 				continue
 			}
 			if line == "OK" && ins == nil {
@@ -148,7 +168,15 @@ func parseInstructions(t *testing.T, filename string, src []byte) []instruction
 				ins = make([]instruction, 0)
 				continue
 			}
-			if strings.Contains(line, "MATCH") {
+			switch extractDataMode(line) {
+			case "json":
+				jsonInst, err := extractInstructionFromJSON(strings.TrimPrefix(line, "json:"), ln)
+				if err != nil {
+					t.Fatalf("At %v:%d: %v", filename, ln, err)
+				}
+				ins = append(ins, jsonInst)
+				break
+			case "classic":
 				match, err := extractPattern(line)
 				if err != nil {
 					t.Fatalf("At %v:%d: %v", filename, ln, err)
@@ -172,12 +200,44 @@ func parseInstructions(t *testing.T, filename string, src []byte) []instruction
 					Match:       match,
 					Replacement: repl,
 				})
+				break
 			}
+
 		}
 	}
 	return ins
 }
 
+func extractInstructionFromJSON(line string, lineNumber int) (instruction, error) {
+	// Use the json.Unmarshal function to parse the JSON into the struct
+	var jsonInst JSONInstruction
+	if err := json.Unmarshal([]byte(line), &jsonInst); err != nil {
+		fmt.Println("Error parsing JSON:", err)
+	}
+
+	ins := instruction{
+		Match:      jsonInst.Match,
+		Confidence: jsonInst.Confidence,
+		Category:   jsonInst.Category,
+		Line:       lineNumber,
+	}
+	return ins, nil
+
+}
+
+func extractDataMode(line string) string {
+
+	if strings.HasPrefix(line, "json") {
+		return "json"
+	}
+	if strings.Contains(line, "MATCH") {
+		return "classic"
+	}
+
+	return ""
+
+}
+
 func extractPattern(line string) (string, error) {
 	a, b := strings.Index(line, "/"), strings.LastIndex(line, "/")
 	if a == -1 || a == b {
diff --git a/testdata/import-alias-naming.go b/testdata/import-alias-naming.go
index 6e2c339..c475f01 100644
--- a/testdata/import-alias-naming.go
+++ b/testdata/import-alias-naming.go
@@ -1,7 +1,8 @@
 package fixtures
 
 import (
-	_ "strings"
+	_ "strings" // _ aliases should be ignored
+	. "dotimport" // . aliases should be ignored
 	bar_foo "strings" // MATCH /import name (bar_foo) must match the regular expression: ^[a-z][a-z0-9]{0,}$/
 	fooBAR "strings"  // MATCH /import name (fooBAR) must match the regular expression: ^[a-z][a-z0-9]{0,}$/
 	v1 "strings"
diff --git a/testdata/import-dot.go b/testdata/import-dot.go
new file mode 100644
index 0000000..2f9ed59
--- /dev/null
+++ b/testdata/import-dot.go
@@ -0,0 +1,22 @@
+// Test that dot imports are flagged.
+
+package fixtures
+
+import (
+	. "context" // in allowedPackages (standard library => just the name without full path)
+	. "errors"  // in allowedPackages (standard library => just the name without full path)
+	. "fmt"     // MATCH /should not use dot imports/
+	"math/rand"
+	tmplt "text/template"
+
+	. "github.com/BurntSushi/toml" // in allowedPackages (not in the standard library)
+)
+
+var _ Stringer // from "fmt"
+var _ = New("fake error")
+var _ = Background()
+
+var _ = Position{} // check a package not in the standard library
+
+var _ = rand.Rand{}      // check non-alias package
+var _ = tmplt.Template{} // check alias package
diff --git a/testdata/json-data-format-atomic.go b/testdata/json-data-format-atomic.go
new file mode 100644
index 0000000..23f73f1
--- /dev/null
+++ b/testdata/json-data-format-atomic.go
@@ -0,0 +1,13 @@
+package fixtures
+
+import (
+	"sync/atomic"
+)
+
+type Counter uint64
+
+func AtomicTests() {
+	x = atomic.AddUint64(&x, 1) // json:{"MATCH": "direct assignment to atomic value","Confidence": 1}
+	x := uint64(1)
+
+}
diff --git a/testdata/json-data-format-var-naming.go b/testdata/json-data-format-var-naming.go
new file mode 100644
index 0000000..fef8522
--- /dev/null
+++ b/testdata/json-data-format-var-naming.go
@@ -0,0 +1,8 @@
+package fixtures
+
+func foo() string {
+	customID := "result"    // ignore
+	customVm := "result"    // json:{"MATCH": "var customVm should be customVM"}
+	CUSTOMER_UP := "result" // json:{"MATCH": "don't use ALL_CAPS in Go names; use CamelCase", "Confidence": 0.8}
+	return customId
+}