mirror of
https://github.com/mgechev/revive.git
synced 2025-09-16 09:06:22 +02:00
feat: implement extensible model
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1,3 @@
|
|||||||
golinter
|
golinter
|
||||||
|
vendor
|
||||||
|
|
||||||
|
56
defaultrules/no-else-return-rule.go
Normal file
56
defaultrules/no-else-return-rule.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package defaultrules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
|
||||||
|
"github.com/mgechev/golinter/file"
|
||||||
|
"github.com/mgechev/golinter/rules"
|
||||||
|
"github.com/mgechev/golinter/visitors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ruleName = "no-else-return"
|
||||||
|
|
||||||
|
// LintElseRule lints given else constructs.
|
||||||
|
type LintElseRule struct {
|
||||||
|
rules.Rule
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies the rule to given file.
|
||||||
|
func (r *LintElseRule) Apply(file *file.File, arguments rules.RuleArguments) []rules.Failure {
|
||||||
|
res := &lintElseVisitor{}
|
||||||
|
visitors.Setup(res, rules.RuleConfig{Name: ruleName, Arguments: arguments}, file)
|
||||||
|
res.Visit(file.GetAST())
|
||||||
|
return res.GetFailures()
|
||||||
|
}
|
||||||
|
|
||||||
|
type lintElseVisitor struct {
|
||||||
|
visitors.RuleVisitor
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *lintElseVisitor) VisitIfStmt(node *ast.IfStmt) {
|
||||||
|
if node.Else == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := node.Else.(*ast.BlockStmt); !ok {
|
||||||
|
// only care about elses without conditions
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(node.Body.List) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// shortDecl := false // does the if statement have a ":=" initialization statement?
|
||||||
|
if node.Init != nil {
|
||||||
|
if as, ok := node.Init.(*ast.AssignStmt); ok && as.Tok == token.DEFINE {
|
||||||
|
// shortDecl = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastStmt := node.Body.List[len(node.Body.List)-1]
|
||||||
|
if _, ok := lastStmt.(*ast.ReturnStmt); ok {
|
||||||
|
w.AddFailure(rules.Failure{
|
||||||
|
Failure: "if block ends with a return statement, so drop this else and outdent its block",
|
||||||
|
Type: rules.FailureTypeWarning,
|
||||||
|
Position: w.GetPosition(node.Else.Pos(), node.Else.End()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
39
file/file.go
Normal file
39
file/file.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
// File abstraction used for representing files.
|
||||||
|
type File struct {
|
||||||
|
Name string
|
||||||
|
files *token.FileSet
|
||||||
|
Content []byte
|
||||||
|
ast *ast.File
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new file
|
||||||
|
func New(name string, content []byte, files *token.FileSet) (*File, error) {
|
||||||
|
f, err := parser.ParseFile(files, name, content, parser.ParseComments)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &File{
|
||||||
|
Name: name,
|
||||||
|
Content: content,
|
||||||
|
files: files,
|
||||||
|
ast: f,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToPosition returns line and column for given position.
|
||||||
|
func (f *File) ToPosition(pos token.Pos) token.Position {
|
||||||
|
return f.files.Position(pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAST returns the AST of the file
|
||||||
|
func (f *File) GetAST() *ast.File {
|
||||||
|
return f.ast
|
||||||
|
}
|
78
formatters/cli_formatter.go
Normal file
78
formatters/cli_formatter.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package formatters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/mgechev/golinter/rules"
|
||||||
|
"github.com/olekukonko/tablewriter"
|
||||||
|
"github.com/ttacon/chalk"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
errorEmoji = ""
|
||||||
|
warningEmoji = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
// CLIFormatter is an implementation of the Formatter interface
|
||||||
|
// which formats the errors to JSON.
|
||||||
|
type CLIFormatter struct {
|
||||||
|
Metadata FormatterMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatFailure(failure rules.Failure) []string {
|
||||||
|
fString := chalk.Blue.Color(failure.Failure)
|
||||||
|
fTypeStr := string(failure.Type)
|
||||||
|
fType := chalk.Red.Color(fTypeStr)
|
||||||
|
lineColumn := failure.Position
|
||||||
|
pos := chalk.Dim.TextStyle(fmt.Sprintf("(%d, %d)", lineColumn.Start.Line, lineColumn.Start.Column))
|
||||||
|
if failure.Type == rules.FailureTypeWarning {
|
||||||
|
fType = chalk.Yellow.Color(fTypeStr)
|
||||||
|
}
|
||||||
|
return []string{failure.GetFilename(), pos, fType, fString}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format formats the failures gotten from the linter.
|
||||||
|
func (f *CLIFormatter) Format(failures []rules.Failure) (string, error) {
|
||||||
|
var result [][]string
|
||||||
|
var totalErrors = 0
|
||||||
|
for _, f := range failures {
|
||||||
|
result = append(result, formatFailure(f))
|
||||||
|
if f.Type == rules.FailureTypeError {
|
||||||
|
totalErrors++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
total := len(failures)
|
||||||
|
ps := "problems"
|
||||||
|
if total == 1 {
|
||||||
|
ps = "problem"
|
||||||
|
}
|
||||||
|
|
||||||
|
fileReport := make(map[string][][]string)
|
||||||
|
|
||||||
|
for _, row := range result {
|
||||||
|
if _, ok := fileReport[row[0]]; !ok {
|
||||||
|
fileReport[row[0]] = [][]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileReport[row[0]] = append(fileReport[row[0]], []string{row[1], row[2], row[3]})
|
||||||
|
}
|
||||||
|
|
||||||
|
output := ""
|
||||||
|
for filename, val := range fileReport {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
table := tablewriter.NewWriter(buf)
|
||||||
|
table.SetBorder(false)
|
||||||
|
table.SetColumnSeparator("")
|
||||||
|
table.SetRowSeparator("")
|
||||||
|
table.SetAutoWrapText(false)
|
||||||
|
table.AppendBulk(val)
|
||||||
|
table.Render()
|
||||||
|
output += chalk.Dim.TextStyle(chalk.Underline.TextStyle(filename) + "\n")
|
||||||
|
output += buf.String() + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
suffix := fmt.Sprintf("\n ✖ %d %s (%d errors) (%d warnings)", total, ps, totalErrors, total-totalErrors)
|
||||||
|
|
||||||
|
return output + suffix, nil
|
||||||
|
}
|
@@ -1,13 +1,15 @@
|
|||||||
package formatters
|
package formatters
|
||||||
|
|
||||||
import "github.com/mgechev/golinter/visitors"
|
import "github.com/mgechev/golinter/rules"
|
||||||
|
|
||||||
|
// FormatterMetadata configuration of a formatter
|
||||||
type FormatterMetadata struct {
|
type FormatterMetadata struct {
|
||||||
Name string
|
Name string
|
||||||
Description string
|
Description string
|
||||||
Sample string
|
Sample string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Formatter defines an interface for failure formatters
|
||||||
type Formatter interface {
|
type Formatter interface {
|
||||||
Format([]visitors.Failure) string
|
Format([]rules.Failure) string
|
||||||
}
|
}
|
||||||
|
22
formatters/json_formatter.go
Normal file
22
formatters/json_formatter.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package formatters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/mgechev/golinter/rules"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JSONFormatter is an implementation of the Formatter interface
|
||||||
|
// which formats the errors to JSON.
|
||||||
|
type JSONFormatter struct {
|
||||||
|
Metadata FormatterMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format formats the failures gotten from the linter.
|
||||||
|
func (f *JSONFormatter) Format(failures []rules.Failure) (string, error) {
|
||||||
|
result, error := json.Marshal(failures)
|
||||||
|
if error != nil {
|
||||||
|
return "", error
|
||||||
|
}
|
||||||
|
return string(result), nil
|
||||||
|
}
|
@@ -1,22 +0,0 @@
|
|||||||
package formatters
|
|
||||||
|
|
||||||
import "github.com/mgechev/golinter/visitors"
|
|
||||||
import "encoding/json"
|
|
||||||
|
|
||||||
type JSONFormatter struct {
|
|
||||||
Metadata FormatterMetadata
|
|
||||||
}
|
|
||||||
|
|
||||||
// {
|
|
||||||
// Name: "JSON Formatter",
|
|
||||||
// Description: "This formatter produces JSON from the errors",
|
|
||||||
// Sample: "[{ \"position\": 10, \"failure\": \"Forbidden semicolon\" }]"
|
|
||||||
// }
|
|
||||||
|
|
||||||
func (f *JSONFormatter) Format(failures []visitors.Failure) (string, error) {
|
|
||||||
result, error := json.Marshal(failures)
|
|
||||||
if error != nil {
|
|
||||||
return "", error
|
|
||||||
}
|
|
||||||
return string(result), nil
|
|
||||||
}
|
|
10
glide.lock
generated
Normal file
10
glide.lock
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
hash: e7fa4d774614236f030e76f8adab9e29c97c0cfc9782e0cffb9c9df47be0e60e
|
||||||
|
updated: 2017-08-27T19:03:09.172223776-07:00
|
||||||
|
imports:
|
||||||
|
- name: github.com/mattn/go-runewidth
|
||||||
|
version: 9e777a8366cce605130a531d2cd6363d07ad7317
|
||||||
|
- name: github.com/olekukonko/tablewriter
|
||||||
|
version: be5337e7b39e64e5f91445ce7e721888dbab7387
|
||||||
|
- name: github.com/ttacon/chalk
|
||||||
|
version: 76b3c8b611dea8f83e49e9ce81fc2b189e0ef3d2
|
||||||
|
testImports: []
|
7
glide.yaml
Normal file
7
glide.yaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package: github.com/mgechev/golinter
|
||||||
|
import:
|
||||||
|
- package: github.com/ttacon/chalk
|
||||||
|
version: ~0.1.0
|
||||||
|
- package: github.com/olekukonko/tablewriter
|
||||||
|
- package: github.com/mattn/go-runewidth
|
||||||
|
version: ~0.0.2
|
45
linter/linter.go
Normal file
45
linter/linter.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package linter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/token"
|
||||||
|
|
||||||
|
"github.com/mgechev/golinter/file"
|
||||||
|
"github.com/mgechev/golinter/rules"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadFile defines an abstraction for reading files.
|
||||||
|
type ReadFile func(path string) (result []byte, err error)
|
||||||
|
|
||||||
|
// Linter is used for lintign set of files.
|
||||||
|
type Linter struct {
|
||||||
|
reader ReadFile
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new Linter
|
||||||
|
func New(reader ReadFile) Linter {
|
||||||
|
return Linter{reader: reader}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lint lints a set of files with the specified rules.
|
||||||
|
func (l *Linter) Lint(filenames []string, ruleSet []rules.Rule) ([]rules.Failure, error) {
|
||||||
|
var fileSet token.FileSet
|
||||||
|
var failures []rules.Failure
|
||||||
|
for _, filename := range filenames {
|
||||||
|
content, err := l.reader(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
file, err := file.New(filename, content, &fileSet)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rule := range ruleSet {
|
||||||
|
currentFailures := rule.Apply(file, []string{})
|
||||||
|
failures = append(failures, currentFailures...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return failures, nil
|
||||||
|
}
|
62
main.go
62
main.go
@@ -2,52 +2,42 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
|
||||||
"go/parser"
|
|
||||||
"go/token"
|
|
||||||
|
|
||||||
"github.com/mgechev/golinter/syntaxvisitor"
|
"github.com/mgechev/golinter/defaultrules"
|
||||||
|
"github.com/mgechev/golinter/formatters"
|
||||||
|
"github.com/mgechev/golinter/linter"
|
||||||
|
"github.com/mgechev/golinter/rules"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CustomLinter struct {
|
func main() {
|
||||||
syntaxvisitor.SyntaxVisitor
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CustomLinter) VisitIdent(node *ast.Ident) {
|
|
||||||
fmt.Println("Child", node.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This example demonstrates how to inspect the AST of a Go program.
|
|
||||||
func ExampleInspect() {
|
|
||||||
// src is the input for which we want to inspect the AST.
|
|
||||||
src := `
|
src := `
|
||||||
package p
|
package p
|
||||||
const c = 1.0
|
|
||||||
var X = f(3.14)*2 + c
|
func Test() {
|
||||||
|
if true {
|
||||||
|
return 42;
|
||||||
|
} else {
|
||||||
|
return 23;
|
||||||
|
}
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
// Create the AST by parsing src.
|
linter := linter.New(func(file string) ([]byte, error) {
|
||||||
fset := token.NewFileSet() // positions are relative to fset
|
return []byte(src), nil
|
||||||
f, err := parser.ParseFile(fset, "src.go", src, 0)
|
})
|
||||||
|
var result []rules.Rule
|
||||||
|
result = append(result, &defaultrules.LintElseRule{})
|
||||||
|
|
||||||
|
failures, err := linter.Lint([]string{"foo.go", "bar.go", "baz.go"}, result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var visitor CustomLinter
|
var formatter formatters.CLIFormatter
|
||||||
visitor.SyntaxVisitor.Impl = &visitor
|
output, err := formatter.Format(failures)
|
||||||
visitor.Visit(f)
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
// output:
|
fmt.Println(output)
|
||||||
// src.go:2:9: p
|
|
||||||
// src.go:3:7: c
|
|
||||||
// src.go:3:11: 1.0
|
|
||||||
// src.go:4:5: X
|
|
||||||
// src.go:4:9: f
|
|
||||||
// src.go:4:11: 3.14
|
|
||||||
// src.go:4:17: 2
|
|
||||||
// src.go:4:21: c
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
ExampleInspect()
|
|
||||||
}
|
}
|
||||||
|
49
rules/rule.go
Normal file
49
rules/rule.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package rules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/token"
|
||||||
|
|
||||||
|
"github.com/mgechev/golinter/file"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// FailureTypeWarning declares failures of type warning
|
||||||
|
FailureTypeWarning = "warning"
|
||||||
|
// FailureTypeError declares failures of type error.
|
||||||
|
FailureTypeError = "error"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FailureType is the type for the failure types.
|
||||||
|
type FailureType string
|
||||||
|
|
||||||
|
// Failure defines a struct for a linting failure.
|
||||||
|
type Failure struct {
|
||||||
|
Failure string
|
||||||
|
Type FailureType
|
||||||
|
Position FailurePosition
|
||||||
|
file *file.File
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFilename returns the filename.
|
||||||
|
func (f *Failure) GetFilename() string {
|
||||||
|
return f.Position.Start.Filename
|
||||||
|
}
|
||||||
|
|
||||||
|
// FailurePosition returns the failure position
|
||||||
|
type FailurePosition struct {
|
||||||
|
Start token.Position
|
||||||
|
End token.Position
|
||||||
|
}
|
||||||
|
|
||||||
|
// RuleArguments is type used for the arguments of a rule.
|
||||||
|
type RuleArguments []string
|
||||||
|
|
||||||
|
type RuleConfig struct {
|
||||||
|
Name string
|
||||||
|
Arguments RuleArguments
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule defines an abstract rule.
|
||||||
|
type Rule interface {
|
||||||
|
Apply(file *file.File, args RuleArguments) []Failure
|
||||||
|
}
|
37
visitors/rule_visitor.go
Normal file
37
visitors/rule_visitor.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package visitors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/token"
|
||||||
|
|
||||||
|
"github.com/mgechev/golinter/file"
|
||||||
|
"github.com/mgechev/golinter/rules"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RuleVisitor defines a struct for a visitor.
|
||||||
|
type RuleVisitor struct {
|
||||||
|
SyntaxVisitor
|
||||||
|
RuleName string
|
||||||
|
RuleArguments rules.RuleArguments
|
||||||
|
failures []rules.Failure
|
||||||
|
File *file.File
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFailure adds a failure to the ist of failures.
|
||||||
|
func (w *RuleVisitor) AddFailure(failure rules.Failure) {
|
||||||
|
w.failures = append(w.failures, failure)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFailures returns the list of failures.
|
||||||
|
func (w *RuleVisitor) GetFailures() []rules.Failure {
|
||||||
|
return w.failures
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPosition returns position by given start and end token.Pos.
|
||||||
|
func (w *RuleVisitor) GetPosition(start token.Pos, end token.Pos) rules.FailurePosition {
|
||||||
|
s := w.File.ToPosition(start)
|
||||||
|
e := w.File.ToPosition(end)
|
||||||
|
return rules.FailurePosition{
|
||||||
|
Start: s,
|
||||||
|
End: e,
|
||||||
|
}
|
||||||
|
}
|
@@ -1,35 +0,0 @@
|
|||||||
package visitors
|
|
||||||
|
|
||||||
import (
|
|
||||||
"go/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RuleArguments []string
|
|
||||||
|
|
||||||
const DefaultLength = 1
|
|
||||||
|
|
||||||
type Failure struct {
|
|
||||||
Failure string
|
|
||||||
Position token.Pos
|
|
||||||
}
|
|
||||||
|
|
||||||
type RuleVisitor struct {
|
|
||||||
SyntaxVisitor
|
|
||||||
ruleName string
|
|
||||||
ruleArguments RuleArguments
|
|
||||||
failures []Failure
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(ruleName string, ruleArguments RuleArguments) *RuleVisitor {
|
|
||||||
result := RuleVisitor{ruleName: ruleName, ruleArguments: ruleArguments}
|
|
||||||
result.failures = make([]Failure, DefaultLength)
|
|
||||||
return &result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *RuleVisitor) AddFailure(failure Failure) {
|
|
||||||
w.failures = append(w.failures, failure)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *RuleVisitor) GetFailures() []Failure {
|
|
||||||
return w.failures
|
|
||||||
}
|
|
27
visitors/setup.go
Normal file
27
visitors/setup.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package visitors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/mgechev/golinter/file"
|
||||||
|
"github.com/mgechev/golinter/rules"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Setup sets the proper pointers of given visitor.
|
||||||
|
func Setup(v interface{}, conf rules.RuleConfig, file *file.File) error {
|
||||||
|
val := reflect.ValueOf(v).Elem()
|
||||||
|
field := val.FieldByName("RuleVisitor")
|
||||||
|
if !field.IsValid() {
|
||||||
|
return errors.New("invalid rule visitor")
|
||||||
|
}
|
||||||
|
field.Set(reflect.ValueOf(RuleVisitor{RuleName: conf.Name, RuleArguments: conf.Arguments, File: file}))
|
||||||
|
|
||||||
|
field = val.FieldByName("Impl")
|
||||||
|
if !field.IsValid() {
|
||||||
|
return errors.New("invalid rule visitor")
|
||||||
|
}
|
||||||
|
field.Set(reflect.ValueOf(v))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@@ -5,10 +5,13 @@ import (
|
|||||||
"go/ast"
|
"go/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// SyntaxVisitor implements a visitor which knows how to handle the individual
|
||||||
|
// Go lang syntax constructs.
|
||||||
type SyntaxVisitor struct {
|
type SyntaxVisitor struct {
|
||||||
Impl Visitor
|
Impl Visitor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Visit accepts an ast.Node and traverse its children.
|
||||||
func (w *SyntaxVisitor) Visit(node ast.Node) {
|
func (w *SyntaxVisitor) Visit(node ast.Node) {
|
||||||
if node == nil {
|
if node == nil {
|
||||||
return
|
return
|
||||||
@@ -181,6 +184,7 @@ func (w *SyntaxVisitor) Visit(node ast.Node) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VisitUnaryExpr visits an unary expression.
|
||||||
func (w *SyntaxVisitor) VisitUnaryExpr(node *ast.UnaryExpr) {
|
func (w *SyntaxVisitor) VisitUnaryExpr(node *ast.UnaryExpr) {
|
||||||
w.Impl.Visit(node.X)
|
w.Impl.Visit(node.X)
|
||||||
}
|
}
|
Reference in New Issue
Block a user