blob: 95727a185a6d3db3411d24d961209cfdc9d95d8f [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"
)
// ReferencesToSymbol returns the locations in `fs` of symbols that reference
// `sym`, as long as those locations are available to the Analyzer (i.e. in
// a.libs somewhere).
//
// For a library name, e.g. `fuchsia.foo`, ReferencesToSymbol returns the
// locations of the `using` import declarations for each file that imports that
// library.
func (a *Analyzer) ReferencesToSymbol(fs *state.FileSystem, sym state.Symbol) ([]state.Location, error) {
// If `sym` is a library name, return locations pointing at all the files
// that import that library (specifically, pointing at their `using`
// 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.importsOfLibrary(fs, lib.name), 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,
)
}
// Search for references to `name` in all the libraries that import `name`'s
// library, as well as its own library.
libs := a.librariesThatImport(name.name.LibraryName())
selfLibrary, ok := a.getLibraryWithFile(name.name.LibraryName(), sym.Location.FileID)
if !ok {
return nil, fmt.Errorf("could not find library of symbol `%s`", sym.Name)
}
libs = append(libs, selfLibrary)
refs := []state.Location{}
for _, lib := range libs {
if lib.ir == nil {
if err := a.compileLibrary(lib); err != nil {
return nil, fmt.Errorf(
"error importing library `%s`: %s",
name.name.LibraryName().FullyQualifiedName(),
err,
)
}
}
for _, si := range lib.symbols {
if a.typeReferencesName(si.typeInfo, name.name) {
refs = append(refs, si.definition)
}
}
}
return refs, nil
}
func (a *Analyzer) importsOfLibrary(fs *state.FileSystem, importedLib fidlcommon.LibraryName) []state.Location {
libs := a.librariesThatImport(importedLib)
locs := []state.Location{}
for _, lib := range libs {
locs = append(locs, a.importsOfLibraryInLibrary(fs, importedLib, lib)...)
}
return locs
}
func (a *Analyzer) librariesThatImport(name fidlcommon.LibraryName) []*Library {
libs := []*Library{}
for _, lib := range a.libs {
for dep := range lib.deps {
if dep == name {
libs = append(libs, lib)
break
}
}
}
return libs
}
func (a *Analyzer) importsOfLibraryInLibrary(fs *state.FileSystem, importedLib fidlcommon.LibraryName, library *Library) []state.Location {
files := []state.FileID{}
for file := range library.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, fileID)
} else {
files = append(files, state.FileID(file))
}
}
locs := []state.Location{}
// Find the `using` declaration for `importedLib` 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)
}
imports := state.ParsePlatformImportsMatch(file)
for _, libraryMatch := range imports {
if libraryMatch.Lib == importedLib {
locs = append(locs, state.Location{
FileID: fileID,
Range: libraryMatch.Range,
})
}
}
}
return locs
}
func (a *Analyzer) typeReferencesName(t Type, name fidlcommon.Name) bool {
if t.FromTypeAlias != nil {
if *t.FromTypeAlias == name.FullyQualifiedName() {
return true
}
return false
}
switch t.Kind {
default:
return false
case ArrayType:
return a.typeReferencesName(t.Array.ElementType, name)
case VectorType:
return a.typeReferencesName(t.Vector.ElementType, name)
case StringType:
return false
case HandleType:
return false
case RequestType:
return t.Request.Subtype == name.FullyQualifiedName()
case PrimitiveType:
return false
case IdentifierType:
if !t.Identifier.IsDecl {
return t.Identifier.Identifier == name.FullyQualifiedName()
}
if t.Identifier.Kind != TypeAliasKind {
return false
}
return a.typeReferencesName(t.Identifier.TypeAlias.Type, name)
}
}