| // 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.getLibrary(name.name.LibraryName()) |
| 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 { |
| log.Printf( |
| "error importing library `%s`: %s", |
| name.name.LibraryName().FullyQualifiedName(), |
| err, |
| ) |
| continue |
| } |
| } |
| |
| 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) |
| } |
| } |