1
1
package app
2
2
3
3
import (
4
+ "context"
4
5
"errors"
5
6
"fmt"
7
+ "go/ast"
8
+ "go/build"
9
+ "go/doc"
10
+ "go/token"
6
11
"net/http"
7
12
"net/url"
8
13
"path"
9
14
"strings"
10
15
11
- "github.com/sourcegraph/go-lsp/lspext"
16
+ "github.com/sourcegraph/ctxvfs"
17
+ "github.com/sourcegraph/sourcegraph/pkg/vfsutil"
18
+ "golang.org/x/tools/go/buildutil"
19
+
20
+ "github.com/sourcegraph/go-lsp"
21
+
22
+ "github.com/hashicorp/go-multierror"
12
23
"github.com/sourcegraph/sourcegraph/cmd/frontend/backend"
24
+ "github.com/sourcegraph/sourcegraph/cmd/frontend/internal/app/pkg/golangserverutil"
13
25
"github.com/sourcegraph/sourcegraph/pkg/api"
14
26
"github.com/sourcegraph/sourcegraph/pkg/errcode"
15
27
"github.com/sourcegraph/sourcegraph/pkg/gituri"
@@ -37,7 +49,27 @@ func serveGoSymbolURL(w http.ResponseWriter, r *http.Request) error {
37
49
}
38
50
}
39
51
52
+ // def
53
+ // vvvvvvvvvvvv
54
+ // http://sourcegraph.com/go/github.com/gorilla/mux/-/Router/Match
55
+ // ^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^ ^^^^^
56
+ // importPath receiver? symbolname
40
57
importPath := strings .Split (symbolID , "/-/" )[0 ]
58
+ def := strings .Split (symbolID , "/-/" )[1 ]
59
+ var symbolName string
60
+ var receiver * string
61
+ symbolComponents := strings .Split (def , "/" )
62
+ switch len (symbolComponents ) {
63
+ case 1 :
64
+ symbolName = symbolComponents [0 ]
65
+ case 2 :
66
+ // This is a method call.
67
+ receiver = & symbolComponents [0 ]
68
+ symbolName = symbolComponents [1 ]
69
+ default :
70
+ return fmt .Errorf ("invalid def %s (must have 1 or 2 path components)" , def )
71
+ }
72
+
41
73
dir , err := gosrc .ResolveImportPath (httputil .CachingClient , importPath )
42
74
if err != nil {
43
75
return err
@@ -62,30 +94,150 @@ func serveGoSymbolURL(w http.ResponseWriter, r *http.Request) error {
62
94
return err
63
95
}
64
96
65
- symbols , err := backend .Symbols .List (r .Context (), repo .Name , commitID , mode , lspext.WorkspaceSymbolParams {
66
- Symbol : lspext.SymbolDescriptor {"id" : symbolID },
67
- })
97
+ vfs , err := repoVFS (r .Context (), repoName , commitID )
68
98
if err != nil {
69
99
return err
70
100
}
71
101
72
- if len (symbols ) > 0 {
73
- symbol := symbols [0 ]
74
- uri , err := gituri .Parse (string (symbol .Location .URI ))
75
- if err != nil {
76
- return err
102
+ location , err := symbolLocation (r .Context (), vfs , commitID , importPath , path .Join ("/" , dir .RepoPrefix , strings .TrimPrefix (dir .ImportPath , string (dir .ProjectRoot ))), receiver , symbolName )
103
+ if err != nil {
104
+ return err
105
+ }
106
+ if location == nil {
107
+ return & errcode.HTTPErr {
108
+ Status : http .StatusNotFound ,
109
+ Err : errors .New ("symbol not found" ),
77
110
}
78
- filePath := uri .Fragment
79
- dest := & url.URL {
80
- Path : "/" + path .Join (string (repo .Name ), "-/blob" , filePath ),
81
- Fragment : fmt .Sprintf ("L%d:%d$references" , symbol .Location .Range .Start .Line + 1 , symbol .Location .Range .Start .Character + 1 ),
111
+ }
112
+
113
+ uri , err := gituri .Parse (string (location .URI ))
114
+ if err != nil {
115
+ return err
116
+ }
117
+ filePath := uri .Fragment
118
+ dest := & url.URL {
119
+ Path : "/" + path .Join (string (repo .Name ), "-/blob" , filePath ),
120
+ Fragment : fmt .Sprintf ("L%d:%d$references" , location .Range .Start .Line + 1 , location .Range .Start .Character + 1 ),
121
+ }
122
+ http .Redirect (w , r , dest .String (), http .StatusFound )
123
+ return nil
124
+ }
125
+
126
+ func symbolLocation (ctx context.Context , vfs ctxvfs.FileSystem , commitID api.CommitID , importPath string , path string , receiver * string , symbol string ) (* lsp.Location , error ) {
127
+ bctx := buildContextFromVFS (ctx , vfs )
128
+
129
+ fileSet := token .NewFileSet ()
130
+ pkg , err := parseFiles (fileSet , & bctx , importPath , path )
131
+ if err != nil {
132
+ return nil , err
133
+ }
134
+
135
+ pos := (func () * token.Pos {
136
+ docPackage := doc .New (pkg , importPath , doc .AllDecls )
137
+ for _ , docConst := range docPackage .Consts {
138
+ for _ , spec := range docConst .Decl .Specs {
139
+ if valueSpec , ok := spec .(* ast.ValueSpec ); ok {
140
+ for _ , ident := range valueSpec .Names {
141
+ if ident .Name == symbol {
142
+ return & ident .NamePos
143
+ }
144
+ }
145
+ }
146
+ }
147
+ }
148
+ for _ , docType := range docPackage .Types {
149
+ if receiver != nil && docType .Name == * receiver {
150
+ for _ , method := range docType .Methods {
151
+ if method .Name == symbol {
152
+ return & method .Decl .Name .NamePos
153
+ }
154
+ }
155
+ }
156
+ for _ , fun := range docType .Funcs {
157
+ if fun .Name == symbol {
158
+ return & fun .Decl .Name .NamePos
159
+ }
160
+ }
161
+ for _ , spec := range docType .Decl .Specs {
162
+ if typeSpec , ok := spec .(* ast.TypeSpec ); ok && typeSpec .Name .Name == symbol {
163
+ return & typeSpec .Name .NamePos
164
+ }
165
+ }
166
+ }
167
+ for _ , docVar := range docPackage .Vars {
168
+ for _ , spec := range docVar .Decl .Specs {
169
+ if valueSpec , ok := spec .(* ast.ValueSpec ); ok {
170
+ for _ , ident := range valueSpec .Names {
171
+ if ident .Name == symbol {
172
+ return & ident .NamePos
173
+ }
174
+ }
175
+ }
176
+ }
177
+ }
178
+ for _ , docFunc := range docPackage .Funcs {
179
+ if docFunc .Name == symbol {
180
+ return & docFunc .Decl .Name .NamePos
181
+ }
82
182
}
83
- http .Redirect (w , r , dest .String (), http .StatusFound )
84
183
return nil
184
+ })()
185
+
186
+ if pos == nil {
187
+ return nil , nil
188
+ }
189
+
190
+ position := fileSet .Position (* pos )
191
+ location := lsp.Location {
192
+ URI : lsp .DocumentURI ("https://" + string (importPath ) + "?" + string (commitID ) + "#" + position .Filename ),
193
+ Range : lsp.Range {
194
+ Start : lsp.Position {
195
+ Line : position .Line - 1 ,
196
+ Character : position .Column - 1 ,
197
+ },
198
+ End : lsp.Position {
199
+ Line : position .Line - 1 ,
200
+ Character : position .Column - 1 ,
201
+ },
202
+ },
203
+ }
204
+
205
+ return & location , nil
206
+ }
207
+
208
+ func buildContextFromVFS (ctx context.Context , vfs ctxvfs.FileSystem ) build.Context {
209
+ bctx := build .Default
210
+ golangserverutil .PrepareContext (& bctx , ctx , vfs )
211
+ return bctx
212
+ }
213
+
214
+ func repoVFS (ctx context.Context , name api.RepoName , rev api.CommitID ) (ctxvfs.FileSystem , error ) {
215
+ if strings .HasPrefix (string (name ), "github.com/" ) {
216
+ return vfsutil .NewGitHubRepoVFS (string (name ), string (rev ))
217
+ }
218
+
219
+ // Fall back to a full git clone for non-github.com repos.
220
+ return nil , fmt .Errorf ("unable to fetch repo %s (only github.com repos are supported)" , name )
221
+ }
222
+
223
+ func parseFiles (fset * token.FileSet , bctx * build.Context , importPath , srcDir string ) (* ast.Package , error ) {
224
+ bpkg , err := bctx .ImportDir (srcDir , 0 )
225
+ if err != nil {
226
+ return nil , err
85
227
}
86
228
87
- return & errcode.HTTPErr {
88
- Status : http .StatusNotFound ,
89
- Err : errors .New ("symbol not found" ),
229
+ pkg := & ast.Package {
230
+ Files : map [string ]* ast.File {},
90
231
}
232
+ var errs error
233
+ for _ , file := range append (bpkg .GoFiles , bpkg .TestGoFiles ... ) {
234
+ if src , err := buildutil .ParseFile (fset , bctx , nil , buildutil .JoinPath (bctx , srcDir ), file , 0 ); err == nil {
235
+ pkg .Name = src .Name .Name
236
+ pkg .Files [file ] = src
237
+ } else {
238
+ errs = multierror .Append (errs , err )
239
+ }
240
+ }
241
+
242
+ return pkg , errs
91
243
}
0 commit comments