[lsp] textDocument.hover
This adds the "textDocument/hover" capability to the language server.
The functionality is split into three main steps:
* in compile.go, when a library is imported, Analyzer.genSymbolMap
builds a map of fully-qualified symbol names to their type
information (types defined in symbol.go).
* when the server receives a `hover` request, it calls
Analyzer.TypeOfSymbol (in type.go), which looks up the symbol's type
information in the symbol map for its library.
* the LangHandler converts that type information to a human-readable
tooltip in symbolTypeToMarkedStrings (hover.go).
Test: go test ./...
Change-Id: I8619d125383688f4438ccce06b83414480d4874f
Reviewed-on: https://fuchsia-review.googlesource.com/c/fidl-misc/+/395996
Reviewed-by: Benjamin Prosnitz <bprosnitz@google.com>
Reviewed-by: Pascal Perez <pascallouis@google.com>
diff --git a/fidl-lsp/analysis/compile.go b/fidl-lsp/analysis/compile.go
index 4084400..885c9a5 100644
--- a/fidl-lsp/analysis/compile.go
+++ b/fidl-lsp/analysis/compile.go
@@ -10,6 +10,7 @@
"fmt"
"io/ioutil"
"os/exec"
+ "strconv"
"strings"
fidlcommon "fidl-lsp/third_party/common"
@@ -121,168 +122,200 @@
return nil
}
-type symbolKind string
-
-const (
- bitsKind symbolKind = "bits"
- constKind = "const"
- enumKind = "enum"
- protocolKind = "protocol"
- serviceKind = "service"
- structKind = "struct"
- tableKind = "table"
- unionKind = "union"
- typeAliasKind = "typeAlias"
-)
-
-type symbolInfo struct {
- lib string
- name string
- definition state.Location
- attrs []attribute
- kind symbolKind
- isMember bool
- isMethod bool
- typeInfo interface{}
- maybeFromTypeAlias typeCtor
-}
-
type symbolMap map[string]*symbolInfo
func (a *Analyzer) genSymbolMap(l FidlLibrary) (symbolMap, error) {
- // TODO: skip SomeLongAnonymousPrefix* structs? (are we double counting method request/response params?)
- // TODO: how to handle const literals?
-
sm := make(symbolMap)
for _, d := range l.BitsDecls {
sm[d.Name] = &symbolInfo{
- lib: l.Name,
- name: d.Name,
- definition: a.fidlLocToStateLoc(d.Loc),
- kind: bitsKind,
- typeInfo: d.Type,
- maybeFromTypeAlias: d.FromTypeAlias,
- attrs: d.Attrs,
+ name: d.Name,
+ definition: a.fidlLocToStateLoc(d.Loc),
+ typeInfo: Type{
+ Kind: IdentifierType,
+ Identifier: &IdentifierTypeInfo{
+ IsDecl: true,
+ Name: d.Name,
+ Kind: BitsKind,
+ Bits: &BitsTypeInfo{
+ Type: d.Type.Type(),
+ },
+ },
+ Attrs: d.Attrs,
+ },
}
- a.addMembersToSymbolMap(sm, l.Name, d.Name, bitsKind, d.Members)
+ a.addMembersToSymbolMap(sm, d.Name, d.Members)
}
for _, d := range l.ConstDecls {
sm[d.Name] = &symbolInfo{
- lib: l.Name,
- name: d.Name,
- definition: a.fidlLocToStateLoc(d.Loc),
- kind: constKind,
- typeInfo: d.Type,
- maybeFromTypeAlias: d.FromTypeAlias,
- attrs: d.Attrs,
+ name: d.Name,
+ definition: a.fidlLocToStateLoc(d.Loc),
+ typeInfo: Type{
+ Kind: IdentifierType,
+ Identifier: &IdentifierTypeInfo{
+ IsDecl: true,
+ Name: d.Name,
+ Kind: ConstKind,
+ Const: &ConstTypeInfo{
+ Type: d.Type.Type(),
+ Value: d.Value.Value,
+ },
+ },
+ Attrs: d.Attrs,
+ },
}
}
for _, d := range l.EnumDecls {
sm[d.Name] = &symbolInfo{
- lib: l.Name,
- name: d.Name,
- definition: a.fidlLocToStateLoc(d.Loc),
- kind: enumKind,
- typeInfo: d.Type,
- maybeFromTypeAlias: d.FromTypeAlias,
- attrs: d.Attrs,
+ name: d.Name,
+ definition: a.fidlLocToStateLoc(d.Loc),
+ typeInfo: Type{
+ Kind: IdentifierType,
+ Identifier: &IdentifierTypeInfo{
+ IsDecl: true,
+ Name: d.Name,
+ Kind: EnumKind,
+ Enum: &EnumTypeInfo{
+ Type: PrimitiveSubtype(d.Type),
+ },
+ },
+ Attrs: d.Attrs,
+ },
}
- a.addMembersToSymbolMap(sm, l.Name, d.Name, enumKind, d.Members)
+ a.addMembersToSymbolMap(sm, d.Name, d.Members)
}
for _, d := range l.ProtocolDecls {
sm[d.Name] = &symbolInfo{
- lib: l.Name,
name: d.Name,
definition: a.fidlLocToStateLoc(d.Loc),
- kind: protocolKind,
- attrs: d.Attrs,
+ typeInfo: Type{
+ Kind: IdentifierType,
+ Identifier: &IdentifierTypeInfo{
+ IsDecl: true,
+ Name: d.Name,
+ Kind: ProtocolKind,
+ },
+ Attrs: d.Attrs,
+ },
}
for _, m := range d.Methods {
methodName := fmt.Sprintf("%s.%s", d.Name, m.Name)
sm[methodName] = &symbolInfo{
- lib: l.Name,
- name: methodName,
+ name: m.Name,
definition: a.fidlLocToStateLoc(m.Loc),
- kind: protocolKind,
- isMethod: true,
- attrs: m.Attrs,
+ typeInfo: Type{
+ IsMethod: true,
+ Attrs: m.Attrs,
+ },
}
if len(m.MaybeRequest) > 0 {
- a.addMembersToSymbolMap(sm, l.Name, methodName, protocolKind, m.MaybeRequest)
+ a.addMembersToSymbolMap(sm, methodName, m.MaybeRequest)
}
if len(m.MaybeResponse) > 0 {
- a.addMembersToSymbolMap(sm, l.Name, methodName, protocolKind, m.MaybeResponse)
+ a.addMembersToSymbolMap(sm, methodName, m.MaybeResponse)
}
}
}
for _, d := range l.ServiceDecls {
sm[d.Name] = &symbolInfo{
- lib: l.Name,
name: d.Name,
definition: a.fidlLocToStateLoc(d.Loc),
- kind: serviceKind,
- attrs: d.Attrs,
+ typeInfo: Type{
+ Kind: IdentifierType,
+ Identifier: &IdentifierTypeInfo{
+ IsDecl: true,
+ Name: d.Name,
+ Kind: ServiceKind,
+ },
+ Attrs: d.Attrs,
+ },
}
- a.addMembersToSymbolMap(sm, l.Name, d.Name, serviceKind, d.Members)
+ a.addMembersToSymbolMap(sm, d.Name, d.Members)
}
for _, d := range l.StructDecls {
+ // Skip anonymous structs, since we add them to the symbol map as method
+ // parameter structs
+ if d.Anonymous {
+ continue
+ }
sm[d.Name] = &symbolInfo{
- lib: l.Name,
name: d.Name,
definition: a.fidlLocToStateLoc(d.Loc),
- kind: structKind,
- attrs: d.Attrs,
+ typeInfo: Type{
+ Kind: IdentifierType,
+ Identifier: &IdentifierTypeInfo{
+ IsDecl: true,
+ Name: d.Name,
+ Kind: StructKind,
+ },
+ Attrs: d.Attrs,
+ },
}
- a.addMembersToSymbolMap(sm, l.Name, d.Name, structKind, d.Members)
+ a.addMembersToSymbolMap(sm, d.Name, d.Members)
}
for _, d := range l.TableDecls {
sm[d.Name] = &symbolInfo{
- lib: l.Name,
name: d.Name,
definition: a.fidlLocToStateLoc(d.Loc),
- kind: tableKind,
- attrs: d.Attrs,
+ typeInfo: Type{
+ Kind: IdentifierType,
+ Identifier: &IdentifierTypeInfo{
+ IsDecl: true,
+ Name: d.Name,
+ Kind: TableKind,
+ },
+ Attrs: d.Attrs,
+ },
}
- a.addMembersToSymbolMap(sm, l.Name, d.Name, tableKind, d.Members)
+ a.addMembersToSymbolMap(sm, d.Name, d.Members)
}
for _, d := range l.UnionDecls {
sm[d.Name] = &symbolInfo{
- lib: l.Name,
name: d.Name,
definition: a.fidlLocToStateLoc(d.Loc),
- kind: unionKind,
- attrs: d.Attrs,
+ typeInfo: Type{
+ Kind: IdentifierType,
+ Identifier: &IdentifierTypeInfo{
+ IsDecl: true,
+ Name: d.Name,
+ Kind: UnionKind,
+ },
+ Attrs: d.Attrs,
+ },
}
- a.addMembersToSymbolMap(sm, l.Name, d.Name, unionKind, d.Members)
+ a.addMembersToSymbolMap(sm, d.Name, d.Members)
}
for _, d := range l.TypeAliasDecls {
sm[d.Name] = &symbolInfo{
- lib: l.Name,
name: d.Name,
definition: a.fidlLocToStateLoc(d.Loc),
- kind: typeAliasKind,
- typeInfo: d.TypeCtor,
- attrs: d.Attrs,
+ typeInfo: Type{
+ Kind: IdentifierType,
+ Identifier: &IdentifierTypeInfo{
+ IsDecl: true,
+ Name: d.Name,
+ Kind: TypeAliasKind,
+ TypeAlias: &TypeAliasTypeInfo{
+ Type: d.TypeCtor.Type(),
+ },
+ },
+ Attrs: d.Attrs,
+ },
}
}
return sm, nil
}
-func (a *Analyzer) addMembersToSymbolMap(sm symbolMap, libName string, declName string, kind symbolKind, members []member) {
+func (a *Analyzer) addMembersToSymbolMap(sm symbolMap, declName string, members []member) {
for _, m := range members {
memberName := fmt.Sprintf("%s.%s", declName, m.Name)
sm[memberName] = &symbolInfo{
- lib: libName,
- name: memberName,
- definition: a.fidlLocToStateLoc(m.Loc),
- kind: kind,
- isMember: true,
- typeInfo: m.Type,
- maybeFromTypeAlias: m.FromTypeAlias,
- attrs: m.Attrs,
+ name: m.Name,
+ definition: a.fidlLocToStateLoc(m.Loc),
+ typeInfo: m.Type.Type(),
}
+ sm[memberName].typeInfo.Attrs = m.Attrs
}
}
@@ -297,7 +330,7 @@
//
// The `Filename` in a decl's location is recorded relative to where
// fidlc was invoked. All the locations in CompiledLibraries are
- // prepended with "../../" which can be replaced by $FUCHSIA_DIR/.
+ // prepended with "../../" which can be replaced by the `BuildRootDir`.
fileID = state.FileID(strings.Replace(loc.Filename, "../..", a.cfg.BuildRootDir, 1))
}
return state.Location{
@@ -308,3 +341,138 @@
},
}
}
+
+func (d declType) Type() Type {
+ t := Type{Kind: d.Kind}
+
+ switch d.Kind {
+ default:
+ // Sometimes this will be called with a zeroed declType, e.g. for bits
+ // and enum members, which don't have types.
+ return t
+ case ArrayType:
+ t.Array = &ArrayTypeInfo{
+ ElementType: d.ElementType.Type(),
+ ElementCount: d.ElementCount,
+ }
+ case VectorType:
+ t.Vector = &VectorTypeInfo{
+ ElementType: d.ElementType.Type(),
+ ElementCount: d.MaybeElementCount,
+ Nullable: d.Nullable,
+ }
+ case StringType:
+ t.String = &StringTypeInfo{
+ ElementCount: d.MaybeElementCount,
+ Nullable: d.Nullable,
+ }
+ case HandleType:
+ t.Handle = &HandleTypeInfo{
+ Subtype: HandleSubtype(d.Subtype),
+ Rights: d.Rights,
+ Nullable: d.Nullable,
+ }
+ case RequestType:
+ t.Request = &RequestTypeInfo{
+ Subtype: d.Subtype,
+ Nullable: d.Nullable,
+ }
+ case PrimitiveType:
+ t.Primitive = &PrimitiveTypeInfo{
+ Subtype: PrimitiveSubtype(d.Subtype),
+ }
+ case IdentifierType:
+ t.Identifier = &IdentifierTypeInfo{
+ Identifier: d.Identifier,
+ Nullable: d.Nullable,
+ }
+ }
+
+ return t
+}
+
+func (t typeCtor) Type() Type {
+ switch t.Name {
+ case string(ArrayType):
+ ty := Type{
+ Kind: ArrayType,
+ Array: &ArrayTypeInfo{
+ ElementType: t.Args[0].Type(),
+ },
+ }
+ if count, err := strconv.ParseUint(t.Size.Value, 10, 32); err == nil {
+ ty.Array.ElementCount = uint(count)
+ }
+ return ty
+
+ case string(VectorType):
+ ty := Type{
+ Kind: VectorType,
+ Vector: &VectorTypeInfo{
+ ElementType: t.Args[0].Type(),
+ Nullable: t.Nullable,
+ },
+ }
+ if count, err := strconv.ParseUint(t.Size.Value, 10, 32); err == nil {
+ countUint := uint(count)
+ ty.Vector.ElementCount = &countUint
+ }
+ return ty
+
+ case string(StringType):
+ ty := Type{
+ Kind: StringType,
+ String: &StringTypeInfo{
+ Nullable: t.Nullable,
+ },
+ }
+ if count, err := strconv.ParseUint(t.Size.Value, 10, 32); err == nil {
+ countUint := uint(count)
+ ty.String.ElementCount = &countUint
+ }
+ return ty
+
+ case string(HandleType):
+ ty := Type{
+ Kind: HandleType,
+ Handle: &HandleTypeInfo{
+ Nullable: t.Nullable,
+ },
+ }
+ if t.HandleSubtype != nil {
+ ty.Handle.Subtype = *t.HandleSubtype
+ }
+ return ty
+
+ case string(RequestType):
+ return Type{
+ Kind: RequestType,
+ Request: &RequestTypeInfo{
+ Subtype: t.Args[0].Name,
+ Nullable: t.Nullable,
+ },
+ }
+
+ case string(Bool), Int8, Int16, Int32, Int64, Uint8, Uint16, Uint32, Uint64, Float32, Float64:
+ return Type{
+ Kind: PrimitiveType,
+ Primitive: &PrimitiveTypeInfo{
+ Subtype: PrimitiveSubtype(t.Name),
+ },
+ }
+
+ default:
+ // We assume it is the name of an identifier type.
+ return Type{
+ Kind: IdentifierType,
+ Identifier: &IdentifierTypeInfo{
+ Identifier: t.Name,
+ Nullable: t.Nullable,
+ },
+ }
+ // Nullable
+ // Args
+ // Size
+ // HandleSubtype
+ }
+}
diff --git a/fidl-lsp/analysis/definition.go b/fidl-lsp/analysis/definition.go
index 3063f59..1b386b8 100644
--- a/fidl-lsp/analysis/definition.go
+++ b/fidl-lsp/analysis/definition.go
@@ -7,7 +7,6 @@
import (
"fmt"
"io/ioutil"
- "strings"
fidlcommon "fidl-lsp/third_party/common"
@@ -55,31 +54,9 @@
)
}
- 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())
+ symInfo, err := a.lookupSymbolInfo(name)
+ if err != nil {
+ return nil, fmt.Errorf("could not find definition of symbol `%s`: %s", name.FullyQualifiedName(), err)
}
return []state.Location{symInfo.definition}, nil
@@ -120,35 +97,3 @@
}
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
-}
diff --git a/fidl-lsp/analysis/library.go b/fidl-lsp/analysis/library.go
index fbde7ef..4fc9480 100644
--- a/fidl-lsp/analysis/library.go
+++ b/fidl-lsp/analysis/library.go
@@ -18,7 +18,7 @@
Line, Column, Length int
}
-type attribute struct {
+type Attribute struct {
Name string
Value string
}
@@ -35,22 +35,34 @@
IdentifierType = "identifier"
)
-type elementType struct {
- Kind string
- Identifier string `json:"identifier,omitempty"`
- Subtype string `json:"subtype,omitempty"`
+type typeCtor struct {
+ Name string
+ Args []typeCtor
+ Nullable bool
+ Size *constValue `json:"maybe_size,omitempty"`
+ HandleSubtype *HandleSubtype `json:"maybe_handle_subtype,omitempty"`
}
-type typeCtor struct {
- Name string
- Args []typeCtor
+type constValue struct {
+ Value string
}
type declType struct {
- Kind TypeKind
- ElementType elementType `json:"element_type,omitempty"`
- Identifier string `json:"identifier,omitempty"`
- Subtype string `json:"subtype,omitempty"`
+ Kind TypeKind
+
+ // Array
+ ElementType *declType `json:"element_type,omitempty"`
+ ElementCount uint `json:"element_count,omitempty"`
+
+ // Vector
+ MaybeElementCount *uint `json:"maybe_element_count,omitempty"`
+ Nullable bool `json:"nullable,omitempty"`
+
+ // Handle
+ Subtype string `json:"subtype,omitempty"`
+ Rights uint `json:"rights,omitempty"`
+
+ Identifier string `json:"identifier,omitempty"`
}
type member struct {
@@ -58,7 +70,7 @@
Loc location `json:"location"`
Type declType `json:"type,omitempty"`
FromTypeAlias typeCtor `json:"experimental_maybe_from_type_alias,omitempty"`
- Attrs []attribute `json:"maybe_attributes,omitempty"`
+ Attrs []Attribute `json:"maybe_attributes,omitempty"`
}
type method struct {
@@ -66,7 +78,7 @@
Loc location `json:"location"`
MaybeRequest []member
MaybeResponse []member
- Attrs []attribute `json:"maybe_attributes,omitempty"`
+ Attrs []Attribute `json:"maybe_attributes,omitempty"`
}
type bitsDecl struct {
@@ -74,7 +86,7 @@
Loc location `json:"location"`
Type declType
FromTypeAlias typeCtor `json:"experimental_maybe_from_type_alias,omitempty"`
- Attrs []attribute `json:"maybe_attributes,omitempty"`
+ Attrs []Attribute `json:"maybe_attributes,omitempty"`
Members []member
}
@@ -85,7 +97,7 @@
// a string
Type string
FromTypeAlias typeCtor `json:"experimental_maybe_from_type_alias,omitempty"`
- Attrs []attribute `json:"maybe_attributes,omitempty"`
+ Attrs []Attribute `json:"maybe_attributes,omitempty"`
Members []member
}
@@ -93,50 +105,52 @@
Name string
Loc location `json:"location"`
Type declType
+ Value constValue `json:"value"`
FromTypeAlias typeCtor `json:"experimental_maybe_from_type_alias,omitempty"`
- Attrs []attribute `json:"maybe_attributes,omitempty"`
+ Attrs []Attribute `json:"maybe_attributes,omitempty"`
}
type protocolDecl struct {
Name string
Loc location `json:"location"`
Methods []method
- Attrs []attribute `json:"maybe_attributes,omitempty"`
+ Attrs []Attribute `json:"maybe_attributes,omitempty"`
}
type serviceDecl struct {
Name string
Loc location `json:"location"`
Members []member
- Attrs []attribute `json:"maybe_attributes,omitempty"`
+ Attrs []Attribute `json:"maybe_attributes,omitempty"`
}
type structDecl struct {
- Name string
- Loc location `json:"location"`
- Members []member
- Attrs []attribute `json:"maybe_attributes,omitempty"`
+ Name string
+ Loc location `json:"location"`
+ Members []member
+ Attrs []Attribute `json:"maybe_attributes,omitempty"`
+ Anonymous bool
}
type tableDecl struct {
Name string
Loc location `json:"location"`
Members []member
- Attrs []attribute `json:"maybe_attributes,omitempty"`
+ Attrs []Attribute `json:"maybe_attributes,omitempty"`
}
type unionDecl struct {
Name string
Loc location `json:"location"`
Members []member
- Attrs []attribute `json:"maybe_attributes,omitempty"`
+ Attrs []Attribute `json:"maybe_attributes,omitempty"`
}
type typeAliasDecl struct {
Name string
Loc location `json:"location"`
TypeCtor typeCtor `json:"partial_type_ctor"`
- Attrs []attribute `json:"maybe_attributes,omitempty"`
+ Attrs []Attribute `json:"maybe_attributes,omitempty"`
}
// TODO: rename to FidlJSONIR, or JSONLibrary?
diff --git a/fidl-lsp/analysis/symbol.go b/fidl-lsp/analysis/symbol.go
new file mode 100644
index 0000000..eefcf29
--- /dev/null
+++ b/fidl-lsp/analysis/symbol.go
@@ -0,0 +1,248 @@
+// 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"
+ "strings"
+
+ fidlcommon "fidl-lsp/third_party/common"
+
+ "fidl-lsp/state"
+)
+
+type symbolInfo struct {
+ name string
+ definition state.Location
+ typeInfo Type
+}
+
+type Type struct {
+ // If IsLib is true, other fields are empty.
+ // TODO: is there a better way to represent this?
+ IsLib bool
+ // If IsMethod is true, only Attrs is set.
+ IsMethod bool
+
+ Attrs []Attribute
+
+ Kind TypeKind
+
+ Array *ArrayTypeInfo
+ Vector *VectorTypeInfo
+ String *StringTypeInfo
+ Handle *HandleTypeInfo
+ Request *RequestTypeInfo
+ Primitive *PrimitiveTypeInfo
+ Identifier *IdentifierTypeInfo
+}
+
+type ArrayTypeInfo struct {
+ ElementType Type
+ ElementCount uint
+}
+
+type VectorTypeInfo struct {
+ ElementType Type
+ ElementCount *uint
+ Nullable bool
+}
+
+type StringTypeInfo struct {
+ ElementCount *uint
+ Nullable bool
+}
+
+type HandleTypeInfo struct {
+ Subtype HandleSubtype
+ Rights uint
+ Nullable bool
+}
+
+type HandleSubtype string
+
+const (
+ Handle HandleSubtype = "handle"
+ Bti = "bti"
+ Channel = "channel"
+ Clock = "clock"
+ DebugLog = "debuglog"
+ Event = "event"
+ Eventpair = "eventpair"
+ Exception = "exception"
+ Fifo = "fifo"
+ Guest = "guest"
+ Interrupt = "interrupt"
+ Iommu = "iommu"
+ Job = "job"
+ Pager = "pager"
+ PciDevice = "pcidevice"
+ Pmt = "pmt"
+ Port = "port"
+ Process = "process"
+ Profile = "profile"
+ Resource = "resource"
+ Socket = "socket"
+ Stream = "stream"
+ SuspendToken = "suspendtoken"
+ Thread = "thread"
+ Time = "timer"
+ Vcpu = "vcpu"
+ Vmar = "vmar"
+ Vmo = "vmo"
+)
+
+type RequestTypeInfo struct {
+ Subtype string
+ Nullable bool
+}
+
+type PrimitiveTypeInfo struct {
+ Subtype PrimitiveSubtype
+}
+
+type PrimitiveSubtype string
+
+const (
+ Bool PrimitiveSubtype = "bool"
+ Int8 = "int8"
+ Int16 = "int16"
+ Int32 = "int32"
+ Int64 = "int64"
+ Uint8 = "uint8"
+ Uint16 = "uint16"
+ Uint32 = "uint32"
+ Uint64 = "uint64"
+ Float32 = "float32"
+ Float64 = "float64"
+)
+
+// IdentifierTypeInfo is not a perfect analog for `identifier-type` from the
+// JSON IR, because it serves as the type for both declarations of identifier
+// types and values of identifier types.
+//
+// For example, if you have declared a struct Foo, and a protocol method that
+// takes a Foo, IdentifierTypeInfo will hold the same type information for each
+// of the following symbols:
+//
+// struct Foo {};
+// ~~~
+// protocol P {
+// Method(Foo foo);
+// ~~~ ~~~
+// };
+//
+// So it is both a kind of "declaration type" as well as an "identifier type".
+// Iff `IsDecl` == true, it is a "declaration type"; otherwise, it is an
+// "identifier type", and `Identifier` is the name of a declaration type that
+// can be looked up in the symbol map.
+type IdentifierTypeInfo struct {
+ // If IsDecl is true, all the type information is contained in the tagged
+ // type info objects -- Kind and one of {Bits, Const, etc.}.
+ // If IsDecl is false, Identifier and Nullable are set, and Identifier is
+ // a key to the declaration of the identifier type, in the symbolMap.
+ IsDecl bool
+ Identifier string
+ Nullable bool
+
+ Kind IdentifierKind
+ Name string
+
+ Bits *BitsTypeInfo
+ Const *ConstTypeInfo
+ Enum *EnumTypeInfo
+ TypeAlias *TypeAliasTypeInfo
+}
+
+type IdentifierKind string
+
+const (
+ BitsKind IdentifierKind = "bits"
+ ConstKind = "const"
+ EnumKind = "enum"
+ ProtocolKind = "protocol"
+ ServiceKind = "service"
+ StructKind = "struct"
+ TableKind = "table"
+ UnionKind = "union"
+ TypeAliasKind = "typeAlias"
+)
+
+type BitsTypeInfo struct {
+ Type Type
+}
+type ConstTypeInfo struct {
+ Type Type
+ Value string
+}
+type EnumTypeInfo struct {
+ Type PrimitiveSubtype
+}
+type TypeAliasTypeInfo struct {
+ Type Type
+}
+
+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
+}
+
+func (a *Analyzer) lookupSymbolInfo(name fidlcommon.Name) (*symbolInfo, error) {
+ 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 symbol `%s`", name.FullyQualifiedName())
+ }
+
+ return symInfo, nil
+}
diff --git a/fidl-lsp/analysis/type.go b/fidl-lsp/analysis/type.go
new file mode 100644
index 0000000..9ae2465
--- /dev/null
+++ b/fidl-lsp/analysis/type.go
@@ -0,0 +1,120 @@
+// 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"
+
+ fidlcommon "fidl-lsp/third_party/common"
+
+ "fidl-lsp/state"
+)
+
+// TypeOfSymbol returns the type information of `sym`.
+//
+// analysis.Type contains all the information needed to create a human-readable
+// description of the type.
+func (a *Analyzer) TypeOfSymbol(fs *state.FileSystem, sym state.Symbol) (Type, error) {
+ // If `sym` is a library name, return a `library`-kinded TypeInfo.
+ libName, err := fidlcommon.ReadLibraryName(sym.Name)
+ if err == nil {
+ if _, isLib := a.libs[libName]; isLib {
+ return Type{
+ IsLib: true,
+ }, nil
+ }
+ }
+
+ // TODO: if we want to support hovering over members (e.g. struct fields,
+ // method parameters, bits members) or protocol methods, we need to check
+ // here whether the symbol is namespaced -- whether it is inside a
+ // declaration.
+ // If it is, prepend it with that declaration's name. For example, this
+ // struct field:
+ //
+ // struct Foo {
+ // MyType my_field;
+ // ~~~~~~~~
+ // }
+ //
+ // Would become "library.name/Foo.my_field", as this is how it's stored in
+ // the symbol map.
+
+ // Otherwise, we assume it is a local or fully-qualified name
+ name, err := a.symbolToFullyQualifiedName(fs, sym)
+ if err != nil {
+ return Type{}, fmt.Errorf(
+ "could not convert symbol `%s` to fully-qualified name: %s",
+ sym.Name,
+ err,
+ )
+ }
+
+ symInfo, err := a.lookupSymbolInfo(name)
+ if err != nil {
+ return Type{}, fmt.Errorf("could not find type of symbol `%s`: %s", name.FullyQualifiedName(), err)
+ }
+
+ // Resolve identifier type, if necessary.
+ if symInfo.typeInfo.Kind == IdentifierType && !symInfo.typeInfo.Identifier.IsDecl {
+ // This means that rather than being a declaration, symInfo is a value
+ // of an identifier type, so we lookup that type's info based on the
+ // type name.
+ typeName, err := fidlcommon.ReadName(symInfo.typeInfo.Identifier.Identifier)
+ if err != nil {
+ return Type{}, fmt.Errorf(
+ "invalid identifier type `%s`: %s",
+ symInfo.typeInfo.Identifier.Identifier,
+ err,
+ )
+ }
+ t, err := a.lookupSymbolInfo(typeName)
+ if err != nil {
+ return Type{}, fmt.Errorf(
+ "could not find identifier type `%s` of symbol `%s`: %s",
+ typeName.FullyQualifiedName(),
+ name.FullyQualifiedName(),
+ err,
+ )
+ }
+ identifierType := t.typeInfo
+ identifierType.Identifier.IsDecl = false
+ identifierType.Identifier.Nullable = symInfo.typeInfo.Identifier.Nullable
+ identifierType.Identifier.Identifier = symInfo.typeInfo.Identifier.Identifier
+ return identifierType, nil
+ }
+
+ // Resolve aliased identifier type, if necessary.
+ if symInfo.typeInfo.Kind == IdentifierType &&
+ symInfo.typeInfo.Identifier.Kind == TypeAliasKind &&
+ symInfo.typeInfo.Identifier.TypeAlias.Type.Kind == IdentifierType {
+ // This means that `sym` is a type alias to an identifier type, so we
+ // need to lookup that identifier type's info based on the type name.
+ aliasedType := symInfo.typeInfo.Identifier.TypeAlias.Type
+ typeName, err := fidlcommon.ReadName(aliasedType.Identifier.Identifier)
+ if err != nil {
+ return Type{}, fmt.Errorf(
+ "invalid identifier type `%s`: %s",
+ aliasedType.Identifier.Identifier,
+ err,
+ )
+ }
+ t, err := a.lookupSymbolInfo(typeName)
+ if err != nil {
+ return Type{}, fmt.Errorf(
+ "could not find identifier type `%s` of symbol `%s`: %s",
+ typeName.FullyQualifiedName(),
+ name.FullyQualifiedName(),
+ err,
+ )
+ }
+ identifierType := t.typeInfo
+ identifierType.Identifier.Nullable = aliasedType.Identifier.Nullable
+ identifierType.Identifier.Identifier = aliasedType.Identifier.Identifier
+ symInfo.typeInfo.Identifier.TypeAlias.Type = identifierType
+ }
+
+ return symInfo.typeInfo, nil
+}
diff --git a/fidl-lsp/langserver/handler.go b/fidl-lsp/langserver/handler.go
index f688369..18a6d4b 100644
--- a/fidl-lsp/langserver/handler.go
+++ b/fidl-lsp/langserver/handler.go
@@ -69,6 +69,7 @@
DefinitionProvider: true,
DocumentFormattingProvider: true,
DocumentLinkProvider: &documentLinkOptions{},
+ HoverProvider: true,
TextDocumentSync: &lsp.TextDocumentSyncOptionsOrKind{
Options: &lsp.TextDocumentSyncOptions{
OpenClose: true,
@@ -260,6 +261,17 @@
return h.handleFormat(params)
+ case "textDocument/hover":
+ if req.Params == nil {
+ return nil, &jsonrpc2.Error{Code: jsonrpc2.CodeInvalidParams}
+ }
+ var params lsp.TextDocumentPositionParams
+ if err := json.Unmarshal(*req.Params, ¶ms); err != nil {
+ return nil, err
+ }
+
+ return h.handleHover(params)
+
default:
return nil, &jsonrpc2.Error{
Code: jsonrpc2.CodeMethodNotFound,
diff --git a/fidl-lsp/langserver/hover.go b/fidl-lsp/langserver/hover.go
new file mode 100644
index 0000000..5977b4c
--- /dev/null
+++ b/fidl-lsp/langserver/hover.go
@@ -0,0 +1,318 @@
+// 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 langserver
+
+import (
+ "fmt"
+
+ "github.com/sourcegraph/go-lsp"
+
+ "fidl-lsp/analysis"
+ "fidl-lsp/state"
+)
+
+// handleHover asks the FileSystem for the symbol at the location specified in
+// `params`, and then asks the Analyzer for the type of that symbol.
+//
+// It then converts that type to a human-readable string representation that
+// will be displayed as a tooltip, including doc comments, other attributes on
+// the type, and the type itself. For example, given this FIDL:
+//
+// library example;
+//
+// /// Foo is a struct.
+// [Attribute = "Value"]
+// struct Foo {};
+//
+// protocol Bar {
+// Method(Foo foo);
+// ~~~
+// }
+//
+// Hovering over the `~~~` would give you this tooltip:
+//
+// Foo is a struct. // In plain text
+// [Attribute = "Value"] // In monospace font
+// struct example/Foo
+//
+func (h *LangHandler) handleHover(params lsp.TextDocumentPositionParams) (lsp.Hover, error) {
+ sym, err := h.fs.SymbolAtPos(
+ state.FileID(params.TextDocument.URI),
+ state.Position{Line: params.Position.Line, Character: params.Position.Character},
+ )
+ if err != nil {
+ h.log.Printf(
+ "could not find symbol at position `%#v` in document `%s`\n",
+ params.Position,
+ params.TextDocument.URI,
+ )
+ return lsp.Hover{}, err
+ }
+
+ symType, err := h.analyzer.TypeOfSymbol(h.fs, sym)
+ if err != nil {
+ h.log.Printf("could not get type of symbol `%s`: %s", sym.Name, err)
+ return lsp.Hover{}, err
+ }
+
+ // Convert SymbolType --> []lsp.MarkedString
+ typeDescription, err := symbolTypeToMarkedStrings(sym.Name, symType)
+ if err != nil {
+ h.log.Printf("could not get string representation of symbol `%s`'s type: %s", sym.Name, err)
+ return lsp.Hover{}, err
+ }
+
+ return lsp.Hover{
+ Contents: typeDescription,
+ Range: &lsp.Range{
+ Start: lsp.Position{
+ Line: sym.Location.Range.Start.Line,
+ Character: sym.Location.Range.Start.Character,
+ },
+ End: lsp.Position{
+ Line: sym.Location.Range.End.Line,
+ Character: sym.Location.Range.End.Character,
+ },
+ },
+ }, nil
+}
+
+func newFIDLString(s string) lsp.MarkedString {
+ return lsp.MarkedString{
+ Language: "fidl",
+ Value: s,
+ }
+}
+
+func symbolTypeToMarkedStrings(name string, symType analysis.Type) ([]lsp.MarkedString, error) {
+ if symType.IsLib {
+ return []lsp.MarkedString{{
+ Language: "fidl",
+ Value: fmt.Sprintf("library %s", name),
+ }}, nil
+ }
+
+ res := []lsp.MarkedString{}
+
+ var docStr *lsp.MarkedString
+ attrList := "["
+ firstAttr := true
+ // Add attributes as a marked string
+ for _, attr := range symType.Attrs {
+ if attr.Name == "Doc" {
+ rawMarkedStr := lsp.RawMarkedString(attr.Value)
+ docStr = &rawMarkedStr
+ } else {
+ if !firstAttr {
+ attrList += ", "
+ }
+ firstAttr = false
+ if attr.Value != "" {
+ attrList += fmt.Sprintf("%s = \"%s\"", attr.Name, attr.Value)
+ } else {
+ attrList += attr.Name
+ }
+ }
+ }
+ attrList += "]"
+ // Prepend doc string if there is one
+ if docStr != nil {
+ res = append([]lsp.MarkedString{*docStr}, res...)
+ }
+ if !firstAttr {
+ res = append(res, lsp.MarkedString{
+ Language: "fidl",
+ Value: attrList,
+ })
+ }
+
+ switch symType.Kind {
+ default:
+ return nil, fmt.Errorf("unknown kind of symbol: %s", symType.Kind)
+
+ case analysis.ArrayType:
+ if symType.Array == nil {
+ goto TypeInfoNotSet
+ }
+ res = append(res, newFIDLString(shortName(symType)))
+
+ case analysis.VectorType:
+ if symType.Vector == nil {
+ goto TypeInfoNotSet
+ }
+ res = append(res, newFIDLString(shortName(symType)))
+
+ case analysis.StringType:
+ if symType.String == nil {
+ goto TypeInfoNotSet
+ }
+ res = append(res, newFIDLString(shortName(symType)))
+
+ case analysis.HandleType:
+ if symType.Handle == nil {
+ goto TypeInfoNotSet
+ }
+ res = append(res, newFIDLString(shortName(symType)))
+
+ case analysis.RequestType:
+ if symType.Request == nil {
+ goto TypeInfoNotSet
+ }
+ res = append(res, newFIDLString(shortName(symType)))
+
+ case analysis.PrimitiveType:
+ if symType.Primitive == nil {
+ goto TypeInfoNotSet
+ }
+ res = append(res, newFIDLString(shortName(symType)))
+
+ case analysis.IdentifierType:
+ if symType.Identifier == nil {
+ goto TypeInfoNotSet
+ }
+
+ if !symType.Identifier.IsDecl {
+ res = append(res, newFIDLString(shortName(symType)))
+ break
+ }
+
+ switch symType.Identifier.Kind {
+ default:
+ return nil, fmt.Errorf("unknown identifier kind: %s", symType.Identifier.Kind)
+
+ case analysis.BitsKind:
+ if symType.Identifier.Bits == nil {
+ goto IdentifierTypeInfoNotSet
+ }
+ res = append(res, newFIDLString(
+ fmt.Sprintf(
+ "bits %s: %s",
+ symType.Identifier.Name,
+ shortName(symType.Identifier.Bits.Type),
+ ),
+ ))
+
+ case analysis.EnumKind:
+ if symType.Identifier.Enum == nil {
+ goto IdentifierTypeInfoNotSet
+ }
+ res = append(res, newFIDLString(
+ fmt.Sprintf(
+ "enum %s: %s",
+ symType.Identifier.Name,
+ symType.Identifier.Enum.Type,
+ ),
+ ))
+
+ case analysis.ConstKind:
+ if symType.Identifier.Const == nil {
+ goto IdentifierTypeInfoNotSet
+ }
+ res = append(res, newFIDLString(
+ fmt.Sprintf(
+ "const %s %s = `%s`",
+ shortName(symType.Identifier.Const.Type),
+ symType.Identifier.Name,
+ symType.Identifier.Const.Value,
+ ),
+ ))
+
+ case analysis.TypeAliasKind:
+ if symType.Identifier.TypeAlias == nil {
+ goto IdentifierTypeInfoNotSet
+ }
+ res = append(res, newFIDLString(
+ fmt.Sprintf(
+ "%s: alias to %s",
+ symType.Identifier.Name,
+ shortName(symType.Identifier.TypeAlias.Type),
+ ),
+ ))
+
+ case analysis.ProtocolKind,
+ analysis.ServiceKind,
+ analysis.StructKind,
+ analysis.TableKind,
+ analysis.UnionKind:
+ res = append(res, newFIDLString(
+ fmt.Sprintf("%s %s", symType.Identifier.Kind, symType.Identifier.Name),
+ ))
+ }
+ }
+
+ return res, nil
+
+TypeInfoNotSet:
+ return nil, fmt.Errorf(
+ "symbol is tagged as a `%s` but `%s` type info not set",
+ symType.Kind,
+ symType.Kind,
+ )
+IdentifierTypeInfoNotSet:
+ return nil, fmt.Errorf(
+ "symbol is tagged as a `%s` but `%s` type info not set",
+ symType.Identifier.Kind,
+ symType.Identifier.Kind,
+ )
+}
+
+func shortName(t analysis.Type) string {
+ switch t.Kind {
+ default:
+ return "unknown type"
+
+ case analysis.ArrayType:
+ return fmt.Sprintf("array<%s>:%d", shortName(t.Array.ElementType), t.Array.ElementCount)
+
+ case analysis.VectorType:
+ str := fmt.Sprintf("vector<%s>", shortName(t.Vector.ElementType))
+ if t.Vector.ElementCount != nil {
+ str += fmt.Sprintf(":%d", *t.Vector.ElementCount)
+ }
+ if t.Vector.Nullable {
+ str += "?"
+ }
+ return str
+
+ case analysis.StringType:
+ str := "string"
+ if t.String.ElementCount != nil {
+ str += fmt.Sprintf(":%d", *t.String.ElementCount)
+ }
+ if t.String.Nullable {
+ str += "?"
+ }
+ return str
+
+ case analysis.RequestType:
+ str := fmt.Sprintf("request<%s>", t.Request.Subtype)
+ if t.Request.Nullable {
+ str += "?"
+ }
+ return str
+
+ case analysis.HandleType:
+ str := fmt.Sprintf("handle<%s>", t.Handle.Subtype)
+ if t.Handle.Nullable {
+ str += "?"
+ }
+ return str
+
+ case analysis.PrimitiveType:
+ return string(t.Primitive.Subtype)
+
+ case analysis.IdentifierType:
+ var str string
+ if t.Identifier.IsDecl {
+ str = t.Identifier.Name
+ } else {
+ str = t.Identifier.Identifier
+ }
+ if t.Identifier.Nullable {
+ str += "?"
+ }
+ return str
+ }
+}
diff --git a/fidl-lsp/langserver/hover_test.go b/fidl-lsp/langserver/hover_test.go
new file mode 100644
index 0000000..7b0bf0a
--- /dev/null
+++ b/fidl-lsp/langserver/hover_test.go
@@ -0,0 +1,366 @@
+// 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 langserver
+
+import (
+ "testing"
+
+ "github.com/sourcegraph/go-lsp"
+
+ "fidl-lsp/analysis"
+)
+
+func TestHover(t *testing.T) {
+ var elementCount uint = 10
+
+ cases := []struct {
+ symName string
+ symType analysis.Type
+ hoverText []lsp.MarkedString
+ }{
+ {
+ symName: "fuchsia.test",
+ symType: analysis.Type{
+ IsLib: true,
+ },
+ hoverText: []lsp.MarkedString{newFIDLString("library fuchsia.test")},
+ },
+ {
+ symName: "array_value",
+ symType: analysis.Type{
+ Kind: analysis.ArrayType,
+ Array: &analysis.ArrayTypeInfo{
+ ElementType: analysis.Type{
+ Kind: analysis.PrimitiveType,
+ Primitive: &analysis.PrimitiveTypeInfo{
+ Subtype: analysis.Uint32,
+ },
+ },
+ ElementCount: 10,
+ },
+ },
+ hoverText: []lsp.MarkedString{newFIDLString("array<uint32>:10")},
+ },
+ {
+ symName: "vector_without_size_constraint",
+ symType: analysis.Type{
+ Kind: analysis.VectorType,
+ Vector: &analysis.VectorTypeInfo{
+ ElementType: analysis.Type{
+ Kind: analysis.PrimitiveType,
+ Primitive: &analysis.PrimitiveTypeInfo{
+ Subtype: analysis.Uint32,
+ },
+ },
+ },
+ },
+ hoverText: []lsp.MarkedString{newFIDLString("vector<uint32>")},
+ },
+ {
+ symName: "nullable_vector_with_size_constraint",
+ symType: analysis.Type{
+ Kind: analysis.VectorType,
+ Vector: &analysis.VectorTypeInfo{
+ ElementType: analysis.Type{
+ Kind: analysis.PrimitiveType,
+ Primitive: &analysis.PrimitiveTypeInfo{
+ Subtype: analysis.Uint32,
+ },
+ },
+ ElementCount: &elementCount,
+ Nullable: true,
+ },
+ },
+ hoverText: []lsp.MarkedString{newFIDLString("vector<uint32>:10?")},
+ },
+ {
+ symName: "nullable_string_without_size_constraint",
+ symType: analysis.Type{
+ Kind: analysis.StringType,
+ String: &analysis.StringTypeInfo{
+ Nullable: true,
+ },
+ },
+ hoverText: []lsp.MarkedString{newFIDLString("string?")},
+ },
+ {
+ symName: "string_with_size_constraint",
+ symType: analysis.Type{
+ Kind: analysis.StringType,
+ String: &analysis.StringTypeInfo{
+ ElementCount: &elementCount,
+ },
+ },
+ hoverText: []lsp.MarkedString{newFIDLString("string:10")},
+ },
+ {
+ symName: "handle",
+ symType: analysis.Type{
+ Kind: analysis.HandleType,
+ Handle: &analysis.HandleTypeInfo{
+ Subtype: analysis.Vmo,
+ Nullable: true,
+ },
+ },
+ hoverText: []lsp.MarkedString{newFIDLString("handle<vmo>?")},
+ },
+ {
+ symName: "request",
+ symType: analysis.Type{
+ Kind: analysis.RequestType,
+ Request: &analysis.RequestTypeInfo{
+ Subtype: "File",
+ },
+ },
+ hoverText: []lsp.MarkedString{newFIDLString("request<File>")},
+ },
+ {
+ symName: "primitive_bool",
+ symType: analysis.Type{
+ Kind: analysis.PrimitiveType,
+ Primitive: &analysis.PrimitiveTypeInfo{
+ Subtype: analysis.Bool,
+ },
+ },
+ hoverText: []lsp.MarkedString{newFIDLString("bool")},
+ },
+ {
+ symName: "primitive_uint64",
+ symType: analysis.Type{
+ Kind: analysis.PrimitiveType,
+ Primitive: &analysis.PrimitiveTypeInfo{
+ Subtype: analysis.Uint64,
+ },
+ },
+ hoverText: []lsp.MarkedString{newFIDLString("uint64")},
+ },
+ {
+ symName: "bits_decl",
+ symType: analysis.Type{
+ Kind: analysis.IdentifierType,
+ Identifier: &analysis.IdentifierTypeInfo{
+ IsDecl: true,
+ Name: "fuchsia.test/MyBits",
+ Kind: analysis.BitsKind,
+ Bits: &analysis.BitsTypeInfo{
+ Type: analysis.Type{
+ Kind: analysis.PrimitiveType,
+ Primitive: &analysis.PrimitiveTypeInfo{
+ Subtype: analysis.Uint32,
+ },
+ },
+ },
+ },
+ },
+ hoverText: []lsp.MarkedString{newFIDLString("bits fuchsia.test/MyBits: uint32")},
+ },
+ {
+ symName: "enum_decl",
+ symType: analysis.Type{
+ Kind: analysis.IdentifierType,
+ Identifier: &analysis.IdentifierTypeInfo{
+ IsDecl: true,
+ Name: "fuchsia.test/MyEnum",
+ Kind: analysis.EnumKind,
+ Enum: &analysis.EnumTypeInfo{
+ Type: "string",
+ },
+ },
+ },
+ hoverText: []lsp.MarkedString{newFIDLString("enum fuchsia.test/MyEnum: string")},
+ },
+ {
+ symName: "const_decl",
+ symType: analysis.Type{
+ Kind: analysis.IdentifierType,
+ Identifier: &analysis.IdentifierTypeInfo{
+ IsDecl: true,
+ Name: "fuchsia.test/MyConst",
+ Kind: analysis.ConstKind,
+ Const: &analysis.ConstTypeInfo{
+ Type: analysis.Type{
+ Kind: analysis.StringType,
+ String: &analysis.StringTypeInfo{},
+ },
+ Value: "hello, world!",
+ },
+ },
+ },
+ hoverText: []lsp.MarkedString{newFIDLString("const string fuchsia.test/MyConst = `hello, world!`")},
+ },
+ {
+ symName: "protocol_decl",
+ symType: analysis.Type{
+ Kind: analysis.IdentifierType,
+ Identifier: &analysis.IdentifierTypeInfo{
+ IsDecl: true,
+ Name: "fuchsia.test/MyProtocol",
+ Kind: analysis.ProtocolKind,
+ },
+ },
+ hoverText: []lsp.MarkedString{newFIDLString("protocol fuchsia.test/MyProtocol")},
+ },
+ {
+ symName: "service_decl",
+ symType: analysis.Type{
+ Kind: analysis.IdentifierType,
+ Identifier: &analysis.IdentifierTypeInfo{
+ IsDecl: true,
+ Name: "fuchsia.test/MyService",
+ Kind: analysis.ServiceKind,
+ },
+ },
+ hoverText: []lsp.MarkedString{newFIDLString("service fuchsia.test/MyService")},
+ },
+ {
+ symName: "struct_decl",
+ symType: analysis.Type{
+ Kind: analysis.IdentifierType,
+ Identifier: &analysis.IdentifierTypeInfo{
+ IsDecl: true,
+ Name: "fuchsia.test/MyStruct",
+ Kind: analysis.StructKind,
+ },
+ },
+ hoverText: []lsp.MarkedString{newFIDLString("struct fuchsia.test/MyStruct")},
+ },
+ {
+ symName: "table_decl",
+ symType: analysis.Type{
+ Kind: analysis.IdentifierType,
+ Identifier: &analysis.IdentifierTypeInfo{
+ IsDecl: true,
+ Name: "fuchsia.test/MyTable",
+ Kind: analysis.TableKind,
+ },
+ },
+ hoverText: []lsp.MarkedString{newFIDLString("table fuchsia.test/MyTable")},
+ },
+ {
+ symName: "union_decl",
+ symType: analysis.Type{
+ Kind: analysis.IdentifierType,
+ Identifier: &analysis.IdentifierTypeInfo{
+ IsDecl: true,
+ Name: "fuchsia.test/MyUnion",
+ Kind: analysis.UnionKind,
+ },
+ },
+ hoverText: []lsp.MarkedString{newFIDLString("union fuchsia.test/MyUnion")},
+ },
+ {
+ symName: "type_alias_decl",
+ symType: analysis.Type{
+ Kind: analysis.IdentifierType,
+ Identifier: &analysis.IdentifierTypeInfo{
+ IsDecl: true,
+ Name: "fuchsia.test/AliasToVectorOfBools",
+ Kind: analysis.TypeAliasKind,
+ TypeAlias: &analysis.TypeAliasTypeInfo{
+ Type: analysis.Type{
+ Kind: analysis.VectorType,
+ Vector: &analysis.VectorTypeInfo{
+ ElementType: analysis.Type{
+ Kind: analysis.PrimitiveType,
+ Primitive: &analysis.PrimitiveTypeInfo{
+ Subtype: analysis.Bool,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ hoverText: []lsp.MarkedString{newFIDLString("fuchsia.test/AliasToVectorOfBools: alias to vector<bool>")},
+ },
+ {
+ symName: "nullable_struct_param",
+ symType: analysis.Type{
+ Kind: analysis.IdentifierType,
+ Identifier: &analysis.IdentifierTypeInfo{
+ IsDecl: false,
+ Identifier: "fuchsia.test/MyStruct",
+ Nullable: true,
+ },
+ },
+ hoverText: []lsp.MarkedString{newFIDLString("fuchsia.test/MyStruct?")},
+ },
+ {
+ symName: "type_with_attributes",
+ symType: analysis.Type{
+ Kind: analysis.IdentifierType,
+ Identifier: &analysis.IdentifierTypeInfo{
+ IsDecl: true,
+ Name: "fuchsia.test/MyProtocol",
+ Kind: analysis.ProtocolKind,
+ },
+ Attrs: []analysis.Attribute{
+ {
+ Name: "Doc",
+ Value: "Example doc comments on MyProtocol",
+ },
+ {Name: "Transitional"},
+ {
+ Name: "OtherAttribute",
+ Value: "AttributeValue",
+ },
+ },
+ },
+ hoverText: []lsp.MarkedString{
+ lsp.RawMarkedString(`Example doc comments on MyProtocol`),
+ newFIDLString(`[Transitional, OtherAttribute = "AttributeValue"]`),
+ newFIDLString(`protocol fuchsia.test/MyProtocol`),
+ },
+ },
+ }
+
+ for _, ex := range cases {
+ hoverText, err := symbolTypeToMarkedStrings(ex.symName, ex.symType)
+ if err != nil {
+ t.Errorf("could not get hover text for symbol `%s`: %s", ex.symName, err)
+ continue
+ }
+ if len(hoverText) != len(ex.hoverText) {
+ t.Errorf(
+ "incorrect number of marked strings for symbol `%s`: expected %d, got %d",
+ ex.symName,
+ len(ex.hoverText),
+ len(hoverText),
+ )
+ continue
+ }
+ for i, expMarkedString := range ex.hoverText {
+ if hoverText[i] != expMarkedString {
+ t.Errorf(
+ "incorrect hoverText for symbol `%s`: expected %v, got %v",
+ ex.symName,
+ expMarkedString,
+ hoverText[i],
+ )
+ }
+ }
+ }
+}
+
+func TestHoverInvalidType(t *testing.T) {
+ _, err := symbolTypeToMarkedStrings(
+ "invalid_type",
+ analysis.Type{
+ Kind: analysis.PrimitiveType,
+ Array: &analysis.ArrayTypeInfo{
+ ElementType: analysis.Type{
+ Kind: analysis.PrimitiveType,
+ Primitive: &analysis.PrimitiveTypeInfo{
+ Subtype: analysis.Uint64,
+ },
+ },
+ ElementCount: 255,
+ },
+ },
+ )
+ if err == nil {
+ t.Errorf("expect error for hover text on invalid type")
+ }
+}