| // 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" |
| "strings" |
| |
| fidlcommon "fidl-lsp/third_party/common" |
| |
| "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 := fidlcommon.ReadLibraryName(sym.Name) |
| if err == nil { |
| if _, isLib := a.libs[libName]; isLib { |
| return a.declarationsOfLibrary(fs, libName), 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, |
| ) |
| } |
| |
| lib, ok := a.libs[name.LibraryName()] |
| if !ok { |
| return nil, fmt.Errorf("unknown library `%s`", name.LibraryName().FullyQualifiedName()) |
| } |
| if lib.ir == nil { |
| if err := a.importLibrary(lib.json); err != nil { |
| return nil, fmt.Errorf( |
| "error importing library `%s`: %s", |
| name.LibraryName().FullyQualifiedName(), |
| err, |
| ) |
| } |
| } |
| |
| // Check that we have a symbolMap for this library, and look up this |
| // symbol's definition location in that symbolMap. |
| if lib.symbols == nil { |
| return nil, fmt.Errorf( |
| "no symbol map for library `%s`", |
| name.LibraryName().FullyQualifiedName(), |
| ) |
| } |
| symInfo, ok := lib.symbols[name.FullyQualifiedName()] |
| if !ok { |
| return nil, fmt.Errorf("could not find definition of symbol `%s`", name.FullyQualifiedName()) |
| } |
| |
| return []state.Location{symInfo.definition}, nil |
| } |
| |
| func (a *Analyzer) declarationsOfLibrary(fs *state.FileSystem, lib fidlcommon.LibraryName) []state.Location { |
| files := []state.FileID{} |
| for file := range a.libs[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 { |
| continue |
| } |
| file = string(bytes) |
| } |
| |
| if libraryMatch, ok := state.ParseLibraryMatch(file); ok { |
| locs = append(locs, state.Location{ |
| FileID: fileID, |
| Range: libraryMatch.Range, |
| }) |
| } |
| } |
| return locs |
| } |
| |
| func (a *Analyzer) symbolToFullyQualifiedName(fs *state.FileSystem, sym state.Symbol) (fidlcommon.Name, error) { |
| // If `sym` is a local name (not fully-qualified), we create a FQN by |
| // attaching its library name. |
| var fqn string |
| if !strings.Contains(sym.Name, ".") { |
| file, err := fs.File(sym.Location.FileID) |
| if err != nil { |
| return fidlcommon.Name{}, fmt.Errorf("could not open file `%s`", sym.Location.FileID) |
| } |
| libName, err := state.LibraryOfFile(file) |
| if err != nil { |
| return fidlcommon.Name{}, fmt.Errorf( |
| "could not find library of symbol `%s` in file `%s`", |
| sym.Name, |
| sym.Location.FileID, |
| ) |
| } |
| fqn = libName.FullyQualifiedName() + "/" + sym.Name |
| } else { |
| // If the symbol contains '.', we assume it is a fully-qualified name. |
| i := strings.LastIndex(sym.Name, ".") |
| fqn = sym.Name[:i] + "/" + sym.Name[i+1:] |
| } |
| |
| // Convert `fqn` to a fidlcommon.Name. |
| name, err := fidlcommon.ReadName(fqn) |
| if err != nil { |
| return fidlcommon.Name{}, fmt.Errorf("could not read fully-qualified name `%s`: %s", fqn, err) |
| } |
| return name, nil |
| } |