blob: e9ccc30d0fd4cd735619c727f785734ae1f7b15e [file] [log] [blame]
// 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"
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 {
// 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
}