@@ -0,0 +1,107 @@
1+package facts
2+3+import (
4+"go/ast"
5+"go/token"
6+"path/filepath"
7+"reflect"
8+"strings"
9+10+"golang.org/x/tools/go/analysis"
11+)
12+13+// A directive is a comment of the form '//lint:<command>
14+// [arguments...]'. It represents instructions to the static analysis
15+// tool.
16+type Directive struct {
17+Command string
18+Arguments []string
19+Directive *ast.Comment
20+Node ast.Node
21+}
22+23+type SerializedDirective struct {
24+Command string
25+Arguments []string
26+// The position of the comment
27+DirectivePosition token.Position
28+// The position of the node that the comment is attached to
29+NodePosition token.Position
30+}
31+32+func parseDirective(s string) (cmd string, args []string) {
33+if !strings.HasPrefix(s, "//lint:") {
34+return "", nil
35+ }
36+s = strings.TrimPrefix(s, "//lint:")
37+fields := strings.Split(s, " ")
38+return fields[0], fields[1:]
39+}
40+41+func directives(pass *analysis.Pass) (interface{}, error) {
42+return ParseDirectives(pass.Files, pass.Fset), nil
43+}
44+45+func ParseDirectives(files []*ast.File, fset *token.FileSet) []Directive {
46+var dirs []Directive
47+for _, f := range files {
48+// OPT(dh): in our old code, we skip all the commentmap work if we
49+// couldn't find any directives, benchmark if that's actually
50+// worth doing
51+cm := ast.NewCommentMap(fset, f, f.Comments)
52+for node, cgs := range cm {
53+for _, cg := range cgs {
54+for _, c := range cg.List {
55+if !strings.HasPrefix(c.Text, "//lint:") {
56+continue
57+ }
58+cmd, args := parseDirective(c.Text)
59+d := Directive{
60+Command: cmd,
61+Arguments: args,
62+Directive: c,
63+Node: node,
64+ }
65+dirs = append(dirs, d)
66+ }
67+ }
68+ }
69+ }
70+return dirs
71+}
72+73+// duplicated from report.DisplayPosition to break import cycle
74+func displayPosition(fset *token.FileSet, p token.Pos) token.Position {
75+if p == token.NoPos {
76+return token.Position{}
77+ }
78+79+// Only use the adjusted position if it points to another Go file.
80+// This means we'll point to the original file for cgo files, but
81+// we won't point to a YACC grammar file.
82+pos := fset.PositionFor(p, false)
83+adjPos := fset.PositionFor(p, true)
84+85+if filepath.Ext(adjPos.Filename) == ".go" {
86+return adjPos
87+ }
88+89+return pos
90+}
91+92+var Directives = &analysis.Analyzer{
93+Name: "directives",
94+Doc: "extracts linter directives",
95+Run: directives,
96+RunDespiteErrors: true,
97+ResultType: reflect.TypeOf([]Directive{}),
98+}
99+100+func SerializeDirective(dir Directive, fset *token.FileSet) SerializedDirective {
101+return SerializedDirective{
102+Command: dir.Command,
103+Arguments: dir.Arguments,
104+DirectivePosition: displayPosition(fset, dir.Directive.Pos()),
105+NodePosition: displayPosition(fset, dir.Node.Pos()),
106+ }
107+}