@@ -16,7 +16,6 @@ import (
16
16
17
17
"golang.org/x/tools/go/analysis"
18
18
"golang.org/x/tools/go/ast/astutil"
19
- "golang.org/x/tools/gopls/internal/util/safetoken"
20
19
"golang.org/x/tools/gopls/internal/util/typesutil"
21
20
"golang.org/x/tools/internal/analysisinternal"
22
21
)
@@ -86,33 +85,129 @@ func CreateUndeclared(fset *token.FileSet, start, end token.Pos, content []byte,
86
85
if isCallPosition (path ) {
87
86
return newFunctionDeclaration (path , file , pkg , info , fset )
88
87
}
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
+ }
89
125
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 )
92
133
if insertBeforeStmt == nil {
93
134
return nil , nil , fmt .Errorf ("could not locate insertion point" )
94
135
}
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
103
154
}
155
+ newLineIndent := "\n " + indent
156
+ assignment := strings .ReplaceAll (buf .String (), "\n " , newLineIndent ) + newLineIndent
104
157
105
- // Create the new local variable statement.
106
- newStmt := fmt .Sprintf ("%s := %s" , ident .Name , indent )
107
158
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
+ },
113
166
}, nil
114
167
}
115
168
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
+
116
211
func newFunctionDeclaration (path []ast.Node , file * ast.File , pkg * types.Package , info * types.Info , fset * token.FileSet ) (* token.FileSet , * analysis.SuggestedFix , error ) {
117
212
if len (path ) < 3 {
118
213
return nil , nil , fmt .Errorf ("unexpected set of enclosing nodes: %v" , path )
0 commit comments