| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package analysis |
| |
| import ( |
| "fmt" |
| "io/ioutil" |
| "log" |
| |
| "fidl-lsp/third_party/fidlgen" |
| |
| "fidl-lsp/state" |
| ) |
| |
| // DefinitionOfSymbol returns the location (or locations) where the given symbol |
| // is defined, as long as that location is available to the Analyzer (i.e. in |
| // a.libs somewhere). |
| // |
| // For a library name, e.g. `fuchsia.foo`, DefinitionOfSymbol returns the |
| // locations of the `library` declarations for each file in that library. |
| // |
| // DefinitionOfSymbol only works for library names and top-level names: enums, |
| // bits, structs, protocols, etc. -- not for member names. For example, it can |
| // find the definition of `Foo` in the following example: |
| // |
| // protocol P { |
| // Method(Foo foo); |
| // }; |
| // |
| // But it can't find the definition of `B.FOO`: |
| // |
| // bits B { |
| // FOO = 0; |
| // } |
| // const ZERO = B.FOO; |
| // |
| func (a *Analyzer) DefinitionOfSymbol(fs *state.FileSystem, sym state.Symbol) ([]state.Location, error) { |
| // If `sym` is a library name, return locations pointing at all the files in |
| // the library (specifically, pointing at their `library` declarations). |
| libName, err := fidlgen.ReadLibraryName(sym.Name) |
| if err == nil { |
| // TODO: We could use getLibraryWithFile here, but it would be more |
| // complicated: if this library name is a declaration, we should pass it |
| // the file the symbol is in; but if it's a library import, we should |
| // pass it a file of that imported library. |
| if lib, isLib := a.getLibrary(libName); isLib { |
| return a.declarationsOfLibrary(fs, lib), nil |
| } |
| } |
| |
| // Otherwise, we assume it is a local or fully-qualified name |
| name, err := a.symbolToFullyQualifiedName(fs, sym) |
| if err != nil { |
| return nil, fmt.Errorf( |
| "could not convert symbol `%s` to fully-qualified name: %s", |
| sym.Name, |
| err, |
| ) |
| } |
| |
| var symInfo *symbolInfo |
| if name.lib != nil { |
| symInfo, err = a.lookupSymbolInfoInLibrary(name.name, name.lib) |
| if err != nil { |
| return nil, fmt.Errorf( |
| "could not find definition of symbol `%s`: %s", |
| name.name.FullyQualifiedName(), |
| err, |
| ) |
| } |
| } else { |
| symInfo, err = a.lookupSymbolInfo(name.name) |
| if err != nil { |
| return nil, fmt.Errorf( |
| "could not find definition of symbol `%s`: %s", |
| name.name.FullyQualifiedName(), |
| err, |
| ) |
| } |
| } |
| |
| return []state.Location{symInfo.definition}, nil |
| } |
| |
| func (a *Analyzer) declarationsOfLibrary(fs *state.FileSystem, lib *Library) []state.Location { |
| files := []state.FileID{} |
| for file := range lib.files { |
| // These files are all absolute paths, but some are to temporary input |
| // files used by the Analyzer. For these, we want to convert the path |
| // to the document URI the editor knows about. |
| if fileID, err := a.inputFileToFileID(file); err != nil { |
| files = append(files, state.FileID(file)) |
| } else { |
| files = append(files, fileID) |
| } |
| } |
| |
| locs := []state.Location{} |
| // Find the `library` declaration in each file |
| for _, fileID := range files { |
| file, err := fs.File(fileID) |
| if err != nil { |
| // If we don't have the file in memory, read it in |
| bytes, err := ioutil.ReadFile(string(fileID)) |
| if err != nil { |
| log.Printf("could not read in file `%s`: %s", fileID, err) |
| continue |
| } |
| file = string(bytes) |
| } |
| |
| if libraryMatch, ok := state.ParseLibraryMatch(file); ok { |
| locs = append(locs, state.Location{ |
| FileID: fileID, |
| Range: libraryMatch.Range, |
| }) |
| } |
| } |
| return locs |
| } |