Skip to content

Commit 4fb47d0

Browse files
committed
gopls/internal/analysis/embeddirective: call AddImport directly
This CL causes the embeddirective analyzer to compute the necessary edits itself, using AddImport, rather than using gopls' hacky lazy-edits mechanism, which requires gopls's ApplyFix command to compute the edits later. Also, support blank imports in AddImport. Change-Id: Ie51a316503933fbea05ed418ed251f7af9cc624f Reviewed-on: https://go-review.googlesource.com/c/tools/+/705475 Reviewed-by: Robert Findley <rfindley@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent cb57b4c commit 4fb47d0

4 files changed

Lines changed: 58 additions & 61 deletions

File tree

gopls/internal/analysis/embeddirective/embeddirective.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ var Analyzer = &analysis.Analyzer{
2626
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/embeddirective",
2727
}
2828

29-
const FixCategory = "addembedimport" // recognized by gopls ApplyFix
30-
3129
func run(pass *analysis.Pass) (any, error) {
3230
for _, f := range pass.Files {
3331
comments := embedDirectiveComments(f)
@@ -47,16 +45,19 @@ func run(pass *analysis.Pass) (any, error) {
4745
pos, end := c.Pos(), c.Pos()+token.Pos(len("//go:embed"))
4846

4947
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-
})
48+
// Add blank import of "embed".
49+
_, _, edits := analysisinternal.AddImport(pass.TypesInfo, f, "_", "embed", "", c.Pos())
50+
if len(edits) > 0 {
51+
pass.Report(analysis.Diagnostic{
52+
Pos: pos,
53+
End: end,
54+
Message: `must import "embed" when using go:embed directives`,
55+
SuggestedFixes: []analysis.SuggestedFix{{
56+
Message: `Add missing "embed" import`,
57+
TextEdits: edits,
58+
}},
59+
})
60+
}
6061
}
6162

6263
var msg string
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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 a
6+
7+
import (
8+
_ "embed"
9+
"fmt"
10+
)
11+
12+
//go:embed embedtext // want "must import \"embed\" when using go:embed directives"
13+
var s string
14+
15+
// This is main function
16+
func main() {
17+
fmt.Println(s)
18+
}

gopls/internal/golang/fix.go

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,13 @@ import (
1010
"go/token"
1111

1212
"golang.org/x/tools/go/analysis"
13-
"golang.org/x/tools/gopls/internal/analysis/embeddirective"
1413
"golang.org/x/tools/gopls/internal/analysis/fillstruct"
1514
"golang.org/x/tools/gopls/internal/analysis/unusedparams"
1615
"golang.org/x/tools/gopls/internal/cache"
1716
"golang.org/x/tools/gopls/internal/cache/parsego"
1817
"golang.org/x/tools/gopls/internal/file"
1918
"golang.org/x/tools/gopls/internal/protocol"
2019
"golang.org/x/tools/gopls/internal/util/bug"
21-
"golang.org/x/tools/internal/imports"
2220
)
2321

