.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.1.1-0.20210319172145-bda8f5cee399 / go / analysis / passes / buildtag / buildtag.go
1 // Copyright 2013 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 //go:build go1.16
6 // +build go1.16
7
8 // Package buildtag defines an Analyzer that checks build tags.
9 package buildtag
10
11 import (
12         "go/ast"
13         "go/build/constraint"
14         "go/parser"
15         "go/token"
16         "strings"
17         "unicode"
18
19         "golang.org/x/tools/go/analysis"
20         "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
21 )
22
23 const Doc = "check that +build tags are well-formed and correctly located"
24
25 var Analyzer = &analysis.Analyzer{
26         Name: "buildtag",
27         Doc:  Doc,
28         Run:  runBuildTag,
29 }
30
31 func runBuildTag(pass *analysis.Pass) (interface{}, error) {
32         for _, f := range pass.Files {
33                 checkGoFile(pass, f)
34         }
35         for _, name := range pass.OtherFiles {
36                 if err := checkOtherFile(pass, name); err != nil {
37                         return nil, err
38                 }
39         }
40         for _, name := range pass.IgnoredFiles {
41                 if strings.HasSuffix(name, ".go") {
42                         f, err := parser.ParseFile(pass.Fset, name, nil, parser.ParseComments)
43                         if err != nil {
44                                 // Not valid Go source code - not our job to diagnose, so ignore.
45                                 return nil, nil
46                         }
47                         checkGoFile(pass, f)
48                 } else {
49                         if err := checkOtherFile(pass, name); err != nil {
50                                 return nil, err
51                         }
52                 }
53         }
54         return nil, nil
55 }
56
57 func checkGoFile(pass *analysis.Pass, f *ast.File) {
58         var check checker
59         check.init(pass)
60         defer check.finish()
61
62         for _, group := range f.Comments {
63                 // A +build comment is ignored after or adjoining the package declaration.
64                 if group.End()+1 >= f.Package {
65                         check.plusBuildOK = false
66                 }
67                 // A //go:build comment is ignored after the package declaration
68                 // (but adjoining it is OK, in contrast to +build comments).
69                 if group.Pos() >= f.Package {
70                         check.goBuildOK = false
71                 }
72
73                 // Check each line of a //-comment.
74                 for _, c := range group.List {
75                         // "+build" is ignored within or after a /*...*/ comment.
76                         if !strings.HasPrefix(c.Text, "//") {
77                                 check.plusBuildOK = false
78                         }
79                         check.comment(c.Slash, c.Text)
80                 }
81         }
82 }
83
84 func checkOtherFile(pass *analysis.Pass, filename string) error {
85         var check checker
86         check.init(pass)
87         defer check.finish()
88
89         // We cannot use the Go parser, since this may not be a Go source file.
90         // Read the raw bytes instead.
91         content, tf, err := analysisutil.ReadFile(pass.Fset, filename)
92         if err != nil {
93                 return err
94         }
95
96         check.file(token.Pos(tf.Base()), string(content))
97         return nil
98 }
99
100 type checker struct {
101         pass         *analysis.Pass
102         plusBuildOK  bool            // "+build" lines still OK
103         goBuildOK    bool            // "go:build" lines still OK
104         crossCheck   bool            // cross-check go:build and +build lines when done reading file
105         inStar       bool            // currently in a /* */ comment
106         goBuildPos   token.Pos       // position of first go:build line found
107         plusBuildPos token.Pos       // position of first "+build" line found
108         goBuild      constraint.Expr // go:build constraint found
109         plusBuild    constraint.Expr // AND of +build constraints found
110 }
111
112 func (check *checker) init(pass *analysis.Pass) {
113         check.pass = pass
114         check.goBuildOK = true
115         check.plusBuildOK = true
116         check.crossCheck = true
117 }
118
119 func (check *checker) file(pos token.Pos, text string) {
120         // Determine cutpoint where +build comments are no longer valid.
121         // They are valid in leading // comments in the file followed by
122         // a blank line.
123         //
124         // This must be done as a separate pass because of the
125         // requirement that the comment be followed by a blank line.
126         var plusBuildCutoff int
127         fullText := text
128         for text != "" {
129                 i := strings.Index(text, "\n")
130                 if i < 0 {
131                         i = len(text)
132                 } else {
133                         i++
134                 }
135                 offset := len(fullText) - len(text)
136                 line := text[:i]
137                 text = text[i:]
138                 line = strings.TrimSpace(line)
139                 if !strings.HasPrefix(line, "//") && line != "" {
140                         break
141                 }
142                 if line == "" {
143                         plusBuildCutoff = offset
144                 }
145         }
146
147         // Process each line.
148         // Must stop once we hit goBuildOK == false
149         text = fullText
150         check.inStar = false
151         for text != "" {
152                 i := strings.Index(text, "\n")
153                 if i < 0 {
154                         i = len(text)
155                 } else {
156                         i++
157                 }
158                 offset := len(fullText) - len(text)
159                 line := text[:i]
160                 text = text[i:]
161                 check.plusBuildOK = offset < plusBuildCutoff
162
163                 if strings.HasPrefix(line, "//") {
164                         check.comment(pos+token.Pos(offset), line)
165                         continue
166                 }
167
168                 // Keep looking for the point at which //go:build comments
169                 // stop being allowed. Skip over, cut out any /* */ comments.
170                 for {
171                         line = strings.TrimSpace(line)
172                         if check.inStar {
173                                 i := strings.Index(line, "*/")
174                                 if i < 0 {
175                                         line = ""
176                                         break
177                                 }
178                                 line = line[i+len("*/"):]
179                                 check.inStar = false
180                                 continue
181                         }
182                         if strings.HasPrefix(line, "/*") {
183                                 check.inStar = true
184                                 line = line[len("/*"):]
185                                 continue
186                         }
187                         break
188                 }
189                 if line != "" {
190                         // Found non-comment non-blank line.
191                         // Ends space for valid //go:build comments,
192                         // but also ends the fraction of the file we can
193                         // reliably parse. From this point on we might
194                         // incorrectly flag "comments" inside multiline
195                         // string constants or anything else (this might
196                         // not even be a Go program). So stop.
197                         break
198                 }
199         }
200 }
201
202 func (check *checker) comment(pos token.Pos, text string) {
203         if strings.HasPrefix(text, "//") {
204                 if strings.Contains(text, "+build") {
205                         check.plusBuildLine(pos, text)
206                 }
207                 if strings.Contains(text, "//go:build") {
208                         check.goBuildLine(pos, text)
209                 }
210         }
211         if strings.HasPrefix(text, "/*") {
212                 if i := strings.Index(text, "\n"); i >= 0 {
213                         // multiline /* */ comment - process interior lines
214                         check.inStar = true
215                         i++
216                         pos += token.Pos(i)
217                         text = text[i:]
218                         for text != "" {
219                                 i := strings.Index(text, "\n")
220                                 if i < 0 {
221                                         i = len(text)
222                                 } else {
223                                         i++
224                                 }
225                                 line := text[:i]
226                                 if strings.HasPrefix(line, "//") {
227                                         check.comment(pos, line)
228                                 }
229                                 pos += token.Pos(i)
230                                 text = text[i:]
231                         }
232                         check.inStar = false
233                 }
234         }
235 }
236
237 func (check *checker) goBuildLine(pos token.Pos, line string) {
238         if !constraint.IsGoBuild(line) {
239                 if !strings.HasPrefix(line, "//go:build") && constraint.IsGoBuild("//"+strings.TrimSpace(line[len("//"):])) {
240                         check.pass.Reportf(pos, "malformed //go:build line (space between // and go:build)")
241                 }
242                 return
243         }
244         if !check.goBuildOK || check.inStar {
245                 check.pass.Reportf(pos, "misplaced //go:build comment")
246                 check.crossCheck = false
247                 return
248         }
249
250         if check.goBuildPos == token.NoPos {
251                 check.goBuildPos = pos
252         } else {
253                 check.pass.Reportf(pos, "unexpected extra //go:build line")
254                 check.crossCheck = false
255         }
256
257         // testing hack: stop at // ERROR
258         if i := strings.Index(line, " // ERROR "); i >= 0 {
259                 line = line[:i]
260         }
261
262         x, err := constraint.Parse(line)
263         if err != nil {
264                 check.pass.Reportf(pos, "%v", err)
265                 check.crossCheck = false
266                 return
267         }
268
269         if check.goBuild == nil {
270                 check.goBuild = x
271         }
272 }
273
274 func (check *checker) plusBuildLine(pos token.Pos, line string) {
275         line = strings.TrimSpace(line)
276         if !constraint.IsPlusBuild(line) {
277                 // Comment with +build but not at beginning.
278                 // Only report early in file.
279                 if check.plusBuildOK && !strings.HasPrefix(line, "// want") {
280                         check.pass.Reportf(pos, "possible malformed +build comment")
281                 }
282                 return
283         }
284         if !check.plusBuildOK { // inStar implies !plusBuildOK
285                 check.pass.Reportf(pos, "misplaced +build comment")
286                 check.crossCheck = false
287         }
288
289         if check.plusBuildPos == token.NoPos {
290                 check.plusBuildPos = pos
291         }
292
293         // testing hack: stop at // ERROR
294         if i := strings.Index(line, " // ERROR "); i >= 0 {
295                 line = line[:i]
296         }
297
298         fields := strings.Fields(line[len("//"):])
299         // IsPlusBuildConstraint check above implies fields[0] == "+build"
300         for _, arg := range fields[1:] {
301                 for _, elem := range strings.Split(arg, ",") {
302                         if strings.HasPrefix(elem, "!!") {
303                                 check.pass.Reportf(pos, "invalid double negative in build constraint: %s", arg)
304                                 check.crossCheck = false
305                                 continue
306                         }
307                         elem = strings.TrimPrefix(elem, "!")
308                         for _, c := range elem {
309                                 if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
310                                         check.pass.Reportf(pos, "invalid non-alphanumeric build constraint: %s", arg)
311                                         check.crossCheck = false
312                                         break
313                                 }
314                         }
315                 }
316         }
317
318         if check.crossCheck {
319                 y, err := constraint.Parse(line)
320                 if err != nil {
321                         // Should never happen - constraint.Parse never rejects a // +build line.
322                         // Also, we just checked the syntax above.
323                         // Even so, report.
324                         check.pass.Reportf(pos, "%v", err)
325                         check.crossCheck = false
326                         return
327                 }
328                 if check.plusBuild == nil {
329                         check.plusBuild = y
330                 } else {
331                         check.plusBuild = &constraint.AndExpr{X: check.plusBuild, Y: y}
332                 }
333         }
334 }
335
336 func (check *checker) finish() {
337         if !check.crossCheck || check.plusBuildPos == token.NoPos || check.goBuildPos == token.NoPos {
338                 return
339         }
340
341         // Have both //go:build and // +build,
342         // with no errors found (crossCheck still true).
343         // Check they match.
344         var want constraint.Expr
345         lines, err := constraint.PlusBuildLines(check.goBuild)
346         if err != nil {
347                 check.pass.Reportf(check.goBuildPos, "%v", err)
348                 return
349         }
350         for _, line := range lines {
351                 y, err := constraint.Parse(line)
352                 if err != nil {
353                         // Definitely should not happen, but not the user's fault.
354                         // Do not report.
355                         return
356                 }
357                 if want == nil {
358                         want = y
359                 } else {
360                         want = &constraint.AndExpr{X: want, Y: y}
361                 }
362         }
363         if want.String() != check.plusBuild.String() {
364                 check.pass.Reportf(check.plusBuildPos, "+build lines do not match //go:build condition")
365                 return
366         }
367 }