@@ -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,123 @@ 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 && info .ObjectOf (n ) == nil {
103
+ if firstOccur == nil {
104
+ firstOccur = n
105
+ // Only consider adding colon at the first occurrence.
106
+ if pos , ok := assignTokPosition (info , 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
+ assignStmt := & ast.AssignStmt {
146
+ Lhs : []ast.Expr {ast .NewIdent (ident .Name )},
147
+ Tok : token .DEFINE ,
148
+ Rhs : []ast.Expr {analysisinternal .ZeroValue (file , pkg , typs [0 ])},
149
+ }
150
+ var buf bytes.Buffer
151
+ if err := format .Node (& buf , fset , assignStmt ); err != nil {
152
+ return nil , nil , err
103
153
}
154
+ newLineIndent := "\n " + indent
155
+ assignment := strings .ReplaceAll (buf .String (), "\n " , newLineIndent ) + newLineIndent
104
156
105
- // Create the new local variable statement.
106
- newStmt := fmt .Sprintf ("%s := %s" , ident .Name , indent )
107
157
return fset , & analysis.SuggestedFix {
108
- TextEdits : []analysis.TextEdit {{
109
- Pos : insertBeforeStmt .Pos (),
110
- End : insertBeforeStmt .Pos (),
111
- NewText : []byte (newStmt ),
112
- }},
158
+ TextEdits : []analysis.TextEdit {
159
+ {
160
+ Pos : insertBeforeStmt .Pos (),
161
+ End : insertBeforeStmt .Pos (),
162
+ NewText : []byte (assignment ),
163
+ },
164
+ },
113
165
}, nil
114
166
}
115
167
168
+ // assignTokPosition returns position of token.ASSIGN if ident meets the following conditions:
169
+ // 1) parent node must be an *ast.AssignStmt with Tok set to token.ASSIGN.
170
+ // 2) ident must not be self assignment.
171
+ //
172
+ // For example, we should not add a colon when
173
+ // a = a + 1
174
+ // ^ ^ cursor here
175
+ func assignTokPosition (info * types.Info , ident * ast.Ident , parent ast.Node ) (token.Pos , bool ) {
176
+ var pos token.Pos
177
+ if assign , ok := parent .(* ast.AssignStmt ); ok && assign .Tok == token .ASSIGN {
178
+ for _ , rhs := range assign .Rhs {
179
+ if referencesIdent (info , rhs , ident ) {
180
+ return pos , false
181
+ }
182
+ }
183
+ pos = assign .TokPos
184
+ return pos , true
185
+ }
186
+ return pos , false
187
+ }
188
+
189
+ // referencesIdent checks whether the given undefined ident appears in the given expression.
190
+ func referencesIdent (info * types.Info , expr ast.Expr , ident * ast.Ident ) bool {
191
+ var hasIdent bool
192
+ ast .Inspect (expr , func (n ast.Node ) bool {
193
+ if n == nil {
194
+ return false
195
+ }
196
+ if i , ok := n .(* ast.Ident ); ok && i .Name == ident .Name && info .ObjectOf (i ) == nil {
197
+ hasIdent = true
198
+ return false
199
+ }
200
+ return true
201
+ })
202
+ return hasIdent
203
+ }
204
+
116
205
func newFunctionDeclaration (path []ast.Node , file * ast.File , pkg * types.Package , info * types.Info , fset * token.FileSet ) (* token.FileSet , * analysis.SuggestedFix , error ) {
117
206
if len (path ) < 3 {
118
207
return nil , nil , fmt .Errorf ("unexpected set of enclosing nodes: %v" , path )
0 commit comments