Skip to content

Commit 593ee32

Browse files
committed
add missing column
1 parent 9387a39 commit 593ee32

File tree

4 files changed

+210
-62
lines changed

4 files changed

+210
-62
lines changed

gopls/doc/features/diagnostics.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,12 +213,13 @@ func main() {
213213
}
214214
```
215215

216-
The quick fix would be:
216+
The quick fix would insert a declration with a default
217+
value inferring its type from the context:
217218

218219
```go
219220
func main() {
220221
x := 42
221-
y :=
222+
y := 0
222223
min(x, y)
223224
}
224225
```

gopls/internal/golang/undeclared.go

Lines changed: 113 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import (
1616

1717
"golang.org/x/tools/go/analysis"
1818
"golang.org/x/tools/go/ast/astutil"
19-
"golang.org/x/tools/gopls/internal/util/safetoken"
2019
"golang.org/x/tools/gopls/internal/util/typesutil"
2120
"golang.org/x/tools/internal/analysisinternal"
2221
)
@@ -86,33 +85,129 @@ func CreateUndeclared(fset *token.FileSet, start, end token.Pos, content []byte,
8685
if isCallPosition(path) {
8786
return newFunctionDeclaration(path, file, pkg, info, fset)
8887
}
88+
var (
89+
firstOccur *ast.Ident // We should insert the new declaration before the first occurrence of the undefined ident.
90+
assignTokPos token.Pos
91+
funcDecl = path[len(path)-2].(*ast.FuncDecl) // This is already ensured by [undeclaredFixTitle].
92+
parent = ast.Node(funcDecl)
93+
canAddColon = false
94+
)
95+
// Search from enclosing FuncDecl to path[0], since we can not use := syntax outside function.
96+
// Adds the missing colon after the first undefined symbol
97+
// when it sits in lhs of an AssignStmt.
98+
ast.Inspect(funcDecl, func(n ast.Node) bool {
99+
if n == nil || firstOccur != nil {
100+
return false
101+
}
102+
if n, ok := n.(*ast.Ident); ok && n.Name == ident.Name {
103+
if firstOccur == nil {
104+
firstOccur = n
105+
// Only consider adding colon at the first occurrence.
106+
if pos, ok := assignTokPosition(n, parent); ok {
107+
assignTokPos = pos
108+
canAddColon = true
109+
return false
110+
}
111+
}
112+
}
113+
parent = n
114+
return true
115+
})
116+
if canAddColon {
117+
return fset, &analysis.SuggestedFix{
118+
TextEdits: []analysis.TextEdit{{
119+
Pos: assignTokPos,
120+
End: assignTokPos,
121+
NewText: []byte(":"),
122+
}},
123+
}, nil
124+
}
89125

90-
// Get the place to insert the new statement.
91-
insertBeforeStmt := analysisinternal.StmtToInsertVarBefore(path)
126+
// FirstOccur should never be nil, at least one ident at cursor position should be found,
127+
// but be defensive.
128+
if firstOccur == nil {
129+
return nil, nil, fmt.Errorf("no identifier found")
130+
}
131+
p, _ := astutil.PathEnclosingInterval(file, firstOccur.Pos(), firstOccur.Pos())
132+
insertBeforeStmt := analysisinternal.StmtToInsertVarBefore(p)
92133
if insertBeforeStmt == nil {
93134
return nil, nil, fmt.Errorf("could not locate insertion point")
94135
}
95-
96-
insertBefore := safetoken.StartPosition(fset, insertBeforeStmt.Pos()).Offset
97-
98-
// Get the indent to add on the line after the new statement.
99-
// Since this will have a parse error, we can not use format.Source().
100-
contentBeforeStmt, indent := content[:insertBefore], "\n"
101-
if nl := bytes.LastIndex(contentBeforeStmt, []byte("\n")); nl != -1 {
102-
indent = string(contentBeforeStmt[nl:])
136+
indent, err := calculateIndentation(content, fset.File(file.FileStart), insertBeforeStmt)
137+
if err != nil {
138+
return nil, nil, err
139+
}
140+
typs := typesutil.TypesFromContext(info, path, start)
141+
if typs == nil {
142+
// Defualt to 0.
143+
typs = []types.Type{types.Typ[types.Int]}
144+
}
145+
zv := analysisinternal.ZeroValue(file, pkg, typs[0])
146+
assignStmt := &ast.AssignStmt{
147+
Lhs: []ast.Expr{ast.NewIdent(ident.Name)},
148+
Tok: token.DEFINE,
149+
Rhs: []ast.Expr{zv},
150+
}
151+
var buf bytes.Buffer
152+
if err := format.Node(&buf, fset, assignStmt); err != nil {
153+
return nil, nil, err
103154
}
155+
newLineIndent := "\n" + indent
156+
assignment := strings.ReplaceAll(buf.String(), "\n", newLineIndent) + newLineIndent
104157

105-
// Create the new local variable statement.
106-
newStmt := fmt.Sprintf("%s := %s", ident.Name, indent)
107158
return fset, &analysis.SuggestedFix{
108-
TextEdits: []analysis.TextEdit{{
109-
Pos: insertBeforeStmt.Pos(),
110-
End: insertBeforeStmt.Pos(),
111-
NewText: []byte(newStmt),
112-
}},
159+
TextEdits: []analysis.TextEdit{
160+
{
161+
Pos: insertBeforeStmt.Pos(),
162+
End: insertBeforeStmt.Pos(),
163+
NewText: []byte(assignment),
164+
},
165+
},
113166
}, nil
114167
}
115168

169+
// assignTokPosition returns position of token.ASSIGN if ident meets the following conditions:
170+
// 1) parent node must be an *ast.AssignStmt with Tok set to token.ASSIGN.
171+
// 2) ident must not be self assignment.
172+
//
173+
// For example, we should not add a colon when
174+
// "a = a + 1"
175+
// ^ ^ cursor here
176+
func assignTokPosition(ident *ast.Ident, parent ast.Node) (token.Pos, bool) {
177+
var pos token.Pos
178+
if assign, ok := parent.(*ast.AssignStmt); ok && assign.Tok == token.ASSIGN {
179+
outer:
180+
for _, lhs := range assign.Lhs {
181+
if lhs == ident {
182+
for _, rhs := range assign.Rhs {
183+
if referencesIdent(rhs, ident) {
184+
break outer
185+
}
186+
}
187+
pos = assign.TokPos
188+
return pos, true
189+
}
190+
}
191+
}
192+
return pos, false
193+
}
194+
195+
// referencesIdent checks whether the given ident appears in the given expression.
196+
func referencesIdent(expr ast.Expr, ident *ast.Ident) bool {
197+
var hasIdent bool
198+
ast.Inspect(expr, func(n ast.Node) bool {
199+
if n == nil {
200+
return false
201+
}
202+
if i, ok := n.(*ast.Ident); ok && i.Name == ident.Name {
203+
hasIdent = true
204+
return false
205+
}
206+
return true
207+
})
208+
return hasIdent
209+
}
210+
116211
func newFunctionDeclaration(path []ast.Node, file *ast.File, pkg *types.Package, info *types.Info, fset *token.FileSet) (*token.FileSet, *analysis.SuggestedFix, error) {
117212
if len(path) < 3 {
118213
return nil, nil, fmt.Errorf("unexpected set of enclosing nodes: %v", path)

gopls/internal/test/marker/testdata/quickfix/undeclared.txt

Lines changed: 0 additions & 42 deletions
This file was deleted.
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
Tests of suggested fixes for "undeclared name" diagnostics,
2+
which are of ("compiler", "error") type.
3+
4+
-- flags --
5+
-ignore_extra_diags
6+
7+
-- go.mod --
8+
module example.com
9+
go 1.12
10+
11+
-- a.go --
12+
package undeclared_var
13+
14+
func a() {
15+
z, _ := 1+y, 11 //@quickfix("y", re"(undeclared name|undefined): y", a)
16+
_ = z
17+
}
18+
19+
-- @a/a.go --
20+
@@ -4 +4 @@
21+
+ y := 0
22+
-- b.go --
23+
package undeclared_var
24+
25+
func b() {
26+
if 100 < 90 {
27+
} else if 100 > n+2 { //@quickfix("n", re"(undeclared name|undefined): n", b)
28+
}
29+
}
30+
31+
-- @b/b.go --
32+
@@ -4 +4 @@
33+
+ n := 0
34+
-- c.go --
35+
package undeclared_var
36+
37+
func c() {
38+
for i < 200 { //@quickfix("i", re"(undeclared name|undefined): i", c)
39+
}
40+
r() //@diag("r", re"(undeclared name|undefined): r")
41+
}
42+
43+
-- @c/c.go --
44+
@@ -4 +4 @@
45+
+ i := 0
46+
-- add_colon.go --
47+
package undeclared_var
48+
49+
func addColon() {
50+
ac = 1 //@quickfix("ac", re"(undeclared name|undefined): ac", add_colon)
51+
}
52+
53+
-- @add_colon/add_colon.go --
54+
@@ -4 +4 @@
55+
- ac = 1 //@quickfix("ac", re"(undeclared name|undefined): ac", add_colon)
56+
+ ac := 1 //@quickfix("ac", re"(undeclared name|undefined): ac", add_colon)
57+
-- add_colon_first.go --
58+
package undeclared_var
59+
60+
func addColonAtFirstStmt() {
61+
ac = 1
62+
ac = 2
63+
ac = 3
64+
b := ac //@quickfix("ac", re"(undeclared name|undefined): ac", add_colon_first)
65+
}
66+
67+
-- @add_colon_first/add_colon_first.go --
68+
@@ -4 +4 @@
69+
- ac = 1
70+
+ ac := 1
71+
-- self_assign.go --
72+
package undeclared_var
73+
74+
func selfAssign() {
75+
ac = ac + 1
76+
ac = ac + 2 //@quickfix("ac", re"(undeclared name|undefined): ac", lhs)
77+
ac = ac + 3 //@quickfix("ac + 3", re"(undeclared name|undefined): ac", rhs)
78+
}
79+
80+
-- @lhs/self_assign.go --
81+
@@ -4 +4 @@
82+
+ ac := nil
83+
-- @rhs/self_assign.go --
84+
@@ -4 +4 @@
85+
+ ac := 0
86+
-- correct_type.go --
87+
package undeclared_var
88+
import "fmt"
89+
func selfAssign() {
90+
fmt.Printf(ac) //@quickfix("ac", re"(undeclared name|undefined): ac", string)
91+
}
92+
-- @string/correct_type.go --
93+
@@ -4 +4 @@
94+
+ ac := ""

0 commit comments

Comments
 (0)