golang.org/x/tools/gopls@v0.15.3/internal/analysis/embeddirective/embeddirective.go (about) 1 // Copyright 2022 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 package embeddirective 6 7 import ( 8 _ "embed" 9 "go/ast" 10 "go/token" 11 "go/types" 12 "strings" 13 14 "golang.org/x/tools/go/analysis" 15 "golang.org/x/tools/internal/analysisinternal" 16 ) 17 18 //go:embed doc.go 19 var doc string 20 21 var Analyzer = &analysis.Analyzer{ 22 Name: "embed", 23 Doc: analysisinternal.MustExtractDoc(doc, "embed"), 24 Run: run, 25 RunDespiteErrors: true, 26 URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/embeddirective", 27 } 28 29 const FixCategory = "addembedimport" // recognized by gopls ApplyFix 30 31 func run(pass *analysis.Pass) (interface{}, error) { 32 for _, f := range pass.Files { 33 comments := embedDirectiveComments(f) 34 if len(comments) == 0 { 35 continue // nothing to check 36 } 37 38 hasEmbedImport := false 39 for _, imp := range f.Imports { 40 if imp.Path.Value == `"embed"` { 41 hasEmbedImport = true 42 break 43 } 44 } 45 46 for _, c := range comments { 47 pos, end := c.Pos(), c.Pos()+token.Pos(len("//go:embed")) 48 49 if !hasEmbedImport { 50 pass.Report(analysis.Diagnostic{ 51 Pos: pos, 52 End: end, 53 Message: `must import "embed" when using go:embed directives`, 54 Category: FixCategory, 55 SuggestedFixes: []analysis.SuggestedFix{{ 56 Message: `Add missing "embed" import`, 57 // No TextEdits => computed by a gopls command. 58 }}, 59 }) 60 } 61 62 var msg string 63 spec := nextVarSpec(c, f) 64 switch { 65 case spec == nil: 66 msg = `go:embed directives must precede a "var" declaration` 67 case len(spec.Names) != 1: 68 msg = "declarations following go:embed directives must define a single variable" 69 case len(spec.Values) > 0: 70 msg = "declarations following go:embed directives must not specify a value" 71 case !embeddableType(pass.TypesInfo.Defs[spec.Names[0]]): 72 msg = "declarations following go:embed directives must be of type string, []byte or embed.FS" 73 } 74 if msg != "" { 75 pass.Report(analysis.Diagnostic{ 76 Pos: pos, 77 End: end, 78 Message: msg, 79 }) 80 } 81 } 82 } 83 return nil, nil 84 } 85 86 // embedDirectiveComments returns all comments in f that contains a //go:embed directive. 87 func embedDirectiveComments(f *ast.File) []*ast.Comment { 88 comments := []*ast.Comment{} 89 for _, cg := range f.Comments { 90 for _, c := range cg.List { 91 if strings.HasPrefix(c.Text, "//go:embed ") { 92 comments = append(comments, c) 93 } 94 } 95 } 96 return comments 97 } 98 99 // nextVarSpec returns the ValueSpec for the variable declaration immediately following 100 // the go:embed comment, or nil if the next declaration is not a variable declaration. 101 func nextVarSpec(com *ast.Comment, f *ast.File) *ast.ValueSpec { 102 // Embed directives must be followed by a declaration of one variable with no value. 103 // There may be comments and empty lines between the directive and the declaration. 104 var nextDecl ast.Decl 105 for _, d := range f.Decls { 106 if com.End() < d.End() { 107 nextDecl = d 108 break 109 } 110 } 111 if nextDecl == nil || nextDecl.Pos() == token.NoPos { 112 return nil 113 } 114 decl, ok := nextDecl.(*ast.GenDecl) 115 if !ok { 116 return nil 117 } 118 if decl.Tok != token.VAR { 119 return nil 120 } 121 122 // var declarations can be both freestanding and blocks (with parenthesis). 123 // Only the first variable spec following the directive is interesting. 124 var nextSpec ast.Spec 125 for _, s := range decl.Specs { 126 if com.End() < s.End() { 127 nextSpec = s 128 break 129 } 130 } 131 if nextSpec == nil { 132 return nil 133 } 134 spec, ok := nextSpec.(*ast.ValueSpec) 135 if !ok { 136 // Invalid AST, but keep going. 137 return nil 138 } 139 return spec 140 } 141 142 // embeddableType in go:embed directives are string, []byte or embed.FS. 143 func embeddableType(o types.Object) bool { 144 if o == nil { 145 return false 146 } 147 148 // For embed.FS the underlying type is an implementation detail. 149 // As long as the named type resolves to embed.FS, it is OK. 150 if named, ok := o.Type().(*types.Named); ok { 151 obj := named.Obj() 152 if obj.Pkg() != nil && obj.Pkg().Path() == "embed" && obj.Name() == "FS" { 153 return true 154 } 155 } 156 157 switch v := o.Type().Underlying().(type) { 158 case *types.Basic: 159 return types.Identical(v, types.Typ[types.String]) 160 case *types.Slice: 161 return types.Identical(v.Elem(), types.Typ[types.Byte]) 162 } 163 164 return false 165 }