-
Notifications
You must be signed in to change notification settings - Fork 42
/
ruleguard.go
189 lines (154 loc) · 5.35 KB
/
ruleguard.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
package ruleguard
import (
"go/ast"
"go/build"
"go/token"
"go/types"
"io"
"github.com/quasilyte/go-ruleguard/ruleguard/ir"
)
// Engine is the main ruleguard package API object.
//
// First, load some ruleguard files with Load() to build a rule set.
// Then use Run() to execute the rules.
//
// It's advised to have only 1 engine per application as it does a lot of caching.
// The Run() method is synchronized, so it can be used concurrently.
//
// An Engine must be created with NewEngine() function.
type Engine struct {
impl *engine
// BuildContext can be used as an override for build.Default context.
// Used during the Go packages resolving.
//
// Use Engine.InferBuildContext() to create a sensible default
// for this field that is better than build.Default.
// We're not using this by default to avoid the excessive work
// if you already have a properly initialized build.Context object.
//
// nil will result in build.Default usage.
BuildContext *build.Context
}
// NewEngine creates an engine with empty rule set.
func NewEngine() *Engine {
return &Engine{impl: newEngine()}
}
func (e *Engine) InferBuildContext() {
e.BuildContext = inferBuildContext()
}
// Load reads a ruleguard file from r and adds it to the engine rule set.
//
// Load() is not thread-safe, especially if used concurrently with Run() method.
// It's advised to Load() all ruleguard files under a critical section (like sync.Once)
// and then use Run() to execute all of them.
func (e *Engine) Load(ctx *LoadContext, filename string, r io.Reader) error {
return e.impl.Load(ctx, e.BuildContext, filename, r)
}
// LoadFromIR is like Load(), but it takes already parsed IR file as an input.
//
// This method can be useful if you're trying to embed a precompiled rules file
// into your binary.
func (e *Engine) LoadFromIR(ctx *LoadContext, filename string, f *ir.File) error {
return e.impl.LoadFromIR(ctx, e.BuildContext, filename, f)
}
// LoadedGroups returns information about all currently loaded rule groups.
func (e *Engine) LoadedGroups() []GoRuleGroup {
return e.impl.LoadedGroups()
}
// Run executes all loaded rules on a given file.
// Matched rules invoke `RunContext.Report()` method.
//
// Run() is thread-safe, unless used in parallel with Load(),
// which modifies the engine state.
func (e *Engine) Run(ctx *RunContext, f *ast.File) error {
return e.impl.Run(ctx, e.BuildContext, f)
}
type LoadContext struct {
DebugFunc string
DebugImports bool
DebugPrint func(string)
// GroupFilter is called for every rule group being parsed.
// If function returns false, that group will not be included
// in the resulting rules set.
// Nil filter accepts all rule groups.
GroupFilter func(*GoRuleGroup) bool
Fset *token.FileSet
}
type RunContext struct {
Debug string
DebugImports bool
DebugPrint func(string)
Types *types.Info
Sizes types.Sizes
Fset *token.FileSet
Pkg *types.Package
// Report is a function that is called for every successful ruleguard match.
// The pointer to ReportData is reused, it should not be kept.
// If you want to keep it after Report() returns, make a copy.
Report func(*ReportData)
GoVersion GoVersion
// TruncateLen is a length threshold (in bytes) for interpolated vars in Report() templates.
//
// Truncation removes the part of the string in the middle and replaces it with <...>
// so it meets the max length constraint.
//
// The default value is 60 (implied if value is 0).
//
// Note that this value is ignored for Suggest templates.
// Ruleguard doesn't truncate suggested replacement candidates.
TruncateLen int
}
type ReportData struct {
RuleInfo GoRuleInfo
Node ast.Node
Message string
Suggestion *Suggestion
// Experimental: fields below are part of the experiment.
// They'll probably be removed or changed over time.
Func *ast.FuncDecl
}
type Suggestion struct {
From token.Pos
To token.Pos
Replacement []byte
}
type GoRuleInfo struct {
// Line is a line inside a file that defined this rule.
Line int
// Group is a function that contains this rule.
Group *GoRuleGroup
}
type GoRuleGroup struct {
// Name is a function name associated with this rule group.
Name string
// Pos is a location where this rule group was defined.
Pos token.Position
// Line is a source code line number inside associated file.
// A pair of Filename:Line form a conventional location string.
Line int
// Filename is a file that defined this rule group.
Filename string
// DocTags contains a list of keys from the `gorules:tags` comment.
DocTags []string
// DocSummary is a short one sentence description.
// Filled from the `doc:summary` pragma content.
DocSummary string
// DocBefore is a code snippet of code that will violate rule.
// Filled from the `doc:before` pragma content.
DocBefore string
// DocAfter is a code snippet of fixed code that complies to the rule.
// Filled from the `doc:after` pragma content.
DocAfter string
// DocNote is an optional caution message or advice.
// Usually, it's used to reference some external resource, like
// issue on the GitHub.
// Filled from the `doc:note` pragma content.
DocNote string
}
// ImportError is returned when a ruleguard file references a package that cannot be imported.
type ImportError struct {
msg string
err error
}
func (e *ImportError) Error() string { return e.msg }
func (e *ImportError) Unwrap() error { return e.err }