GoPLS Viewer

Home|gopls/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.
9package buildtag
10
11import (
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
23const Doc = "check that +build tags are well-formed and correctly located"
24
25var Analyzer = &analysis.Analyzer{
26    Name"buildtag",
27    Doc:  Doc,
28    Run:  runBuildTag,
29}
30
31func runBuildTag(pass *analysis.Pass) (interface{}, error) {
32    for _f := range pass.Files {
33        checkGoFile(passf)
34    }
35    for _name := range pass.OtherFiles {
36        if err := checkOtherFile(passname); err != nil {
37            return nilerr
38        }
39    }
40    for _name := range pass.IgnoredFiles {
41        if strings.HasSuffix(name".go") {
42            ferr := parser.ParseFile(pass.Fsetnamenilparser.ParseComments)
43            if err != nil {
44                // Not valid Go source code - not our job to diagnose, so ignore.
45                return nilnil
46            }
47            checkGoFile(passf)
48        } else {
49            if err := checkOtherFile(passname); err != nil {
50                return nilerr
51            }
52        }
53    }
54    return nilnil
55}
56
57func checkGoFile(pass *analysis.Passf *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.Slashc.Text)
80        }
81    }
82}
83
84func checkOtherFile(pass *analysis.Passfilename stringerror {
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    contenttferr := analysisutil.ReadFile(pass.Fsetfilename)
92    if err != nil {
93        return err
94    }
95
96    check.file(token.Pos(tf.Base()), string(content))
97    return nil
98}
99
100type 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
112func (check *checkerinit(pass *analysis.Pass) {
113    check.pass = pass
114    check.goBuildOK = true
115    check.plusBuildOK = true
116    check.crossCheck = true
117}
118
119func (check *checkerfile(pos token.Postext 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
202func (check *checkercomment(pos token.Postext string) {
203    if strings.HasPrefix(text"//") {
204        if strings.Contains(text"+build") {
205            check.plusBuildLine(postext)
206        }
207        if strings.Contains(text"//go:build") {
208            check.goBuildLine(postext)
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(posline)
228                }
229                pos += token.Pos(i)
230                text = text[i:]
231            }
232            check.inStar = false
233        }
234    }
235}
236
237func (check *checkergoBuildLine(pos token.Posline 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    xerr := 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
274func (check *checkerplusBuildLine(pos token.Posline 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        yerr := 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{Xcheck.plusBuildYy}
332        }
333    }
334}
335
336func (check *checkerfinish() {
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    lineserr := 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        yerr := 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{XwantYy}
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}
368
MembersX
checker.goBuildLine.i
checker.goBuildLine.x
checker.plusBuildLine.BlockStmt.y
checker.file.plusBuildCutoff
checker.goBuildLine.line
runBuildTag
checkOtherFile.err
checker
checker.crossCheck
checker.file.check
checker.file
checker.plusBuildLine.RangeStmt_7587.BlockStmt.RangeStmt_7622.elem
checker.finish.err
checker.finish.check
checker.finish.RangeStmt_8985.line
analysis
Doc
runBuildTag.RangeStmt_873.name
checker.init.check
checker.file.fullText
checker.goBuildLine.pos
checkOtherFile.pass
checker.file.BlockStmt.BlockStmt.BlockStmt.i
checker.plusBuildLine.i
checker.plusBuildLine.fields
runBuildTag.RangeStmt_873.BlockStmt.BlockStmt.err
checker.file.text
checker.comment.pos
checker.plusBuildLine
checker.plusBuildLine.RangeStmt_7587.BlockStmt.RangeStmt_7622.BlockStmt.RangeStmt_7881.c
constraint
checker.plusBuildLine.check
checker.plusBuildLine.BlockStmt.err
checker.finish.RangeStmt_8985.BlockStmt.y
checker.finish.RangeStmt_8985.BlockStmt.err
checker.plusBuild
checker.goBuildLine.check
ast
unicode
runBuildTag.RangeStmt_754.BlockStmt.err
checkGoFile.pass
checkOtherFile
checkOtherFile.filename
runBuildTag.RangeStmt_696.f
checkOtherFile.tf
checker.inStar
checker.goBuild
checker.plusBuildLine.pos
parser
strings
checkGoFile
checkGoFile.RangeStmt_1395.group
checker.file.BlockStmt.i
checker.goBuildLine.err
runBuildTag.RangeStmt_873.BlockStmt.BlockStmt.f
checker.goBuildPos
checker.plusBuildLine.line
checker.plusBuildLine.RangeStmt_7587.arg
checker.pass
checker.init.pass
checker.file.pos
checker.comment
checker.comment.BlockStmt.BlockStmt.BlockStmt.i
checker.goBuildLine
checker.finish
checker.finish.want
checkGoFile.RangeStmt_1395.BlockStmt.RangeStmt_1808.c
checkOtherFile.content
checker.plusBuildPos
checker.comment.BlockStmt.i
runBuildTag.RangeStmt_754.name
checker.init
checker.comment.text
checker.finish.lines
checkGoFile.check
checker.goBuildOK
token
runBuildTag.pass
checkOtherFile.check
checker.plusBuildOK
checker.comment.check
analysisutil
checkGoFile.f
Members
X