2422
// A fixer is a function that suggests a fix for a diagnostic produced
@@ -94,8 +92,7 @@ func ApplyFix(ctx context.Context, fix string, snapshot *cache.Snapshot, fh file
9492
fixers := map[string]fixer{
9593
// Fixes for analyzer-provided diagnostics.
9694
// These match the Diagnostic.Category.
97-
embeddirective.FixCategory: addEmbedImport,
98-
fillstruct.FixCategory: singleFile(fillstruct.SuggestedFix),
95+
fillstruct.FixCategory: singleFile(fillstruct.SuggestedFix),
9996

10097
// Ad-hoc fixers: these are used when the command is
10198
// constructed directly by logic in server/code_action.
@@ -183,36 +180,3 @@ func suggestedFixToDocumentChange(ctx context.Context, snapshot *cache.Snapshot,
183180
}
184181
return changes, nil
185182
}
186-
187-
// addEmbedImport adds a missing embed "embed" import with blank name.
188-
func addEmbedImport(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, _, _ token.Pos) (*token.FileSet, *analysis.SuggestedFix, error) {
189-
// Like golang.AddImport, but with _ as Name and using our pgf.
190-
protoEdits, err := ComputeImportFixEdits(snapshot.Options().Local, pgf.Src, &imports.ImportFix{
191-
StmtInfo: imports.ImportInfo{
192-
ImportPath: "embed",
193-
Name: "_",
194-
},
195-
FixType: imports.AddImport,
196-
})
197-
if err != nil {
198-
return nil, nil, fmt.Errorf("compute edits: %w", err)
199-
}
200-
201-
var edits []analysis.TextEdit
202-
for _, e := range protoEdits {
203-
start, end, err := pgf.RangePos(e.Range)
204-
if err != nil {
205-
return nil, nil, err // e.g. invalid range
206-
}
207-
edits = append(edits, analysis.TextEdit{
208-
Pos: start,
209-
End: end,
210-
NewText: []byte(e.NewText),
211-
})
212-
}
213-
214-
return pkg.FileSet(), &analysis.SuggestedFix{
215-
Message: "Add embed import",
216-
TextEdits: edits,
217-
}, nil
218-
}

internal/analysisinternal/analysis.go

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -193,18 +193,23 @@ func CheckReadable(pass *analysis.Pass, filename string) error {
193193
return fmt.Errorf("Pass.ReadFile: %s is not among OtherFiles, IgnoredFiles, or names of Files", filename)
194194
}
195195

196-
// AddImport checks whether this file already imports pkgpath and
197-
// that import is in scope at pos. If so, it returns the name under
198-
// which it was imported and a zero edit. Otherwise, it adds a new
199-
// import of pkgpath, using a name derived from the preferred name,
200-
// and returns the chosen name, a prefix to be concatenated with member
201-
// to form a qualified name, and the edit for the new import.
196+
// AddImport checks whether this file already imports pkgpath and that
197+
// the import is in scope at pos. If so, it returns the name under
198+
// which it was imported and no edits. Otherwise, it adds a new import
199+
// of pkgpath, using a name derived from the preferred name, and
200+
// returns the chosen name, a prefix to be concatenated with member to
201+
// form a qualified name, and the edit for the new import.
202202
//
203-
// In the special case that pkgpath is dot-imported then member, the
204-
// identifier for which the import is being added, is consulted. If
205-
// member is not shadowed at pos, AddImport returns (".", "", nil).
206-
// (AddImport accepts the caller's implicit claim that the imported
207-
// package declares member.)
203+
// The member argument indicates the name of the desired symbol within
204+
// the imported package. This is needed in the case when the existing
205+
// import is a dot import, because then it is possible that the
206+
// desired symbol is shadowed by other declarations in the current
207+
// package. If member is not shadowed at pos, AddImport returns (".",
208+
// "", nil). (AddImport accepts the caller's implicit claim that the
209+
// imported package declares member.)
210+
//
211+
// Use a preferredName of "_" to request a blank import;
212+
// member is ignored in this case.
208213
//
209214
// It does not mutate its arguments.
210215
func AddImport(info *types.Info, file *ast.File, preferredName, pkgpath, member string, pos token.Pos) (name, prefix string, newImport []analysis.TextEdit) {
@@ -220,6 +225,10 @@ func AddImport(info *types.Info, file *ast.File, preferredName, pkgpath, member
220225
pkgname := info.PkgNameOf(spec)
221226
if pkgname != nil && pkgname.Imported().Path() == pkgpath {
222227
name = pkgname.Name()
228+
if preferredName == "_" {
229+
// Request for blank import; any existing import will do.
230+
return name, "", nil
231+
}
223232
if name == "." {
224233
// The scope of ident must be the file scope.
225234
if s, _ := scope.LookupParent(member, pos); s == info.Scopes[file] {
@@ -232,8 +241,12 @@ func AddImport(info *types.Info, file *ast.File, preferredName, pkgpath, member
232241
}
233242

234243
// We must add a new import.
244+
235245
// Ensure we have a fresh name.
236-
newName := FreshName(scope, pos, preferredName)
246+
newName := preferredName
247+
if preferredName != "_" {
248+
newName = FreshName(scope, pos, preferredName)
249+
}
237250

238251
// Create a new import declaration either before the first existing
239252
// declaration (which must exist), including its comments; or
@@ -246,6 +259,7 @@ func AddImport(info *types.Info, file *ast.File, preferredName, pkgpath, member
246259
if newName != preferredName || newName != pathpkg.Base(pkgpath) {
247260
newText = fmt.Sprintf("%s %q", newName, pkgpath)
248261
}
262+
249263
decl0 := file.Decls[0]
250264
var before ast.Node = decl0
251265
switch decl0 := decl0.(type) {

0 commit comments

Comments
 (0)